LeakCanary是一個在安卓平臺上檢測內存泄漏的工具庫换淆。
粗略的看了以下LeakCanary的實現(xiàn)原理。
工程目錄
-
leakcanary-analyzer
負責分析內存泄漏,主要使用了com.squareup.haha:haha庫來分析
-
leakcanary-android
負責android的接入
-
leakcanary-android-no-op
空實現(xiàn)忘晤,就2個類,release后引用的空包
-
leakcanary-sample
如何使用LeakCanary的示例
leackcanary-watcher
負責監(jiān)視對象是否泄漏
工作流程
- 安裝LeakCanary
安裝LeakCanary過程中注冊監(jiān)聽Activity的生命周期。 - 監(jiān)聽Activity生命周期寄摆,當Activity發(fā)生destroyed的時候,弱引用Activity為KeyedWeakReference修赞。
- 當主線程空閑的時候執(zhí)行GC操作婶恼,判斷弱引用是否釋放桑阶。
- 弱引用沒有釋放,則找到內存泄漏勾邦,進行內存泄漏分析蚣录,之后通知和展示。
源碼解析
看源碼的時候眷篇,從初始化入手萎河,然后找到核心鏈路。
- 安裝過程
初始化的安裝流程最終調用的是
/**
*
* @param application
* @param listenerServiceClass 默認傳遞 DisplayLeakService.class
* @param excludedRefs 排除的情況 默認為AndroidExcludedRefs.createAppDefaults().build()
* @return
*/
public static RefWatcher install(Application application,
Class<? extends AbstractAnalysisResultService> listenerServiceClass,
ExcludedRefs excludedRefs) {
//是否在分析的進程(HeapAnalyzerService進程)
if (isInAnalyzerProcess(application)) {
return RefWatcher.DISABLED;
}
//在桌面顯示內存泄漏Activity(DisplayLeakActivity)的圖標
enableDisplayLeakActivity(application);
//啟用分析的回調 結果會啟用HeapAnalyzerService進行HeapDump分析來找出泄漏的源頭
HeapDump.Listener heapDumpListener =
new ServiceHeapDumpListener(application, listenerServiceClass);
//監(jiān)視器 leakcanary核心部分 后面會分析
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
//把Activity列為監(jiān)視器的監(jiān)視對象 通過監(jiān)聽Activity發(fā)生destroyed
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}
install主要做了3件事情
1. 在桌面啟用DisplayLeakActivity的圖標
2. 初始化監(jiān)聽器RefWatcher蕉饼,并監(jiān)聽Activity
3. 在監(jiān)聽到有內存泄漏后調用heapDumpListener來啟用HeapAnalyzerService
-
RefWatcher
RefWatcher是leackcanary的核心虐杯,他負責監(jiān)聽內存泄漏是否發(fā)生。
RefWatcher的成員變量
//監(jiān)聽執(zhí)行器 實現(xiàn)類 AndroidWatchExecutor 核心代碼 Looper.myQueue().addIdleHandler(IdleHandler)
private final Executor watchExecutor;
//負責日志輸出 實現(xiàn)類 AndroidDebuggerControl 通過Debug.isDebuggerConnected()來判斷是否輸出日志
private final DebuggerControl debuggerControl;
//GC觸發(fā)器 抄AOSP代碼 https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/java/lang/ref/FinalizationTester.java
private final GcTrigger gcTrigger;
//進行headDump操作 實現(xiàn)類 AndroidHeapDumper 核心代碼 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); 另外還做了一個5s超時處理 超時實現(xiàn)方法可以參考下^_^
private final HeapDumper heapDumper;
//保存在監(jiān)聽的對象 如果GC后還存在里面 說明內存泄漏了
private final Set<String> retainedKeys;
//內存被成功回收會進入該隊列 然后會更新retainedKeys
private final ReferenceQueue<Object> queue;
//在install的時候傳入的ServiceHeapDumpListener 負責dump后的回調
private final HeapDump.Listener heapdumpListener;
//排除項
private final ExcludedRefs excludedRefs;
此處需要一個圖來解釋RefWatcher工作流程
- 泄漏分析
找到泄漏點后開始啟用HeapAnalyzerService進行泄漏分析昧港。
//獲取泄漏分析結果 核心代碼 ShortestPathFinder.findPath(Snapshot snapshot, Instance leakingRef)
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//交給DisplayLeakService進行展示處理
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
找到內存泄漏的路徑的核心代碼
大概思路是從GCRoot出發(fā)擎椰,廣度優(yōu)先搜索到leakingRef就返回,其中利用excludedRefs進行剪枝慨飘。
Result findPath(Snapshot snapshot, Instance leakingRef) {
clearState();
canIgnoreStrings = !isString(leakingRef);
//搜索隊列里增加GCRoot
enqueueGcRoots(snapshot);
boolean excludingKnownLeaks = false;
LeakNode leakingNode = null;
//優(yōu)先找toVisitQueue隊列中的 找完再找toVisitIfNoPathQueue确憨,而路徑中包含toVisitIfNoPathQueue里的元素則標示excludingKnownLeaks為true
while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
LeakNode node;
if (!toVisitQueue.isEmpty()) {
node = toVisitQueue.poll();
} else {
node = toVisitIfNoPathQueue.poll();
if (node.exclusion == null) {
throw new IllegalStateException("Expected node to have an exclusion " + node);
}
excludingKnownLeaks = true;
}
// 找到泄漏點 跳出循環(huán)
if (node.instance == leakingRef) {
leakingNode = node;
break;
}
//判斷是否搜索過了 看了代碼 按我的理解 這里沒必要搞toVisitSet,toVisitIfNoPathSet瓤的,visitedSet 保留visitedSet就夠了
if (checkSeen(node)) {
continue;
}
if (node.instance instanceof RootObj) {
visitRootObj(node);
} else if (node.instance instanceof ClassObj) {
visitClassObj(node);
} else if (node.instance instanceof ClassInstance) {
visitClassInstance(node);
} else if (node.instance instanceof ArrayInstance) {
visitArrayInstance(node);
} else {
throw new IllegalStateException("Unexpected type for " + node.instance);
}
}
return new Result(leakingNode, excludingKnownLeaks);
}
- 內存泄漏通知和展示
在拿到泄漏路徑后休弃,交給DisplayLeakService進行處理。代碼很簡單就發(fā)了個通知圈膏。
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d(leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
showNotification(this, contentTitle, contentText, pendingIntent);
afterDefaultHandling(heapDump, result, leakInfo);
}
DisplayLeakActivity就不分析了塔猾,主要負責內存泄漏的展示。
總結
本文只是粗略的梳理LeakCanary流程稽坤,其中還有許多細節(jié)沒有提及丈甸。
本文分析的是master分支上的代碼,只支持監(jiān)聽Activity泄漏尿褪,不過了解了整個流程后睦擂,我們可以加入更多的監(jiān)聽對象,如WebView Fragment等杖玲。