请点赞关注,你的支持对我意义重大。
LeakCanary 是我们非常熟悉内存泄漏检测工具,它能够帮助开发者非常高效便捷地检测 Android 中常见的内存泄漏。在各大厂自研的内存泄漏检测框架(如腾讯 Matrix 和快手 Koom)的帮助文档中,也会引述 LeakCanary 原理分析。
不吹不黑,LeakCanary 源码中除了实现内存泄漏的监控方案外,还有非常多值得学习的编程技巧,只有沉下心去阅读的人才能够真正体会到。在这篇文章里,我将带你从入门开始掌握 LeakCanary 的使用场景以及使用方法,再介绍 LeakCanary 的工作流程和高级用法,最后通过源码解析深入理解原理。本文示例程序已上传到 Github: DemoHall · HelloLeakCanary ,有用请给 Star 支持,谢谢。
提示: 本文源码分析基于 2022 年 4 月发布的 LeakCanary 2.9.1。
学习路线图:
内存泄露(Memory Leaks)指不再使用的对象或数据没有被回收,随着内存泄漏的堆积,应用性能会逐渐变差,甚至发生 OOM 奔溃。在 Android 应用中的内存泄漏可以分为 2 类:
LeakCanray 是 Square 开源的 Java 内存泄漏分析工具,用于在实验室阶段检测 Android 应用中常见中的内存泄漏。
LeakCanary 的特点或优势在于提前预判出 Android 应用中最常见且影响较大的内存泄漏场景,并对此做针对性的监测手段。 这使得 LeakCanary 相比于其他排查内存泄漏的方案(如分析 OOM 异常时的堆栈日志、MAT 分析工具)更加高效。因为当内存泄漏堆积而内存不足时,应用可能从任何一次无关紧要的内存分配中抛出 OOM,堆栈日志只能体现最后一次内存分配的堆栈信息,而无法体现出导致发生 OOM 的主要原因。
目前,LeakCanary 支持以下五种 Android 场景中的内存泄漏监测:
LeakCanary 通过以下 2 点实现内存泄漏监控:
详细的源码分析下文内容。
虽然 LeakCanary 的使用方法非常简单,但是并不意味着 LeakCanary 的工作流程也非常简单。在了解 LeakCanary 的使用方法和深入 LeakCanary 的源码之前,我们先理解 LeakCanary 的核心工作流程,我将其概括为以下 5 个阶段:
收集过程中的系统通知消息
提示: LeakCanary 为不同的 App 状态设置了不同默认阈值:App 可见时阈值为 5 个泄漏对象,App 不可见时阈值为 1 个泄漏对象。举个例子,如果 App 在前台可见并且已经收集了 4 个泄漏的对象,此时 App 退到后台,LeakCanary 会在五秒后触发分析工作。
.hprof
文件存储到文件系统中。Heap Dump 的过程中会锁堆,会使应用冻结一段时间;Heap Dump 过程中的全局对话框
leakcanary-android-process
依赖项,才会使用 WorkManager 多进程策略)。分析过程 LeakCanary 使用 Shark
分析 .hprof
文件,替换了 LeakCanary 1.0 使用的 haha
;分析结束后的系统通知消息
新增的启动图标
可视化分析报告
至此,LeakCanary 一次内存泄漏分析工作流程执行完毕。
这一节,我们来介绍 LeakCanary 的基础用法。
在 build.gradle 中添加 LeakCanary 依赖,此外不需要调用任何初始化 API(LeakCanary 内部默认使用了 ContentProvider 实现无侵入初始化)。另外,因为 LeakCanary 是只在实验室环境使用的工具,所以这里要记得使用 debugImplementation
依赖配置。
build.gradle
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' }
LeakCanary 2.0 默认采用了 ContentProvider 机制实现了无侵入初始化,为了给予开发者手动初始化 LeakCanary 的可能性,LeakCanary 在 ContentProvider 中设置了布尔值开关:
AndroidManifest.xml
<application> <provider android:name="leakcanary.internal.MainProcessAppWatcherInstaller" android:authorities="${applicationId}.leakcanary-installer" android:enabled="@bool/leak_canary_watcher_auto_install" android:exported="false"/> </application>
开发者只需要在资源文件里覆写 @bool/eak_canary_watcher_auto_install
布尔值来关闭自动初始化,并在合适的时机手动调用 AppWatcher#manualInstall
。
values.xml
<resources> <bool name="leak_canary_watcher_auto_install">false</bool> </resources>
LeakCanary 为开发者提供了便捷的配置 API,并且这个配置 API 在初始化前后都允许调用。
示例程序
// Java 语法 LeakCanary.Config config = LeakCanary.getConfig().newBuilder() .retainedVisibleThreshold(3) .build(); LeakCanary.setConfig(config);
// Kotlin 语法 LeakCanary.config = LeakCanary.config.copy( retainedVisibleThreshold = 3 )
以下用一个表格总结 LeakCanary 主要的配置项:
配置项 | 描述 | 默认值 |
---|---|---|
dumpHeap: Boolean | Heap Dump 分析开关 | true |
dumpHeapWhenDebugging: Boolean | 调试时 Heap Dump 分析开关 | false |
retainedVisibleThreshold: Int | App 可见时泄漏计数阈值 | 5 |
objectInspectors: List | 对象检索器 | AndroidObjectInspectors.appDefaults |
computeRetainedHeapSize: Boolean | 是否计算泄漏内存空间 | true |
maxStoredHeapDumps: Int | 最大堆快照存储数量 | 7 |
requestWriteExternalStoragePermission: Boolean | 是否请求文件存储权限 | true |
leakingObjectFinder: LeakingObjectFinder | 引用链分析器 | KeyedWeakReferenceFinder |
heapDumper: HeapDumper | Heap Dump 执行器 | Debug.dumpHprofData |
eventListeners: List | 事件监听器 | 多个内部监听器 |
内存泄漏分析报告是 LeakCanary 所有监控和分析工作后输出的目标产物,要根据修复内存泄漏,首先就要求开发者能够读懂 LeakCanary 的分析报告。我将 LeakCanary 的分析报告总结为以下 4 个要点:
泄漏对象的引用链是分析报告的核心信息,LeakCanary 会收集泄漏对象到 GC Root 的完整引用链信息。例如,以下示例程序在 static 变量中持有一个 Helper
对象,当 Helper 被期望被垃圾回收时用 AppWatcher 监测该对象,如果未按预期被回收,则会输出以下分析报告:
示例程序
class Helper { } class Utils { public static Helper helper = new Helper(); } // Helper 无用后监测 AppWatcher.objectWatcher.watch(helper, "Helper is no longer useful")
Logcat 日志
┬─── │ GC Root: Local variable in native code │ ├─ dalvik.system.PathClassLoader instance │ ↓ PathClassLoader.runtimeInternalObjects // 表示 PathClassLoader 中的 runtimeInternalObjects 字段,它是一个 Object 数组 ├─ java.lang.Object[] array │ ↓ Object[].[43] // 表示 Object 数组的第 43 位,它是一个 Utils 类型引用 ├─ com.example.Utils class │ ↓ static Utils.helper // 表示 Utils 的 static 字段,它是一个 Helper 类型引用 ╰→ java.example.Helper
解释一下其中的符号:
├
代表一个 Java 对象;│ ↓
代表一个 Java 引用,关联的实际对象在下一行;╰→
代表泄漏的对象,即 AppWatcher.objectWatcher.watch()
直接监控的对象。用减少重复的排查工作,LeakCanary 会将相同问题重复触发的内存泄漏进行分组,分组方法是按引用链的签名。引用链签名是对引用链上经过的每个对象的类型拼接后取哈希值,既然应用链完全相同,就没必要重复排查了。
例如,对于泄漏对象 instance
,对应的泄漏签名计算公式如下:
Logcat 日志
... │ ├─ com.example.leakcanary.LeakingSingleton class │ Leaking: NO (a class is never leaking) │ ↓ static LeakingSingleton.leakedViews │ ~~~~~~~~~~~ ├─ java.util.ArrayList instance │ Leaking: UNKNOWN │ ↓ ArrayList.elementData │ ~~~~~~~~~~~ ├─ java.lang.Object[] array │ Leaking: UNKNOWN │ ↓ Object[].[0] │ ~~~ ├─ android.widget.TextView instance │ Leaking: YES (View.mContext references a destroyed activity)
对应的签名计算公式
val leakSignature = sha1Hash( "com.example.leakcanary.LeakingSingleton.leakedView" + "java.util.ArrayList.elementData" + "java.lang.Object[].[x]" ) println(leakSignature) // dbfa277d7e5624792e8b60bc950cd164190a11aa
为了提高排查内存泄漏的效率,LeakCanary 会自动帮助我们根据对象的生命周期信息或状态信息缩小排查范围,排除原本就具有全局生命周期的对象,剩下的用 ~~~
下划线标记为怀疑对象。
例如,在以下内存泄漏报告中,ExampleApplication
对象被 FontsContract.sContext
静态变量持有,表面看起来是 sContext 静态变量导致内存泄漏。其实不是,因为 ExampleApplication 的生命周期是全局的且永远不会被垃圾回收的,所以内存泄漏的根本原因一定不是因为 sContext 持有 ExampleApplication 引起的,sContext 这条引用可以排除,所以它不会用 ~~~
下划线标记。
为了提高排查内存泄漏的效率,LeakCanary 会自动将泄漏报告划分为 2 类:
其实,Library Leaks 这个名词起得并不好,应该叫作 Framework Leaks。 小彭最初在阅读官方文档后,以为 Library Leaks 是只第三方库代码产生的内存泄漏,LeakCanary 还提到开发者对于 Library Leaks 几乎无法做什么,让我一度很好奇 LeakCanary 是如何定义二方库和三方库。最后还是通过源码才得知,Library Leaks 原来是指 Android Framework 中产生的内存泄漏,例如什么 TextView、InputMethodManager 之类的。
Logcat 中的 Library Leak 标记
==================================== HEAP ANALYSIS RESULT ==================================== 0 APPLICATION LEAKS ==================================== 1 LIBRARY LEAK ... ┬─── │ GC Root: Local variable in native code │ ...
可视化分析报告中的 Library Leak 标记
LeakCanary 2.8 提供了对 Jetpack · App Startup 的支持。如果想使用 App Startup 初始化 LeakCanary,只需要替换为另一个依赖。不过,毕竟 LeakCanary 是主要在实验室环境使用的工具,这个优化的意义并不大。
build.gradle
dependencies { // 替换为另一个依赖 // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.9.1' }
对应的 App Startup 启动器源码:
AppWatcherStartupInitializer.kt
internal class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> { override fun create(context: Context) = apply { val application = context.applicationContext as Application AppWatcher.manualInstall(application) } override fun dependencies() = emptyList<Class<out Initializer<*>>>() }
由于 LeakCanary 分析堆快照的过程存在一定的内存消耗,整个分析过程一般会持续几十秒,对于一些性能差的机型会造成明显的卡顿甚至 ANR。为了优化内存占用和卡顿问题,LeakCanary 2.8 提供了对多进程的支持。开发者只需要依赖 LeakCanary 的多进程依赖项,LeakCanary 会自动将分析工作转移到子进程中(基于 androidX.work.multiprocess
):
build.gradle
dependencies { // 官方文档对多进程功能的介绍有矛盾,经过测试,以下两个依赖都需要 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.9.1' }
同时,开发者需要在自定义 Application 中检查当前进程信息,避免在 LeakCanary 的子进程中执行不必要的初始化操作:
ExampleApplication.kt
class ExampleApplication : Application() { override fun onCreate() { if (LeakCanaryProcess.isInAnalyzerProcess(this)) { return } super.onCreate() // normal init goes here, skipped in :leakcanary process. } }
Logcat 进程选项
Logcat 日志
LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof on WorkManager remote worker
LeakCanary 默认的 Java Heap Dump 使用的是 Debug.dumpHprofData()
,在 Dump 的过程中会有较长时间的应用冻结时间。 快手技术团队在开源框架 Koom 中提出了优化方案:利用 Copy-on-Write 思想,fork 子进程再进行 Heap Dump 操作。
LeakCanary 配置项可以修改 Heap Dump 执行器,示例程序如下:
示例程序
// 依赖: debugImplementation "com.kuaishou.koom:koom-java-leak:2.2.0" // 使用默认配置初始化 Koom DefaultInitTask.init(application) // 自定义 LeakCanary 配置 LeakCanary.config = LeakCanary.config.copy( // 自定义 Heap Dump 执行器 heapDumper = { ForkJvmHeapDumper.getInstance().dump(it.absolutePath) } )
Logcat 日志对比
// 使用默认的 Debug.dumpHprofData() 的日志 helloleakcanar: hprof: heap dump "/storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_18-47-28_674.hprof" starting... helloleakcanar: hprof: heap dump completed (34MB) in 1.552s objects 549530 objects with stack traces 0 LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-58-13_310.hprof on WorkManager remote worker ... // 使用快手 Koom Heap Dump 的日志 OOMMonitor_ForkJvmHeapDumper: dump /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof OOMMonitor_ForkJvmHeapDumper: before suspend and fork. OOMMonitor_ForkJvmHeapDumper: dump true, notify from pid 8567 LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof on WorkManager remote worker ...
看一眼 Koom 源码:
ForkJvmHeapDumper.java
public synchronized boolean dump(String path) { boolean dumpRes = false; int pid = suspendAndFork(); if (pid == 0) { // Child process Debug.dumpHprofData(path); exitProcess(); } else if (pid > 0) { // Parent process dumpRes = resumeAndWait(pid); } return dumpRes; } private native void nativeInit(); private native int suspendAndFork(); private native boolean resumeAndWait(int pid); private native void exitProcess();
LeakCanary 配置项可以自定义 ObjectInspector 对象检索器,在引用链上的节点中标记必要的信息和状态。标记信息会显示在分析报告中,并且会影响报告中的提示。
Leaking: NO (notLeakingReasons)
;Leaking: YES (leakingReasons)
;Leaking: UNKNOWN
。示例程序如下:
示例程序
// 自定义 LeakCanary 配置 LeakCanary.config = LeakCanary.config.copy( // 自定义对象检索器 objectInspectors = LeakCanary.config.objectInspectors + ObjectInspector { reporter -> // reporter.notLeakingReasons += "非泄漏原因" // reporter.leakingReasons += "泄漏原因" } + AppSingletonInspector( // 标记全局类的类名即可 ) )
另外,引用链 LEAKING 节点以后到第一个 NOT_LEAKING 节点中间的节点,才会用 ~~~
下划线标记为怀疑对象。例如:
使用一张示意图表示 LeakCanary 的基本架构:
旧版本的 LeakCanary 需要在 Application 中调用相关初始化 API,而在 LeakCanary v2 版本中却不再需要手动初始化,为什么呢?—— 这是因为 LeakCanary 利用了 ContentProvider 的初始化机制来间接调用初始化 API。
ContentProvider 的常规用法是提供内容服务,而另一个特殊的用法是提供无侵入的初始化机制,这在第三方库中很常见,Jetpack 中提供的轻量级初始化框架 App Startup 也是基于 ContentProvider 的方案。
MainProcessAppWatcherInstaller.kt
internal class MainProcessAppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { // 初始化 LeakCanary val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true } ... }
LeakCanary 的初始化工程可以概括为 2 项内容:
AppWathcer.kt
// LeakCanary 初始化 API @JvmOverloads fun manualInstall( application: Application, retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) ) { checkMainThread() ... // 初始化 InternalLeakCanary 内部引擎 (已简化为等价代码,后文会提到) InternalLeakCanary(application) // 注册五种 Android 泄漏场景的监控 Hook 点 watchersToInstall.forEach { it.install() } } fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List<InstallableWatcher> { // 对应 5 种 Android 泄漏场景(后文具体分析) return listOf( ActivityWatcher(application, reachabilityWatcher), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) ) }
下面展开具体分析:
初始化内容 1 - 初始化 LeakCanary 内部分析引擎: 创建 HeapDumpTrigger 触发器,并在 Android Framework 上注册前后台切换监听、前台 Activity 监听和 ObjectWatcher 的泄漏监听。
InternalLeakCanary.kt
override fun invoke(application: Application) { _application = application // 1. 检查是否运行在 debug 构建变体,否则抛出异常 checkRunningInDebuggableBuild() // 2. 注册泄漏回调,在 ObjectWathcer 判定对象发生泄漏会后回调 onObjectRetained() 方法 AppWatcher.objectWatcher.addOnObjectRetainedListener(this) // 3. 垃圾回收触发器(用于调用 Runtime.getRuntime().gc()) val gcTrigger = GcTrigger.Default // 4. 配置提供器 val configProvider = { LeakCanary.config } // 5. (主角) 创建 HeapDump 触发器 heapDumpTrigger = HeapDumpTrigger(...) // 6. App 前后台切换监听 application.registerVisibilityListener { applicationVisible -> this.applicationVisible = applicationVisible heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible) } // 7. 前台 Activity 监听(用于发送 Heap Dump 进行中的全局 Toast) registerResumedActivityListener(application) // 8. 增加可视化分析报告的桌面快捷入口 addDynamicShortcut(application) } override fun onObjectRetained() = scheduleRetainedObjectCheck() fun scheduleRetainedObjectCheck() { heapDumpTrigger.scheduleRetainedObjectCheck() }
HeapDumpTrigger.kt
// App 前后台切换状态变化回调 fun onApplicationVisibilityChanged(applicationVisible: Boolean) { if (applicationVisible) { // App 可见 applicationInvisibleAt = -1L } else { // App 不可见 applicationInvisibleAt = SystemClock.uptimeMillis() scheduleRetainedObjectCheck(delayMillis = AppWatcher.retainedDelayMillis) } } fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) { // 已简化:源码此处使用时间戳拦截,避免重复 postDelayed backgroundHandler.postDelayed({ checkScheduledAt = 0 checkRetainedObjects() }, delayMillis) }
初始化内容 2 - 在 Android Framework 中注入对五种 Android 泄漏场景的监控: 实现在对象的使用生命周期结束后,自动将对象交给 ObjectWatcher
进行监控。
以下为 5 种 Android 泄漏场景的监控原理分析:
Application#registerActivityLifecycleCallbacks(…)
接口监听 Activity#onDestroy 事件,将当前 Activity 对象交给 ObjectWatcher 监控;ActivityWatcher.kt
private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { // reachabilityWatcher 即 ObjectWatcher reachabilityWatcher.expectWeaklyReachable(activity /*被监控对象*/, "${activity::class.java.name} received Activity#onDestroy() callback") } }
FragmentAndViewModelWatcher
实现,首先是通过 Application#registerActivityLifecycleCallbacks(…)
接口监听 Activity#onCreate 事件,再通过 FragmentManager#registerFragmentLifecycleCallbacks(…)
接口监听 Fragment 的生命周期:FragmentAndViewModelWatcher.kt
// fragmentDestroyWatchers 是一个 Lambda 表达式数组 // 对应原生、AndroidX 和 Support 三个版本 Fragment 的 Hook 工具 private val fragmentDestroyWatchers: List<(Activity) -> Unit> = 略... private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { for (watcher in fragmentDestroyWatchers) { // 最终调用到下文的 invokde() 方法 watcher(activity) } } }
以 AndroidX Fragment 为例:
AndroidXFragmentDestroyWatcher.kt
override fun invoke(activity: Activity) { // 这里在 Activity#onCreate 状态执行: if (activity is FragmentActivity) { val supportFragmentManager = activity.supportFragmentManager // 注册 Fragment 生命周期监听 supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) // 注册 Activity 级别 ViewModel Hook ViewModelClearedWatcher.install(activity, reachabilityWatcher) } } private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { // 注册 Fragment 级别 ViewModel Hook ViewModelClearedWatcher.install(fragment, reachabilityWatcher) } override fun onFragmentViewDestroyed(fm: FragmentManager, fragment: Fragment) { // reachabilityWatcher 即 ObjectWatcher reachabilityWatcher.expectWeaklyReachable(fragment.view /*被监控对象*/, "${fragment::class.java.name} received Fragment#onDestroyView() callback " + "(references to its views should be cleared to prevent leaks)") } override fun onFragmentDestroyed(fm: FragmentManager, fragment: Fragment) { // reachabilityWatcher 即 ObjectWatcher reachabilityWatcher.expectWeaklyReachable(fragment /*被监控对象*/, "${fragment::class.java.name} received Fragment#onDestroy() callback") } }
ViewModelClearedWatcher.kt
// ViewModel 的子类 internal class ViewModelClearedWatcher( storeOwner: ViewModelStoreOwner, private val reachabilityWatcher: ReachabilityWatcher ) : ViewModel() { // 反射获取 ViewModelStore 中的 ViewModel 映射表,即可获取当前作用域所有 ViewModel 对象 private val viewModelMap: Map<String, ViewModel>? = try { val mMapField = ViewModelStore::class.java.getDeclaredField("mMap") mMapField.isAccessible = true mMapField[storeOwner.viewModelStore] as Map<String, ViewModel> } catch (ignored: Exception) { null } override fun onCleared() { // 遍历当前作用域所有 ViewModel 对象 viewModelMap?.values?.forEach { viewModel -> // reachabilityWatcher 即 ObjectWatcher reachabilityWatcher.expectWeaklyReachable(viewModel /*被监控对象*/, "${viewModel::class.java.name} received ViewModel#onCleared() callback") } } companion object { // 直接在 storeOwner 作用域实例化 ViewModelClearedWatcher 对象 fun install(storeOwner: ViewModelStoreOwner, reachabilityWatcher: ReachabilityWatcher) { val provider = ViewModelProvider(storeOwner, object : Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T = ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T }) provider.get(ViewModelClearedWatcher::class.java) } } }
Service 监控这部分源码比较复杂了,需要通过 2 步 Hook 来实现:
mH.mCallback
回调,监听其中的 STOP_SERVICE 消息,将即将 Destroy 的 Service 对象暂存起来(由于 ActivityThread.H 中没有 DESTROY_SERVICE 消息,所以不能直接监听到 onDestroy() 事件,需要第 2 步);IActivityManager
Binder 对象,代理其中的 serviceDoneExecuting()
方法,视为 Service#onDestroy() 的执行时机,拿到暂存的 Service 对象交给 ObjectWatcher 监控。源码摘要如下:
ServiceWatcher.kt
private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null // 暂存即将 Destroy 的 Service private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>() override fun install() { // 1. Hook mH.mCallback swapActivityThreadHandlerCallback { mCallback /*原对象*/ -> // uninstallActivityThreadHandlerCallback:用于取消 Hook uninstallActivityThreadHandlerCallback = { swapActivityThreadHandlerCallback { mCallback } } // 新对象(lambda 表达式的末行就是返回值) Handler.Callback { msg -> // 1.1 Service#onStop() 事件 if (msg.what == STOP_SERVICE) { val key = msg.obj as IBinder // 1.2 activityThreadServices:反射获取 ActivityThread mServices 映射表 <IBinder, CreateServiceData> activityThreadServices[key]?.let { // 1.3 暂存即将 Destroy 的 Service servicesToBeDestroyed[token] = WeakReference(service) } } // 1.4 继续执行 Framework 原有逻辑 mCallback?.handleMessage(msg) ?: false } } // 2. Hook AMS IActivityManager swapActivityManager { activityManagerInterface, activityManagerInstance /*原对象*/ -> // uninstallActivityManager:用于取消 Hook uninstallActivityManager = { swapActivityManager { _, _ -> activityManagerInstance } } // 新对象(lambda 表达式的末行就是返回值) Proxy.newProxyInstance(activityManagerInterface.classLoader, arrayOf(activityManagerInterface)) { _, method, args -> // 2.1 代理 serviceDoneExecuting() 方法 if (METHOD_SERVICE_DONE_EXECUTING == method.name) { // 2.2 取出暂存的即将 Destroy 的 Service val token = args!![0] as IBinder if (servicesToBeDestroyed.containsKey(token)) { servicesToBeDestroyed.remove(token)?.also { serviceWeakReference -> // 2.3 交给 ObjectWatcher 监控 serviceWeakReference.get()?.let { service -> reachabilityWatcher.expectWeaklyReachable(service /*被监控对象*/, "${service::class.java.name} received Service#onDestroy() callback") } } } } // 2.4 继续执行 Framework 原有逻辑 method.invoke(activityManagerInstance, *args) } } } override fun uninstall() { // 关闭 mH.mCallback 的 Hook uninstallActivityManager?.invoke() uninstallActivityThreadHandlerCallback?.invoke() uninstallActivityManager = null uninstallActivityThreadHandlerCallback = null } // 使用反射修改 ActivityThread 的主线程消息循环的 mH.mCallback // swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象 private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) { val mHField = activityThreadClass.getDeclaredField("mH").apply { isAccessible = true } val mH = mHField[activityThreadInstance] as Handler val mCallbackField = Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true } val mCallback = mCallbackField[mH] as Handler.Callback? // 将 swap 的返回值作为新对象,实现 Hook mCallbackField[mH] = swap(mCallback) } // 使用反射修改 AMS 与 App 通信的 IActivityManager Binder 对象 // swap 是一个 lambda 表达式,参数为 IActivityManager 的 Class 对象和接口原实现对象,返回值为注入的新对象 private fun swapActivityManager(swap: (Class<*>, Any) -> Any) { val singletonClass = Class.forName("android.util.Singleton") val mInstanceField = singletonClass.getDeclaredField("mInstance").apply { isAccessible = true } val singletonGetMethod = singletonClass.getDeclaredMethod("get") val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { "android.app.ActivityManager" to "IActivityManagerSingleton" } else { "android.app.ActivityManagerNative" to "gDefault" } val activityManagerClass = Class.forName(className) val activityManagerSingletonField = activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true } val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass] // Calling get() instead of reading from the field directly to ensure the singleton is // created. val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance) val iActivityManagerInterface = Class.forName("android.app.IActivityManager") // 将 swap 的返回值作为新对象,实现 Hook mInstanceField[activityManagerSingletonInstance] = swap(iActivityManagerInterface, activityManagerInstance!!) }
curtains
实现的。RootView 监控这部分源码也比较复杂了,需要通过 2 步 Hook 来实现:
WindowManagerGlobal.mViews
RootView 列表,获取 RootView 新增和移除的时机;View#addOnAttachStateChangeListener()
监听,在其中的 onViewDetachedFromWindow() 回调中将 View 对象交给 ObjectWatcher 监控。LeakCanary 源码摘要如下:
RootViewWatcher.kt
override fun install() { // 1. 注册 RootView 监听 Curtains.onRootViewsChangedListeners += listener } private val listener = OnRootViewAddedListener { rootView -> val trackDetached = when(rootView.windowType) { PHONE_WINDOW -> { when (rootView.phoneWindow?.callback?.wrappedCallback) { // Activity 类型已经在 ActivityWatcher 中监控了,不需要重复监控 is Activity -> false is Dialog -> { // leak_canary_watcher_watch_dismissed_dialogs:Dialog 监控开关 val resources = rootView.context.applicationContext.resources resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs) } // DreamService 屏保等 else -> true } } POPUP_WINDOW -> false TOOLTIP, TOAST, UNKNOWN -> true } if (trackDetached) { // 2. 注册 View#addOnAttachStateChangeListener 监听 rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener { val watchDetachedView = Runnable { // 3. 交给 ObjectWatcher 监控 reachabilityWatcher.expectWeaklyReachable(rootView /*被监控对象*/ , "${rootView::class.java.name} received View#onDetachedFromWindow() callback") } override fun onViewAttachedToWindow(v: View) { mainHandler.removeCallbacks(watchDetachedView) } override fun onViewDetachedFromWindow(v: View) { mainHandler.post(watchDetachedView) } }) } }
curtains 源码摘要如下:
RootViewsSpy.kt
private val delegatingViewList = object : ArrayList<View>() { // 重写 ArrayList#add 方法 override fun add(element: View): Boolean { // 回调 listeners.forEach { it.onRootViewsChanged(element, true) } return super.add(element) } // 重写 ArrayList#removeAt 方法 override fun removeAt(index: Int): View { // 回调 val removedView = super.removeAt(index) listeners.forEach { it.onRootViewsChanged(removedView, false) } return removedView } } companion object { fun install(): RootViewsSpy { return RootViewsSpy().apply { WindowManagerSpy.swapWindowManagerGlobalMViews { mViews /*原对象*/ -> // 新对象(lambda 表达式的末行就是返回值) delegatingViewList.apply { addAll(mViews) } } } } }
WindowManageSpy.kt
// Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表 // swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象 fun swapWindowManagerGlobalMViews(swap: (ArrayList<View>) -> ArrayList<View>) { windowManagerInstance?.let { windowManagerInstance -> mViewsField?.let { mViewsField -> val mViews = mViewsField[windowManagerInstance] as ArrayList<View> mViewsField[windowManagerInstance] = swap(mViews) } } }
至此,LeakCanary 初始化完成,并且成功在 Android Framework 的各个位置安插监控,实现对 Activity 和 Service 等对象进入无用状态的监听。我们可以用一张示意图描述 LeakCanary 的部分结构:
在以上步骤中,当对象的使用生命周期结束后,会交给 ObjectWatcher
监控,现在我们来具体看下它是怎么判断对象发生泄漏的。主要逻辑概括为 3 步:
watchedObject
创建一个 KeyedWeakReference
弱引用,并存储到 <UUID, KeyedWeakReference> 的映射表中;retainedUptimeMillis
字段以标记为泄漏;onObjectRetained
告知 LeakCanary 内部发生新的内存泄漏。源码摘要如下:
AppWatcher.kt
val objectWatcher = ObjectWatcher( // lambda 表达式获取当前系统时间 clock = { SystemClock.uptimeMillis() }, // lambda 表达式实现 Executor SAM 接口 checkRetainedExecutor = { mainHandler.postDelayed(it, retainedDelayMillis) }, // lambda 表达式获取监控开关 isEnabled = { true } )
ObjectWatcher.kt
class ObjectWatcher constructor( private val clock: Clock, private val checkRetainedExecutor: Executor, private val isEnabled: () -> Boolean = { true } ) : ReachabilityWatcher { if (!isEnabled()) { // 监控开关 return } // 被监控的对象映射表 <UUID,KeyedWeakReference> private val watchedObjects = mutableMapOf<String, KeyedWeakReference>() // KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏 private val queue = ReferenceQueue<Any>() // 1. 为 watchedObject 对象增加监控 @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) { // 1.1 移除 watchedObjects 中未泄漏的引用对象 removeWeaklyReachableObjects() // 1.2 新建一个 KeyedWeakReference 引用对象 val key = UUID.randomUUID().toString() val watchUptimeMillis = clock.uptimeMillis() watchedObjects[key] = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) // 2. 五秒后检查引用对象是否出现在引用队列中,否则判定发生泄漏 // checkRetainedExecutor 相当于 postDelay 五秒后执行 moveToRetained() 方法 checkRetainedExecutor.execute { moveToRetained(key) } } // 2. 五秒后检查引用对象是否出现在引用队列中,否则说明发生泄漏 @Synchronized private fun moveToRetained(key: String) { // 2.1 移除 watchedObjects 中未泄漏的引用对象 removeWeaklyReachableObjects() // 2.2 依然存在的引用对象被判定发生泄漏 val retainedRef = watchedObjects[key] if (retainedRef != null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() // 3. 回调通知 LeakCanary 内部处理 onObjectRetainedListeners.forEach { it.onObjectRetained() } } } // 移除未泄漏对象对应的 KeyedWeakReference private fun removeWeaklyReachableObjects() { var ref: KeyedWeakReference? do { ref = queue.poll() as KeyedWeakReference? if (ref != null) { // KeyedWeakReference 出现在引用队列中,说明未发生泄漏 watchedObjects.remove(ref.key) } } while (ref != null) } // 4. Heap Dump 后移除所有监控时间早于 heapDumpUptimeMillis 的引用对象 @Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) { val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis } weakRefsToRemove.values.forEach { it.clear() } watchedObjects.keys.removeAll(weakRefsToRemove.keys) } // 获取是否有内存泄漏对象 val hasRetainedObjects: Boolean @Synchronized get() { // 移除 watchedObjects 中未泄漏的引用对象 removeWeaklyReachableObjects() return watchedObjects.any { it.value.retainedUptimeMillis != -1L } } // 获取内存泄漏对象计数 val retainedObjectCount: Int @Synchronized get() { // 移除 watchedObjects 中未泄漏的引用对象 removeWeaklyReachableObjects() return watchedObjects.count { it.value.retainedUptimeMillis != -1L } } }
被监控对象 watchedObject
关联的弱引用对象:
KeyedWeakReference.kt
class KeyedWeakReference( // 被监控对象 referent: Any, // 唯一 Key,根据此字段匹配映射表中的记录 val key: String, // 描述信息 val description: String, // 监控开始时间,即引用对象创建时间 val watchUptimeMillis: Long, // 关联的引用队列 referenceQueue: ReferenceQueue<Any> ) : WeakReference<Any>(referent, referenceQueue) { // 记录实际对象 referent 被判定为泄漏对象的时间 // -1L 表示非泄漏对象,或者还未判定完成 @Volatile var retainedUptimeMillis = -1L override fun clear() { super.clear() retainedUptimeMillis = -1L } companion object { // 记录最近一次触发 Heap Dump 的时间 @Volatile @JvmStatic var heapDumpUptimeMillis = 0L } }
ObjectWatcher 判定被监控对象发生泄漏后,会通过接口方法 OnObjectRetainedListener#onObjectRetained()
回调到 LeakCanary 内部的管理器 InternalLeakCanary 处理(在前文 AppWatcher 初始化中提到过)。LeakCanary 不会每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:
源码摘要如下:
InternalLeakCanary.kt
// 从 ObjectWatcher 回调过来 override fun onObjectRetained() = scheduleRetainedObjectCheck() private lateinit var heapDumpTrigger: HeapDumpTrigger fun scheduleRetainedObjectCheck() { if (this::heapDumpTrigger.isInitialized) { heapDumpTrigger.scheduleRetainedObjectCheck() } }
HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) { // 已简化:源码此处使用时间戳拦截,避免重复 postDelayed backgroundHandler.postDelayed({ checkRetainedObjects() }, delayMillis) } private fun checkRetainedObjects() { val config = configProvider() // 泄漏对象计数 var retainedReferenceCount = objectWatcher.retainedObjectCount if (retainedReferenceCount > 0) { // 主动触发 GC,并等待 100 ms gcTrigger.runGc() // 重新获取泄漏对象计数 retainedReferenceCount = objectWatcher.retainedObjectCount } // 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值 if (retainedKeysCount < retainedVisibleThreshold) { // App 位于前台或者刚刚进入后台 if (applicationVisible || applicationInvisibleLessThanWatchPeriod) { // 发送通知提醒 showRetainedCountNotification("App visible, waiting until %d retained objects") // 延迟 2 秒再检查 scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS) return; } } // 拦截 2:计算距离上一次 HeapDump 未超过 60s val now = SystemClock.uptimeMillis() val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { // 发送通知提醒 showRetainedCountNotification("Last heap dump was less than a minute ago") // 延迟 (60 - elapsedSinceLastDumpMillis)s 再检查 scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis) return } // 移除通知提醒 dismissRetainedCountNotification() // 触发 HeapDump(此时,应用有可能在后台) dumpHeap(...) } // 真正开始执行 Heap Dump private fun dumpHeap(...) { // 1. 获取文件存储提供器 val directoryProvider = InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application) // 2. 创建 .hprof File 文件 val heapDumpFile = directoryProvider.newHeapDumpFile() // 3. 执行 Heap Dump // Heap Dump 开始时间戳 val heapDumpUptimeMillis = SystemClock.uptimeMillis() // heapDumper.dumpHeap:最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath) configProvider().heapDumper.dumpHeap(heapDumpFile) // 4. 清除 ObjectWatcher 中过期的监控 objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) // 5. 分析堆快照 InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason)) }
请求 GC 的源码可以看一眼:
GcTrigger.kt
fun interface GcTrigger { fun runGc() object Default : GcTrigger { override fun runGc() { // Runtime.gc() 相比于 System.gc() 更有可能触发 GC Runtime.getRuntime().gc() // 暂停等待 GC Thread.sleep(100) System.runFinalization() } } }
在前面的工作中,LeakCanary 已经成功生成 .hprof
堆快照文件,并且发送了一个 LeakCanary 内部事件 HeapDump
。那么这个事件在哪里被消费的呢?
一步步跟踪代码可以看到 LeakCanary 的配置项中设置了多个事件消费者 EventListener,其中与 HeapDump 事件有关的是 when{}
代码块中三个消费者。不过,这三个消费者并不是并存的,而是会根据 App 当前的依赖项而选择最优的执行策略:
LeakCanary 配置项中的事件消费者:
LeakCanary.kt
data class Config( val eventListeners: List<EventListener> = listOf( LogcatEventListener, ToastEventListener, LazyForwardingEventListener { if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener }, when { // 策略 1 - WorkerManager 多进程分析 RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->RemoteWorkManagerHeapAnalyzer // 策略 2 - WorkManager 异步分析 WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer // 策略 3 - 异步线程分析(兜底策略) else -> BackgroundThreadHeapAnalyzer } ), ... )
RemoteLeakCanaryWorkerService
,这个类位于前文提到的 com.squareup.leakcanary:leakcanary-android-process:2.9.1
依赖中。如果可以类加载成功则视为有依赖,使用 WorkerManager 多进程分析;RemoteWorkManagerHeapAnalyzer.kt
object RemoteWorkManagerHeapAnalyzer : EventListener { // 通过类加载是否成功,判断是否存在依赖 internal val remoteLeakCanaryServiceInClasspath by lazy { try { Class.forName("leakcanary.internal.RemoteLeakCanaryWorkerService") true } catch (ignored: Throwable) { false } } override fun onEvent(event: Event) { if (event is HeapDump) { // 创建并分发 WorkManager 多进程请求 val heapAnalysisRequest = OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply { val dataBuilder = Data.Builder() .putString(ARGUMENT_PACKAGE_NAME, application.packageName) .putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME) setInputData(event.asWorkerInputData(dataBuilder)) with(WorkManagerHeapAnalyzer) { addExpeditedFlag() } }.build() WorkManager.getInstance(application).enqueue(heapAnalysisRequest) } } }
RemoteHeapAnalyzerWorker.kt
internal class RemoteHeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) : RemoteListenableWorker(appContext, workerParams) { override fun startRemoteWork(): ListenableFuture<Result> { val heapDump = inputData.asEvent<HeapDump>() val result = SettableFuture.create<Result>() heapAnalyzerThreadHandler.post { // 1.1 分析堆快照 val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = { result.isCancelled }) { progressEvent -> // 1.2 发送分析进度事件 if (!result.isCancelled) { InternalLeakCanary.sendEvent(progressEvent) } } // 1.3 发送分析完成事件 InternalLeakCanary.sendEvent(doneEvent) result.set(Result.success()) } return result } }
androidx.work.WorkManager
,如果可以,则使用 WorkManager 异步分析;WorkManagerHeapAnalyzer.kt
internal val validWorkManagerInClasspath by lazy { // 判断 WorkManager 依赖,代码略 } override fun onEvent(event: Event) { if (event is HeapDump) { // 创建并分发 WorkManager 请求 val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply { setInputData(event.asWorkerInputData()) addExpeditedFlag() }.build() val application = InternalLeakCanary.application WorkManager.getInstance(application).enqueue(heapAnalysisRequest) } }
HeapAnalyzerWorker.kt
internal class HeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { // 2.1 分析堆快照 val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(inputData.asEvent()) { event -> // 2.2 发送分析进度事件 InternalLeakCanary.sendEvent(event) } // 2.3 发送分析完成事件 InternalLeakCanary.sendEvent(doneEvent) return Result.success() } }
BackgroundThreadHeapAnalyzer.kt
object BackgroundThreadHeapAnalyzer : EventListener { // HandlerThread internal val heapAnalyzerThreadHandler by lazy { val handlerThread = HandlerThread("HeapAnalyzer") handlerThread.start() Handler(handlerThread.looper) } override fun onEvent(event: Event) { if (event is HeapDump) { // HandlerThread 请求 heapAnalyzerThreadHandler.post { // 3.1 分析堆快照 val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event -> // 3.2 发送分析进度事件 InternalLeakCanary.sendEvent(event) } // 3.3 发送分析完成事件 InternalLeakCanary.sendEvent(doneEvent) } } } }
可以看到,不管采用那种执行策略,最终执行的逻辑都是一样的:
在前面的分析中,我们已经知道 LeakCanary 是通过子线程或者子进程执行 AndroidDebugHeapAnalyzer.runAnalysisBlocking
方法来分析堆快照的,并在分析过程中和分析完成后发送回调事件。现在我们来阅读 LeakCanary 的堆快照分析过程:
AndroidDebugHeapAnalyzer.kt
fun runAnalysisBlocking( heapDumped: HeapDump, isCanceled: () -> Boolean = { false }, progressEventListener: (HeapAnalysisProgress) -> Unit ): HeapAnalysisDone<*> { ... // 1. .hprof 文件 val heapDumpFile = heapDumped.file // 2. 分析堆快照 val heapAnalysis = analyzeHeap(heapDumpFile, progressListener, isCanceled) val analysisDoneEvent = ScopedLeaksDb.writableDatabase(application) { db -> // 3. 将分析报告持久化到 DB val id = HeapAnalysisTable.insert(db, heapAnalysis) // 4. 发送分析完成事件(返回到上一级进行发送:InternalLeakCanary.sendEvent(doneEvent)) val showIntent = LeakActivity.createSuccessIntent(application, id) val leakSignatures = fullHeapAnalysis.allLeaks.map { it.signature }.toSet() val leakSignatureStatuses = LeakTable.retrieveLeakReadStatuses(db, leakSignatures) val unreadLeakSignatures = leakSignatureStatuses.filter { (_, read) -> !read}.keys.toSet() HeapAnalysisSucceeded(heapDumped.uniqueId, fullHeapAnalysis, unreadLeakSignatures ,showIntent) } return analysisDoneEvent }
核心分析方法是 analyzeHeap(…)
,继续往下走:
AndroidDebugHeapAnalyzer.kt
private fun analyzeHeap( heapDumpFile: File, progressListener: OnAnalysisProgressListener, isCanceled: () -> Boolean ): HeapAnalysis { ... // Shark 堆快照分析器 val heapAnalyzer = HeapAnalyzer(progressListener) ... // 构建对象图信息 val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile) val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping()) ... // 开始分析 heapAnalyzer.analyze( heapDumpFile = heapDumpFile, graph = graph, leakingObjectFinder = config.leakingObjectFinder, // 默认是 KeyedWeakReferenceFinder referenceMatchers = config.referenceMatchers, // 默认是 AndroidReferenceMatchers computeRetainedHeapSize = config.computeRetainedHeapSize, // 默认是 true objectInspectors = config.objectInspectors, // 默认是 AndroidObjectInspectors metadataExtractor = config.metadataExtractor // 默认是 AndroidMetadataExtractor ) }
开始进入 Shark 组件:
shark.HeapAnalyzer.kt
// analyze -> analyze -> FindLeakInput.analyzeGraph private fun FindLeakInput.analyzeGraph( metadataExtractor: MetadataExtractor, leakingObjectFinder: LeakingObjectFinder, heapDumpFile: File, analysisStartNanoTime: Long ): HeapAnalysisSuccess { ... // 1. 在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象 // leakingObjectFinder 默认是 KeyedWeakReferenceFinder val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph) // 2. 分析泄漏对象的最短引用链,并按照应用链签名分类 // applicationLeaks: Application Leaks // librbuildLeakTracesaryLeaks:Library Leaks // unreachableObjects:LeakCanary 无法分析出强引用链,可以提 Stack Overflow val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds) // 3. 返回分析完成事件 return HeapAnalysisSuccess(...) } private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects { // PathFinder:引用链分析器 val pathFinder = PathFinder(graph, listener, referenceReader, referenceMatchers) // pathFindingResults:完整引用链 val pathFindingResults = pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize) // unreachableObjects:LeakCanary 无法分析出强引用链(相当于 LeakCanary 的 Bug) val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds) // shortestPaths:最短引用链 val shortestPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects) // inspectedObjectsByPath:标记信息 val inspectedObjectsByPath = inspectObjects(shortestPaths) // retainedSizes:泄漏内存大小 val retainedSizes = computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree) // 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类 // applicationLeaks: Application Leaks // librbuildLeakTracesaryLeaks:Library Leaks val (applicationLeaks, librbuildLeakTracesaryLeaks) = buildLeakTraces(shortestPaths, inspectedObjectsByPath, retainedSizes) return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects) }
可以看到,堆快照分析最终是交给 Shark 中的 HeapAnalizer 完成的,核心流程是:
第 1 步和第 3 步不用说了,继续分析最复杂的第 2 步:
shark.HeapAnalyzer.kt
// 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类 private fun FindLeakInput.buildLeakTraces( shortestPaths: List<ShortestPath> /*最短引用链*/ , inspectedObjectsByPath: List<List<InspectedObject>> /*标记信息*/ , retainedSizes: Map<Long, Pair<Int, Int>>? /*泄漏内存大小*/ ): Pair<List<ApplicationLeak>, List<LibraryLeak>> { // Application Leaks val applicationLeaksMap = mutableMapOf<String, MutableList<LeakTrace>>() // Library Leaks val libraryLeaksMap = mutableMapOf<String, Pair<LibraryLeakReferenceMatcher, MutableList<LeakTrace>>>() shortestPaths.forEachIndexed { pathIndex, shortestPath -> // 标记信息 val inspectedObjects = inspectedObjectsByPath[pathIndex] // 实例化引用链上的每个对象快照(非怀疑对象的 leakingStatus 为 NOT_LEAKING) val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes) val referencePath = buildReferencePath(shortestPath, leakTraceObjects) // 分析报告 val leakTrace = LeakTrace( gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot), referencePath = referencePath, leakingObject = leakTraceObjects.last() ) val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher() if (firstLibraryLeakMatcher != null) { // Library Leaks val signature: String = firstLibraryLeakMatcher.pattern.toString().createSHA1Hash() libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() }.second += leakTrace } else { // Application Leaks applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace } } val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) -> // 实例化为 ApplicationLeak 类型 ApplicationLeak(leakTraces) } val libraryLeaks = libraryLeaksMap.map { (_, pair) -> // 实例化为 LibraryLeak 类型 val (matcher, leakTraces) = pair LibraryLeak(leakTraces, matcher.pattern, matcher.description) } return applicationLeaks to libraryLeaks }
LeakCanary 会使用 ObjectInspector 对象检索器在引用链上的节点中标记必要的信息和状态,标记信息会显示在分析报告中,并且会影响报告中的提示。而引用链 LEAKING
节点以后到第一个 NOT_LEAKING
节点中间的节点,才会用 ~~~
下划线标记为怀疑对象。
在第 6.5 节中,LeakCanary 通过 leakingObjectFinder
标记引用信息,leakingObjectFinder 默认是 AndroidObjectInspectors.appDefaults
,也可以在配置项中自定义。
// inspectedObjectsByPath:筛选出非怀疑对象(分析报告中 ~~~ 标记的是怀疑对象) val inspectedObjectsByPath = inspectObjects(shortestPaths)
看一下可视化报告中相关源码:
DisplayLeakAdapter.kt
... val reachabilityString = when (leakingStatus) { UNKNOWN -> extra("UNKNOWN") NOT_LEAKING -> "NO" + extra(" (${leakingStatusReason})") LEAKING -> "YES" + extra(" (${leakingStatusReason})") } ...
LeakTrace.kt
// 是否为怀疑对象 fun referencePathElementIsSuspect(index: Int): Boolean { return when (referencePath[index].originObject.leakingStatus) { UNKNOWN -> true NOT_LEAKING -> index == referencePath.lastIndex || referencePath[index + 1].originObject.leakingStatus != NOT_LEAKING else -> false } }
有两个位置处理了 HeapAnalysisSucceeded
事件:
LogcatEventListener.kt
object LogcatEventListener : EventListener { ... SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(event.heapAnalysis.toString(), 120)}" } ... }
NotificationEventListener.kt
object NotificationEventListener : EventListener { ... val flags = if (Build.VERSION.SDK_INT >= 23) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } // 点击通知消息打开可视化分析报告 val pendingIntent = PendingIntent.getActivity(appContext, 1, event.showIntent, flags) showHeapAnalysisResultNotification(contentTitle,pendingIntent) ... }
至此,LeakCanary 原理分析完毕。
到这里,LeakCanary 的使用和原理分析就讲完了。不过,LeakCanary 毕竟是实验室使用的工具,如果要实现线上内存泄漏监控,你知道怎么做吗?要实现 Native 内存泄漏监控又要怎么做?关注我,带你了解更多。