工具版本
- android studio 3.6.1
- Eclipse Memory Analyzer Version 1.9.2
模拟内存泄露
- Android studio建立工程,把下面的自定义view放入项目Activity中,反复进入退出界面
package com.enjoy.memory.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
public class IOSStyleLoadingView extends View {
private final Context context;
private double radius;
private double insideRadius;
private float northwestXStart;
private float northwestYStart;
private float northXStart;
private float northYStart;
private float notheastXStart;
private float notheastYStart;
private float eastXStart;
private float eastYStart;
private float southeastXStart;
private float southeastYStart;
private float southXStart;
private float southYStart;
private float southwestXStart;
private float southwestYStart;
private float westXStart;
private float westYStart;
private float northwestXEnd;
private float northwestYEnd;
private float northXEnd;
private float northYEnd;
private float notheastXEnd;
private float notheastYEnd;
private float eastXEnd;
private float eastYEnd;
private float southeastXEnd;
private float southeastYEnd;
private float southXEnd;
private float southYEnd;
private float southwestXEnd;
private float southwestYEnd;
private float westXEnd;
private float westYEnd;
private int currentColor = 7;
String color[] = new String[]{
"#a5a5a5",
"#b7b7b7",
"#c0c0c0",
"#c9c9c9",
"#d2d2d2",
"#dbdbdb",
"#e4e4e4",
"#e4e4e4"
};
int[] colors = new int[8];
public IOSStyleLoadingView(Context context) {
this(context, null, 0);
}
public IOSStyleLoadingView(Context context, AttributeSet attrs) {
this(context, null, 0);
}
public IOSStyleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
radius = UIKits.dip2Px(context, 9);
insideRadius = UIKits.dip2Px(context, 5);
for (int i = 0; i < color.length; i++) {
colors[i] = Color.parseColor(color[i]);
}
paint.setAntiAlias(true);
paint.setStrokeWidth(UIKits.dip2Px(context, 2));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
}
Path path = new Path();
Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(northwestXStart, northwestYStart);
path.lineTo(northwestXEnd, northwestYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(northXStart, northYStart);
path.lineTo(northXEnd, northYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(notheastXStart, notheastYStart);
path.lineTo(notheastXEnd, notheastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(eastXStart, eastYStart);
path.lineTo(eastXEnd, eastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(southeastXStart, southeastYStart);
path.lineTo(southeastXEnd, southeastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(southXStart, southYStart);
path.lineTo(southXEnd, southYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(southwestXStart, southwestYStart);
path.lineTo(southwestXEnd, southwestYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[currentColor++%8]);
path.moveTo(westXStart, westYStart);
path.lineTo(westXEnd, westYEnd);
canvas.drawPath(path, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
double centerX = getMeasuredWidth() / 2;
double centerY = getMeasuredHeight() / 2;
double leg = radius * Math.cos(Math.PI / 4);
double insideLeg = insideRadius * Math.cos(Math.PI / 4);
northwestXStart = (float) (centerX - leg);
northwestYStart = (float) (centerY - leg);
northXStart = (float) centerX;
northYStart = (float) (centerY - radius);
notheastXStart = (float) (centerX + leg);
notheastYStart = (float) (centerY - leg);
eastXStart = (float) (centerX + radius);
eastYStart = (float) centerY;
southeastXStart = (float) (centerX + leg);
southeastYStart = (float) (centerY + leg);
southXStart = (float) centerX;
southYStart = (float) (centerY + radius);
southwestXStart = (float) (centerX - leg);
southwestYStart = (float) (centerY + leg);
westXStart = (float) (centerX - radius);
westYStart = (float) centerY;
northwestXEnd = (float) (centerX - insideLeg);
northwestYEnd = (float) (centerY - insideLeg);
northXEnd = (float) centerX;
northYEnd = (float) (centerY - insideRadius);
notheastXEnd = (float) (centerX + insideLeg);
notheastYEnd = (float) (centerY - insideLeg);
eastXEnd = (float) (centerX + insideRadius);
eastYEnd = (float) centerY;
southeastXEnd = (float) (centerX + insideLeg);
southeastYEnd = (float) (centerY + insideLeg);
southXEnd = (float) centerX;
southYEnd = (float) (centerY + insideRadius);
southwestXEnd = (float) (centerX - insideLeg);
southwestYEnd = (float) (centerY + insideLeg);
westXEnd = (float) (centerX - insideRadius);
westYEnd = (float) centerY;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
private ValueAnimator valueAnimator;
public void startAnimation() {
valueAnimator = ValueAnimator.ofInt(7, 0);
valueAnimator.setDuration(400);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if ((int) animation.getAnimatedValue() != currentColor) {
invalidate();
currentColor = (int) animation.getAnimatedValue();
}
}
});
valueAnimator.start();
}
}
内存分析
- 使用profiler工具进入memory界面,反复进入退出有自定义view的activity,点击
按钮进行强制GC(多点几次,防止没来得及回收的类干扰分析),进行垃圾回收,然后点击下图按钮两次(一次开始。一次终止),记录一段时间内的内存情况,勾选Activity/fragment Leaks 选项,就能看到可能存在内存泄露的activity和fragment,如下图:
2.Activity/Fragment leaks may include false positives. (意思是内存泄露可能存在误报,也就是说可能不准)在计算机领域中,“False Positive”通常指误报;“alse Negative”通常指漏报。
使用mat工具进行进一步确认
- 首先导出.hprof文件
- 需要将 .hprof 文件从 Android 格式转换为 Java SE hprof 格式。 你可以使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。
cd Library/Android/sdk/platform-tools/
hprof-conv 原始文件名.prof 转换后的文件名(随便起一个).prof
- 转换成功后,用mat工具打开转换后的文件(mat工具下载地址)
- 点击Histogram,搜索我们没有确定的Activity的名字
- 继续查找这个activity没有被回收的原因
查看引用关系如下图:
问题与总结
- 内存泄露的原因 经过上面分析是由于动画监听没有取消,追查代码得知动画监听作为内部类持有IOSStyleLoadingView引用,IOSStyleLoadingView又持有Activity的引用,最终导致无法被GC回收。
- 解决方法,在IOSStyleLoadingView中取消动画,移除监听。
...
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (valueAnimator !=null){
valueAnimator.removeAllUpdateListeners();
valueAnimator.cancel();
}
}
...