一、简介
使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary
二、使用
在app build.gradle 中加入引用:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
在 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); } }
如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。

例子代码只能够检测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监控,如下所示。
public class OtherActivity 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 { CommonUtils.getInstance(OtherActivity.this); Thread.sleep(6 * 60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1 refWatcher.watch(this); } }
MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。
它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露,当前此例onDestroy加是多余的,这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。
运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来。
提示的notification:

使用小结:
如果只关注activity的内存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,
如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:
RefWatcher refWatcher = MyApplication.getRefWatcher(this); refWatcher.watch(this);
三、使用踩坑
在刚开始使用LeakCanary的时候,遇到了几个问题导致有内存泄漏发生时LeakCanary不发生通知,这里和大家分享一下。
1、 你的应用需要有写SD权限,因为LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的应用要先申请权限
四、原理介绍
4.1 触发检测
每次当Activity/Fragment执行完onDestroy生命周期,LeakCanary就会获取到这个Activity/Fragment,然后初始化RefWatcher对它进行分析,查看是否存在内存泄漏。
4.2 判断是否存在内存泄漏
通过WeakReference
+ ReferenceQueue
来判断对象是否被系统GC回收。
软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中。
首先尝试着从ReferenceQueue中获取待分析对象:如果不为空,那么说明正在被系统回收;如果直接就返回DONE,说明已经被系统回收了;如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。
4.3 分析内存泄漏
确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录。HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链,找到最短引用链后,定位问题,排查代码将会事半功倍。

ensureGone 方法
我现在讲ensureGone
方法的完整代码贴出来,我们逐行分析:
...... // 移除所有弱引用可达对象,后面细讲 removeWeaklyReachableReferences(); ...... // 上面执行 removeWeaklyReachableReferences 方法,判断是不是监视对象已经被回收了,如果被回收了,那么说明没有发生内存泄漏,直接结束 if (gone(reference)) { return DONE; }
// 手动触发一次 GC 垃圾回收 gcTrigger.runGc();
// 再次移除所有弱引用可达对象 removeWeaklyReachableReferences();
// 如果对象没有被回收 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // 使用 Debug 类 dump 当前堆内存中对象使用情况 File heapDumpFile = heapDumper.dumpHeap(); // dumpHeap 失败的话,会走重试机制 if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); // 将hprof文件、key等属性构造一个 HeapDump 对象 HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); // heapdumpListener 分析 heapDump 对象 heapdumpListener.analyze(heapDump); } return DONE; } 复制代码
看完上述代码,基本把检测泄漏的大致过程走了一遍,下面我们来看一些具体的细节。
(1). removeWeaklyReachableReferences 方法
removeWeaklyReachableReferences
移除所有弱引用可达对象是怎么工作的?
private void removeWeaklyReachableReferences() { KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
还记得我们在 refWatcher.watch 方法保存了当前监视对象的 ref.key 了么,如果这个对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,通过 poll 操作就可以取出这个弱引用,这时候我们从retainedKeys
中移除这个 key, 代表这个对象已经被正常回收,不需要再被监视了。
(2).
那么现在来看,判断这个对象是否被回收就比较简单了?
private boolean gone(KeyedWeakReference reference) { // retainedKeys 中不包含 reference.key 的话,就代表这个对象已经被回收了 return !retainedKeys.contains(reference.key); }
整体流程如下:

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