內(nèi)存泄漏是性能優(yōu)化中必須去關注的一個方面,LeakCanary 在發(fā)現(xiàn)內(nèi)存泄漏問題上是一個優(yōu)秀的工具晓褪,今天來分析下它內(nèi)部的工作原理是怎樣的。
首先來看幾個問題:
- 集成 LeakCanary 后哭靖,安裝應用到手機上衷佃,會發(fā)現(xiàn)桌面上多了一個 LeakCanary 的圖標,這個圖標是怎么來的呢
- 內(nèi)存泄漏的問題是怎么檢測到的呢
先來看第一個問題吧鼎俘,這個比較簡單哲身,在調(diào)用 LeakCanary.install(this) 時,點進源碼贸伐,會看到這樣一行代碼
enableDisplayLeakActivity(application);
這行代碼最終執(zhí)行的是
public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled?1:2;
packageManager.setComponentEnabledSetting(component, newState, 1);
}
這里的 componentClass 是 DisplayLeakActivity.class, 在 AndroidManifest.xml 文件中聲明這個 Activity 時勘天,設置 android:enabled="false", 然后再在代碼中動態(tài)這樣設置,就會在桌面上生成新的圖標捉邢,作為 Activity 的入口脯丝。
接下來解答第二個問題,內(nèi)存泄漏的問題是怎么檢測到的伏伐,也就是說比如如何檢測 Activity finish 后還沒有被回收宠进,這里 LeakCanary 用的方法是,記錄所有的 Activity藐翎,在 Application 中注冊 LeakCanary 時通過 registerActivityLifecycleCallbacks 監(jiān)聽 Activity 的生命周期材蹬,然后在 Activity 執(zhí)行 onDestroy 時看 Activity 是否被回收实幕,如果沒有沒回收,觸發(fā) gc, 再看有沒有被回收堤器,如果還沒有被回收昆庇,那么就是有內(nèi)存泄漏了,就收集內(nèi)存泄漏相關日志信息闸溃。
大概的流程就是這樣子了整吆,具體細節(jié),如何檢測 Activity 是否被回收辉川,如何觸發(fā)了 gc, 看下 LeakCanary 的實現(xiàn)方式的主要代碼:
public void watch(Object watchedReference, String referenceName) {
//...
if(!this.debuggerControl.isDebuggerAttached()) {
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
this.retainedKeys.add(key);
// watchedReference 執(zhí)行過onDrstroy的Activity的引用表蝙,key為隨機數(shù),queue 是一個ReferenceQueue對象乓旗,在引用中用于記錄回收的對象
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
this.watchExecutor.execute(new Runnable() {
public void run() {
// 方法最后執(zhí)行到這里
RefWatcher.this.ensureGone(reference, watchStartNanoTime);
}
});
}
}
// reference 這是一個弱引用
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 這個方法做的操作是 有被回收的府蛇,從集合中移除 reference.key
// 這個方法里就利用的 Reference.queue, 從 queue 里面取出來說明是被回收的
this.removeWeaklyReachableReferences();
if(!this.gone(reference) && !this.debuggerControl.isDebuggerAttached()) {
// 進到 if 里面說明還沒有被回收
// 觸發(fā) gc, 這里觸發(fā) gc 的方式是調(diào)用 Runtime.getRuntime().gc();
this.gcTrigger.runGc();
// 重新把已回收的 key 從集合中 remove 掉
this.removeWeaklyReachableReferences();
if(!this.gone(reference)) {
// 進到 if 里,說明還沒有被回收寸齐,gc后還沒有被回收欲诺,說明這是回收不了的了,也就是發(fā)生了內(nèi)存泄漏了
long startDumpHeap = System.nanoTime();
long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 收集 hprof 文件
File heapDumpFile = this.heapDumper.dumpHeap();
if(heapDumpFile == HeapDumper.NO_DUMP) {
return;
}
long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 解析泄漏日志渺鹦,通知有泄漏扰法,并保存到本地
this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
}
}
}