Android内存优化(六)LeakCanary使用详解

2,862 阅读4分钟

相关文章
Android性能优化系列
Java虚拟机系列

1.概述

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

2.使用LeakCanary

首先配置build.gradle:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.2'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.2'
 }

接下来在Application加入如下代码。

public class LeakApplication 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);
  }
}

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

3.LeakCanary应用举例

第二节的例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示。

public class LeakApplication 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) {
        LeakApplication leakApplication = (LeakApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。
最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }
    class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = LeakApplication.getRefWatcher(this);//1
        refWatcher.watch(this);
    }
}

MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。关于内存泄漏可以查看Android内存优化(三)避免可控的内存泄漏这篇文章。
在注释1处得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。当然,在这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。
运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来,比如三星S8的通知栏如下所示。

Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为787B。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示。

内存泄漏详细页如下图所示。

点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:MainActiviy的内部类LeakThread引用了LeakThread的this$0this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。

除此之外,我们还可以将 heap dump(hprof文件)和info信息分享出去,如下图所示。

device-2017-09-02-181759_副本.png
device-2017-09-02-181759_副本.png

需要注意的是分享出去的hprof文件并不是标准的hprof文件,还需要将它转换为标准的hprof文件,这样才会被MAT识别从而进行分析,关于MAT可以查看Android内存优化(五)详解内存分析工具MAT这篇文章。

解决方法就是将LeakThread改为静态内部类。

public class MainActivity extends AppCompatActivity {
  ...
    static class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
...
}

再次运行程序LeakThread就不会给出内存泄漏的提示了。

参考资料
《高性能Android应用开发》
使用LeakCanary检测安卓中的内存泄漏(实战)
github.com/square/leak…
LeakCanary 中文使用说明
Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Android相关原创技术干货。
扫一扫下方二维码或者长按识别二维码,即可关注。