借助profiler和mat进行内存泄露的分析

工具版本

  • android studio 3.6.1
  • Eclipse Memory Analyzer Version 1.9.2

模拟内存泄露

  1. 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();
    }

}

内存分析

  1. 使用profiler工具进入memory界面,反复进入退出有自定义view的activity,点击强制GC
    按钮进行强制GC(多点几次,防止没来得及回收的类干扰分析),进行垃圾回收,然后点击下图按钮两次(一次开始。一次终止),记录一段时间内的内存情况,勾选Activity/fragment Leaks 选项,就能看到可能存在内存泄露的activity和fragment,如下图:
    在这里插入图片描述
    2.Activity/Fragment leaks may include false positives. (意思是内存泄露可能存在误报,也就是说可能不准)在计算机领域中,“False Positive”通常指误报;“alse Negative”通常指漏报。

使用mat工具进行进一步确认

  1. 首先导出.hprof文件
    在这里插入图片描述
  2. 需要将 .hprof 文件从 Android 格式转换为 Java SE hprof 格式。 你可以使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。
//1.首先进入到该目录
 cd Library/Android/sdk/platform-tools/
 // 2.进行格式转换
hprof-conv  原始文件名.prof   转换后的文件名(随便起一个).prof
//例如 hprof-conv /Users/zyl/Downloads/memory-20200412T212233.hprof /Users/zyl/Downloads/memory-mat.hprof
  1. 转换成功后,用mat工具打开转换后的文件(mat工具下载地址
    在这里插入图片描述
  2. 点击Histogram,搜索我们没有确定的Activity的名字
    在这里插入图片描述
  3. 继续查找这个activity没有被回收的原因
    在这里插入图片描述
    查看引用关系如下图:
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200412220137613.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODY4NzMwMw==,size_16,color_FFFFFF,t_70

问题与总结

  1. 内存泄露的原因 经过上面分析是由于动画监听没有取消,追查代码得知动画监听作为内部类持有IOSStyleLoadingView引用,IOSStyleLoadingView又持有Activity的引用,最终导致无法被GC回收。
  2. 解决方法,在IOSStyleLoadingView中取消动画,移除监听。
...
 @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (valueAnimator !=null){
            valueAnimator.removeAllUpdateListeners();
            valueAnimator.cancel();
        }
    }
...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨三点的北京

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值