一. 概述:
流程分為三塊:
1.監(jiān)聽(tīng)
2.檢測(cè)泄露
3.分析
監(jiān)聽(tīng)
在Android中,當(dāng)一個(gè)Activity走完onDestroy生命周期后,說(shuō)明該頁(yè)面已經(jīng)被銷毀了,應(yīng)該被系統(tǒng)GC回收。通過(guò)Application.registerActivityLifecycleCallbacks()方法注冊(cè)Activity生命周期的監(jiān)聽(tīng),每當(dāng)一個(gè)Activity頁(yè)面銷毀時(shí)候,獲取到這個(gè)Activity去檢測(cè)這個(gè)Activity是否真的被系統(tǒng)GC。
檢測(cè)
當(dāng)獲取了待分析的對(duì)象后,需要確定這個(gè)對(duì)象是否產(chǎn)生了內(nèi)存泄漏。
通過(guò)WeakReference + ReferenceQueue來(lái)判斷對(duì)象是否被系統(tǒng)GC回收,WeakReference 創(chuàng)建時(shí),可以傳入一個(gè) ReferenceQueue 對(duì)象。當(dāng)被 WeakReference 引用的對(duì)象的生命周期結(jié)束,一旦被 GC 檢查到,GC 將會(huì)把該對(duì)象添加到 ReferenceQueue 中,待ReferenceQueue處理。當(dāng) GC 過(guò)后對(duì)象一直不被加入 ReferenceQueue,它可能存在內(nèi)存泄漏。
當(dāng)我們初步確定待分析對(duì)象未被GC回收時(shí)候,手動(dòng)觸發(fā)GC,二次確認(rèn)。
分析
分析這塊使用了Square的另一個(gè)開(kāi)源庫(kù)haha,https://github.com/square/haha,利用它獲取當(dāng)前內(nèi)存中的heap堆信息的快照snapshot,然后通過(guò)待分析對(duì)象去snapshot里面去查找強(qiáng)引用關(guān)系。
二. 源碼流程
1.監(jiān)聽(tīng):
在Application里加入代碼:
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(getApplication());
然后, install里調(diào)用的是:
if (refWatcher != DISABLED) {
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
所以, Application里默認(rèn)會(huì)放對(duì)Activity和Fragment的監(jiān)控。
放的過(guò)程比較有意思:
先: application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
在這里lifecycleCallbacks的定義是:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
Application里這個(gè)Callback的調(diào)用時(shí)機(jī)是:
在Activity.java里, 每個(gè)方法比如onCreate里,會(huì)有調(diào)用:
getApplication().dispatchActivityCreated(this, savedInstanceState);
而dispatchActivityCreated里就會(huì)調(diào)用自己的callback相應(yīng)的代碼
2 檢測(cè)
在onActivityDestroy里調(diào)用的是:refWatcher.watch(activity);
watch的主要工作就是這個(gè):
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
對(duì)每一個(gè)監(jiān)控的對(duì)象, 隨機(jī)生成一個(gè)key,放在retainedKeys(private final Set<String> retainedKeys;)中。這個(gè)是作為以后對(duì)象是否還存活的依據(jù)。
這里的關(guān)鍵點(diǎn)在創(chuàng)建KeyedWeakReference的時(shí)候, 傳入了queue。這個(gè)是WeakReference的一個(gè)特有功能,如果創(chuàng)建一個(gè)弱引用, 并且傳入一個(gè)queue的話, 那么當(dāng)gc回收這個(gè)弱引用的時(shí)候, 會(huì)將相應(yīng)的弱引用放在傳入的queue中。這個(gè)很重要!
接下來(lái), 看ensureGoneAsync的實(shí)現(xiàn):
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
其中的removeWeaklyReachableReferences就是
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
根據(jù)gc回收時(shí)放入queue里的弱引用,將剛才生成key時(shí)放入的retainKeys里的被回收的對(duì)象的key去掉。
而怎么根據(jù)queue來(lái)找到key呢?就是在創(chuàng)建弱引用的時(shí)候, 將key作為參數(shù)傳給了弱引用并且存下來(lái)當(dāng)做一個(gè)屬性。
下面繼續(xù)看ensureGone的步驟:
remove后retainkeys依然包含我們監(jiān)控的key的話, 就繼續(xù)執(zhí)行g(shù)c(gc這里也有一點(diǎn)值得注意的, 放在gc篇里了)。然后看reference是否依然包含key:
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
然后,就檢測(cè)到有內(nèi)存泄露了。下面是分析的步驟
- 分析
File heapDumpFile = heapDumper.dumpHeap(); 先拿到dump文件。
AndroidHeapDumper.java中的dumpHeap:
@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;
}
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
dump生成hprof文件是調(diào)用:Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
這是集成在vm中的一個(gè)功能,不做詳細(xì)討論,大致就是把當(dāng)前的內(nèi)存狀態(tài)(對(duì)象引用關(guān)系)放到一個(gè)hprof文件中。生成文件后,彈窗消失。
然后就是生成一個(gè)HeapDump的對(duì)象, 用heapdumpListner:heapdumpListener.analyze(heapDump);
ServiceHeapDumpListener.java:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
而這里的HeapAnalyzerService是一個(gè)IntentService。在這個(gè)新啟動(dòng)的service中執(zhí)行對(duì)hprof文件的分析查找, 最后打印出泄露棧。具體HeapAnalyzerService的工作流程, 會(huì)在HeapAnalyzerService篇講。
參考文章:
https://allenwu.itscoder.com/leakcanary-source
https://blog.csdn.net/xiaohanluo/article/details/78196755
https://allenwu.itscoder.com/leakcanary-source
https://ivanljt.github.io/blog/2017/12/15/%E6%8B%86%E8%BD%AE%E5%AD%90%E7%B3%BB%E5%88%97%E2%80%94%E2%80%94LeakCanary%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/
https://cloud.tencent.com/developer/article/1327724