一、簡介
使用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原理分析
附: