Android:性能优化工具之内存泄露-LeakCanary

目录

一 简介

二 使用

三 进阶用法

四 hprof分析复杂内存泄露问题

五 使用小结

六 使用踩坑

6.1 权限

6.2  NullPointerException

七 原理简单介绍

7.1 触发检测

7.2 判断是否存在内存泄漏

7.3 分析内存泄漏

八 总结


参考源码地址:https://github.com/LucasXu01/Autils

一 简介

使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary ;

github地址:https://github.com/square/leakcanary

官方文档:https://square.github.io/leakcanary/


二 使用

在app build.gradle 中加入引用:

dependencies {
    //内存泄露
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'

}

在 Application 中:

public class MyApplication extends Application {
    @Override public void onCreate() {
    super.onCreate();

    /** 基础使用 */
    if (LeakCanary.isInAnalyzerProcess(this)) {//1
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);


  }
}

如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。

Android:性能优化工具之内存泄露-LeakCanary_第1张图片

 


三 进阶用法

例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。

改写Application,如下所示:

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }

    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        MyApplication leakApplication = (MyApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。

最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

泄露所在的活动:

/**
 * 内存泄露
 * @author lucas
 * created at 2019/9/22 2:53 PM
 */
public class LeakActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak_test);

        LeakTestManager manager = LeakTestManager.getInstance(LeakActivity.this);
    }
    

外部引用:

public class LeakTestManager {

    private static LeakTestManager manager;
    private Context context;

    private LeakTestManager(Context context) {
        this.context = context;
    }

    /**
     *  如果传入的context是activity/service的上下文,会导致内存泄漏
     *  原因是我们的manger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长
     *  当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏
     */

    public static LeakTestManager getInstance(Context context) {
        if (manager == null) {
            manager = new LeakTestManager(context);
        }
        return manager;
    }

    //正确写法
    public static LeakTestManager getInstanceSafe(Context context) {
        if (manager == null) {
            manager = new LeakTestManager(context.getApplicationContext());
        }
        return manager;
    }

}

LeakActivity存在内存泄漏,原因就是非静态内部类LeakTestManager持有外部类LeakActivity的引用,LeakTestmanger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长,当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏。

***LeakCanary会自动监控Activity执行onDestroy方法之后是否发生内存泄露,当前此例onDestroy加是多余的,这里只是为了方便举例,如果想要监控Fragment,则必须在Fragment中添加如上的onDestroy方法!

运行程序,打开泄露的活动界面,需要稍等片刻,打开LeakCanary内存泄漏信息就会展示出来(原生安卓会直接Notification,深度定制过的类似小米不会弹出弹窗,因不同版本而异):

Android:性能优化工具之内存泄露-LeakCanary_第2张图片

 

Android:性能优化工具之内存泄露-LeakCanary_第3张图片


四 hprof分析复杂内存泄露问题

上面内存泄漏的例子比较简单,可以很明显的看出泄漏的原因,有时候我们会遇到比较复杂的内存泄漏情况,这个时候我们可能需要分析一下hprof文件。
打开Device File explorer,找到安卓中的Download文件夹:

Android:性能优化工具之内存泄露-LeakCanary_第4张图片

双击hprof文件,再进行分析:

Android:性能优化工具之内存泄露-LeakCanary_第5张图片

hprof文件里信息很多,其中常用字段的含义:

名称 描述
Class name 类名
Total Count 该类的实例总数
Heap Count 所选择的堆中该类的实例的数量
Sizeof 单个实例所占空间大小(如果每个实例所占空间大小不一样则显示0)
Shallow Size 堆里所有实例大小总和(Heap Count * Sizeof)
Retained Size 该类所有实例所支配的内存大小
Instance 具体的实例
Reference Tree 所选实例的引用,以及指向该引用的引用。
Depth GC根节点到所选实例的最短路径的深度
Shallow Size 所选实例的大小
Dominating Size 所选实例所支配的内存大小

五 使用小结


如果只关注activity的内存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,

如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:

RefWatcher refWatcher = MyApplication.getRefWatcher(this);
     refWatcher.watch(this);

六 使用踩坑

在刚开始使用LeakCanary的时候,遇到了几个问题导致有内存泄漏发生时LeakCanary不发生通知,这里和大家分享一下。

6.1 权限

你的应用需要有写SD权限,因为LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的应用要先申请权限。但在本文中的1.6.3版本中,你只需要在manifest清单中添加下面两条权限即可,需要时LeakCanary会自动申请零时零时权限,不需要自己再在代码中手动控制了。





6.2  NullPointerException

java.lang.NullPointerException: Attempt to invoke virtual method’boolean java.lang.String.equals(java.lang.Object)’ on a null object reference

atcom.squareup.leakcanary.HeapAnalyzer.findLeakingReference(HeapAnalyzer.java:160)

….

如果遇到这个问题,是LeakCanary的版本过低了,不适合Android6.0及以上的机型,我看网上大部分引用的还是基于1.3的版本,升级到高版本的就没问题了。


七 原理简单介绍

7.1 触发检测

每次当Activity/Fragment执行完onDestroy生命周期,LeakCanary就会获取到这个Activity/Fragment,然后初始化RefWatcher对它进行分析,查看是否存在内存泄漏。

7.2 判断是否存在内存泄漏

首先尝试着从ReferenceQueue队列中获取待分析对象(软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中),如果不为空,那么说明正在被系统回收,如果直接就返回DONE,说明已经被系统回收了,如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

没有引用了,就去ReferenceQueue中找,没有就是泄露了。

7.3 分析内存泄漏

确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录。HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链。找到最短引用链后,定位问题,排查代码将会事半功倍。

Android:性能优化工具之内存泄露-LeakCanary_第6张图片

整体流程如下:

Android:性能优化工具之内存泄露-LeakCanary_第7张图片

详细原理分析可以参考:LeakCanary原理分析


八 总结

LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。

6.1 无法检测出Service中的内存泄漏问题

6.2 如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。

所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。

 

参考:

性能优化工具(九)-LeakCanary : https://www.jianshu.com/p/70b8c87ea877

Android内存优化(六)LeakCanary使用详解:http://liuwangshu.cn/application/performance/ram-6-leakcanary.html

LeakCanary原理解析:https://blog.csdn.net/xiaohanluo/article/details/78196755

性能优化总结:https://blog.csdn.net/baidu_31093133/article/details/81779711

「Leakcanary 源码分析」看这一篇就够了:https://www.jianshu.com/p/9cc0db9f7c52

 

你可能感兴趣的:(Android)