你是否曾被OOM問題困擾過?對絕大部分客戶端開發人員來講,內存溢出問題如同幽靈通常如影相隨,平時也許不必定容易察覺,可一旦發生了真想把它揪出來時,又只能抓耳撓腮不得其蹤。 然而幸運的是,開源屆的良心企業Square推出了LeakCanary,這是一個用來檢查 Android 下內存泄漏的開源庫,從而幫助咱們可以以簡潔明瞭的方式定位內存泄露隱患。這篇文章主要介紹其用法、架構和其背後的實現原理。html
Square 有篇文章介紹了開發這個庫的緣由。他們的一個付款流程裏,他們直接用 Bitmap 來畫用戶簽名,Bitmap 大小和屏幕分辨率是同樣的。問題來了,在試圖建立這個 Bitmap 對象時,常常會引起OutOfMemoryError。他們嘗試了一些解決方案,但都沒解決問題:java
最終這些都不起做用,他們才發現走錯了方向。當存在內存泄漏時,可用內存愈來愈少,這個時候 OOM 能夠發生在任何地方,特別是試圖建立一些大內存對象,如 Bitmap 的時候。android
雖然咱們能夠經過MAT對內存泄漏問題進行分析,可是畢竟步驟繁瑣,還不必定能從茫茫對象中定位到異常發生現場。若是有一個工具能自動完成這些事情,甚至在發生 OOM 以前,就把內存泄漏報告給你,豈不美哉?LeakCanary 就是用來幹這個事情的。在測試你的 App 時,若是發生了內存泄漏,狀態欄上會有通知,同時在logcat 上也會有相應的 log。git
咱們常常把 Activity 看成爲 Context 對象使用,在不一樣場合由各類對象引用 Activity。因此,Activity 泄漏是一個重要的須要檢查的內存泄漏之一。github
public class ExampleApplication extends Application { public static RefWatcher getRefWatcher(Context context) { ExampleApplication application = (ExampleApplication) context.getApplicationContext(); return application.refWatcher; } private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } }
LeakCanary.install()返回一個配置好了的 RefWatcher 實例。它同時安裝了 ActivityRefWatcher 來監控 Activity 泄漏。即當 Activity.onDestroy() 被調用以後,若是這個 Activity 沒有被銷燬,logcat 就會打印出以下信息告訴你內存泄漏發生了。架構
* com.example.leakcanary.MainActivity has leaked: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1') * references com.example.leakcanary.MainActivity$2.this$0 (anonymous class extends android.os.AsyncTask) * leaks com.example.leakcanary.MainActivity instance * Reference Key: c4d32914-618d-4caf-993b-4b835c255873 * Device: Genymotion generic Google Galaxy Nexus - 4.2.2 - API 17 - 720x1280 vbox86p * Android Version: 4.2.2 API: 17 * Durations: watch=5100ms, gc=104ms, heap dump=82ms, analysis=3008ms
LeakCanary 自動檢測 Activity 泄漏只支持 Android ICS 以上版本。由於 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。若是要在 ICS 以前監測 Activity 泄漏,能夠重載 Activity.onDestroy() 方法,而後在這個方法裏調用 RefWatcher.watch(this) 來實現。app
public abstract class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } }
當 Fragment.onDestroy() 被調用以後,若是這個 fragment 實例沒有被銷燬,那麼就會從 logcat 裏看到相應的泄漏信息。ide
... RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(someObjNeedGced);
當 someObjNeedGced還在內存中時,就會在 logcat 裏看到內存泄漏的提示。函數
dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' }
在 debug 版本上,集成 LeakCanary 庫,並執行內存泄漏監測,而在 release 版本上,集成一個無操做的 wrapper ,這樣對程序性能就不會有影響。工具
LeakCanary 的機制以下:
(1). 如何導出 hprof 文件
@Override public File dumpHeap() { File heapDumpFile = leakDirectoryProvider.newHeapDumpFile(); if (heapDumpFile == RETRY_LATER) { return RETRY_LATER; } FutureResult<Toast> waitingForToast = new FutureResult<>(); showToast(waitingForToast); if (!waitingForToast.wait(5, SECONDS)) { CanaryLog.d("Did not dump heap, too much time waiting for Toast."); return RETRY_LATER; } Toast toast = waitingForToast.get(); try { Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); cancelToast(toast); return heapDumpFile; } catch (Exception e) { CanaryLog.d(e, "Could not dump heap"); // Abort heap dump return RETRY_LATER; } }
能夠參閱 AndroidHeapDumper.java 的代碼。
(2). 如何分析 hprof 文件
這是個比較大的話題,感興趣的能夠移步另一個開源庫 HAHA,它的祖先是 MAT。
(3). 如何使用 HandlerThread
能夠參閱 AndroidWatchExecutor.java的代碼,特別是關於 Handler, Loop 的使用。
(4). 如何判斷某個變量是否被已經被 GC 回收
能夠參閱 RefWatcher.java 的 ensureGone() 函數。最主要是利用WeakReference和 ReferenceQueue 機制。簡單地講,就是當弱引用 WeakReference 所引用的對象被回收後,這個 WeakReference 對象就會被添加到 ReferenceQueue 隊列裏,咱們能夠經過其 poll() 方法獲取到這個被回收的對象的 WeakReference 實例,經過判斷其是否爲 null 進而知道須要監控的對象是否被回收了。