前言
内存优化是 Android 开发无法避免的一个问题,而内存泄漏又让开发者花费了大量的时间,那怎样才能快速定位内存泄漏了?对于内存泄漏的检测,有 Android Studio 自带的 Profiler,以及 LeakCanary等。
由于在项目中使用了 LeakCanary,很好奇它的工作原理,它是如何对对象的泄漏进行检测的?什么样的对象判定为已经发生了内存泄漏?
可达性分析算法
可达性分析算法是理解 LeakCanary 的前置知识,它可以用来判断一个对象是否是垃圾,如果是垃圾的话,需要进行垃圾回收。之前在写 JVM 的时候讲到过,总结地说:这是一种垃圾检测的方法,内存泄漏就是一个成为垃圾的对象,没有办法被垃圾回收器回收。
LeakCanary 工作原理
一、添加依赖
使用 LeakCanary 需要添加依赖如下:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
自定义 Application,在 onCreate() 中需要对 LeakCanary 进行构建:
public void onCreate() {
super.onCreate();
// 内存泄漏的检测需要运行在一个独立的进程,如果当前进程不在 LeakCanary 进程,则需要进行初始化
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
// 关键代码,构建了 RefWatcher 对象
LeakCanary.install(this);
}
当然,如果构建的是 LeakCanary 新版本的依赖,已经不需要添加如上代码。
二、构建 RefWatcher 对象
具体又可以分为以下几个步骤:
- refWatcher(): 构建一个 AndroidRefWatcherBuilder 对象
- listenerServiceClass(): 传入展示分析结果的 Service
- excludedRefs(): 排除 Android SDK 或者一些第三方库导致的内存泄漏
- buildAndInstall(): 构建 RefWatcher 对象,开始对应用的进行内存泄漏的监听。
其中最核心的是在第4步,下面俩看具体的代码实现。
三、监听内存泄漏
public @NonNull RefWatcher buildAndInstall() {
// ...
// 1、构建 RefWatcher 对象
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
// 2、监听所有的 Activity
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 3、监听所有的 Fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
接下来重点关注 Activity 的内存泄漏的分析。
四、监听 Activity 内存泄漏
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//通过调用 registerActivityLifecycleCallbacks() 来监听 Activity 的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
传入的这个回调如下,可以看到实际上调用了 RefWatcher 的 watch 方法:
接下来看 refWatcher.watch(activity) 的具体实现:
public void watch(Object watchedReference, String referenceName) {
// 空值检查...
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// 使用 key 和 watchedReference 构造一个 KeyedWeakReference 弱引用队列
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 监测是否发生了内存泄漏
ensureGoneAsync(watchStartNanoTime, reference);
}
这里涉及到了弱引用相关的知识,监测的原理就是利用了 Java 的 WeakReference 和 ReferenceQueue,如果被监测的这个 Activity 是引用可达的,那么就会被放入 ReferenceQueue 中,通过监测 ReferenceQueue 是否存在该 Activity 就可以知道是否发生了内存泄漏(泄漏的这个 Activity 没有被回收,依然被其他对象所引用着,那么就不能在引用队列中找到这个 Activity,因此就可以判断这个 Activity 发生了内存泄漏)。
具体的监测细节策略在 ensureGone() 中实现:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
// 1、如果存在引用队列中,说明已经被 GC 回收了,手动移除 retainedKeys 中的 key
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
return RETRY;//debug 不进行检测分析
}
// 2、如果 retainedKeys 不存在该引用对应的 key 了,说明已经被垃圾回收,直接返回
if (gone(reference)) {
return DONE;
}
// 3、手动触发 GC,检查该引用是否能被 GC 回收,回收的话会放到引用队列里
gcTrigger.runGc();
// 如果引用队列存在,需要删除 retainedKeys 中该引用对应的 key
removeWeaklyReachableReferences();
// 4、如果 retainedKeys 还存在 Activity 对应的 key,则说明发生了内存泄漏,生成堆内存快照,分析快照
if (!gone(reference)) {
//
// 5、生成 HeapDump 文件
File heapDumpFile = heapDumper.dumpHeap();
//...
// 6、生成 HeapDump 对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 7、调用 HeapAnalyzerService,开启后台执行分析任务
heapdumpListener.analyze(heapDump);
}
return DONE;
}
这里的 heapdumpListener 实际上是 ServiceHeapDumpListener,在它的 analyze() 内部调用了 HeapAnalyzerService#runAnalysis():
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
// 启动 HeapAnalyzerService
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
}
HeapAnalyzerService 继承自 IntentService,它被启动后会在子线程中执行具体的任务,即调用 onHandleIntentInForeground,任务执行完成后会自动销毁,接下来看具体的流程:
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// 生成 HeapAnalyzer对象
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// 分析对内存快照,找出 GC root 最短引用路径
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 启动 DisplayLeakService 记录日志和展示通知
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}