LeakCanary是一个在安卓平台上检测内存泄漏的工具库。
粗略的看了以下LeakCanary的实现原理。
工程目录
-
leakcanary-analyzer
负责分析内存泄漏,主要使用了com.squareup.haha:haha库来分析
-
leakcanary-android
负责android的接入
-
leakcanary-android-no-op
空实现,就2个类,release后引用的空包
-
leakcanary-sample
如何使用LeakCanary的示例
leackcanary-watcher
负责监视对象是否泄漏
工作流程
- 安装LeakCanary
安装LeakCanary过程中注册监听Activity的生命周期。 - 监听Activity生命周期,当Activity发生destroyed的时候,弱引用Activity为KeyedWeakReference。
- 当主线程空闲的时候执行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);
//监视器 leakcanary核心部分 后面会分析
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
//把Activity列为监视器的监视对象 通过监听Activity发生destroyed
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}
install主要做了3件事情
1. 在桌面启用DisplayLeakActivity的图标
2. 初始化监听器RefWatcher,并监听Activity
3. 在监听到有内存泄漏后调用heapDumpListener来启用HeapAnalyzerService
-
RefWatcher
RefWatcher是leackcanary的核心,他负责监听内存泄漏是否发生。
RefWatcher的成员变量
//监听执行器 实现类 AndroidWatchExecutor 核心代码 Looper.myQueue().addIdleHandler(IdleHandler)
private final Executor watchExecutor;
//负责日志输出 实现类 AndroidDebuggerControl 通过Debug.isDebuggerConnected()来判断是否输出日志
private final DebuggerControl debuggerControl;
//GC触发器 抄AOSP代码 https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/java/lang/ref/FinalizationTester.java
private final GcTrigger gcTrigger;
//进行headDump操作 实现类 AndroidHeapDumper 核心代码 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); 另外还做了一个5s超时处理 超时实现方法可以参考下^_^
private final HeapDumper heapDumper;
//保存在监听的对象 如果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出发,广度优先搜索到leakingRef就返回,其中利用excludedRefs进行剪枝。
Result findPath(Snapshot snapshot, Instance leakingRef) {
clearState();
canIgnoreStrings = !isString(leakingRef);
//搜索队列里增加GCRoot
enqueueGcRoots(snapshot);
boolean excludingKnownLeaks = false;
LeakNode leakingNode = null;
//优先找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;
}
// 找到泄漏点 跳出循环
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进行处理。代码很简单就发了个通知。
@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流程,其中还有许多细节没有提及。
本文分析的是master分支上的代码,只支持监听Activity泄漏,不过了解了整个流程后,我们可以加入更多的监听对象,如WebView Fragment等。