携手创造,共同生长!这是我参加「日新计划 8 月更文应战」的第9天,点击查看活动概况

请点赞重视,你的支撑对我含义严重。

Hi,我是小彭。本文已收录到 GitHub AndroidFamily 中。这里有 Android 进阶生长常识体系,有志同道合的朋友,重视大众号 [彭旭锐] 带你树立中心竞争力。

前语

LeakCanary 是咱们十分了解内存走漏检测东西,它能够协助开发者十分高效方便地检测 Android 中常见的内存走漏。在各大厂自研的内存走漏检测结构(如腾讯 Matrix 和快手 Koom)的协助文档中,也会引述 LeakCanary 原理剖析。

不吹不黑,LeakCanary 源码中除了完结内存走漏的监控计划外,还有十分多值得学习的编程技巧,只有沉下心去阅览的人才能够真正体会到。在这篇文章里,我将带你从入门开端掌握 LeakCanary 的运用场景以及运用办法,再介绍 LeakCanary 的作业流程和高档用法,终究经过源码解析深化了解原理。本文示例程序已上传到 Github: DemoHall HelloLeakCanary ,有用请给 Star 支撑,谢谢。

提示: 本文源码剖析依据 2022 年 4 月发布的 LeakCanary 2.9.1。


本文原理剖析触及的 Java 虚拟机内存管理根底:

  • 1、废物收回机制
  • 2、引证机制:说一下 Java 的四种引证类型
  • 3、Finalizer 机制:为什么 finalize() 办法只会履行一次

本文源码剖析触及的 Android 原理根底:

  • 1、Jetpack App Startup:轻量级初始化结构
  • 2、Jetpack Fragment:模块化的微型 Activity
  • 3、Jetpack ViewModet:数据驱动型界面控制器
  • 4、Framework ContentProvider 发动进程剖析
  • 5、Framework Activity 发动进程剖析
  • 6、Framework Service 发动进程剖析

学习路线图:

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!


1. 知道 LeakCanary

1.1 什么是内存走漏?

内存走漏(Memory Leaks)指不再运用的目标或数据没有被收回,跟着内存走漏的堆积,运用功能会逐渐变差,甚至产生 OOM 奔溃。在 Android 运用中的内存走漏能够分为 2 类:

  • Java 内存走漏: 不再运用的目标被生命周期更长的 GC Root 引证,无法被断定为废物目标而导致内存走漏(LeakCanary 只能监控 Java 内存走漏);
  • Native 内存走漏: Native 内存没有废物收回机制,未手动收回导致内存走漏。

1.2 为什么要运用 LeakCanary?

LeakCanray 是 Square 开源的 Java 内存走漏剖析东西,用于在实验室阶段检测 Android 运用中常见中的内存走漏。

LeakCanary 的特色或优势在于提前预判出 Android 运用中最常见且影响较大的内存走漏场景,并对此做针对性的监测手法。 这使得 LeakCanary 比较于其他排查内存走漏的计划(如剖析 OOM 异常时的库房日志、MAT 剖析东西)更加高效。因为当内存走漏堆积而内存不足时,运用或许从任何一次无关紧要的内存分配中抛出 OOM,库房日志只能表现终究一次内存分配的库房信息,而无法表现出导致产生 OOM 的主要原因。

目前,LeakCanary 支撑以下五种 Android 场景中的内存走漏监测:

  • 1、已毁掉的 Activity 目标(进入 DESTROYED 状况);
  • 2、已毁掉的 Fragment 目标和 Fragment View 目标(进入 DESTROYED 状况);
  • 3、已铲除的的 ViewModel 目标(进入 CLEARED 状况);
  • 4、已毁掉的的 Service 目标(进入 DESTROYED 状况);
  • 5、已从 WindowManager 中移除的 RootView 目标;

1.3 LeakCanary 怎么完结内存走漏监控?

LeakCanary 经过以下 2 点完结内存走漏监控:

  • 1、在 Android Framework 中注册无用目标监听: 经过大局监听器或许 Hook 的办法,在 Android Framework 上监听 Activity 和 Service 等目标进入无用状况的机遇(例如在 Activity#onDestroy() 后,产生一个无用 Activity 目标);
  • 2、运用引证目标可感知目标废物收回的机制断定内存走漏: 为无用目标包装弱引证,并在一段时刻后(默以为五秒)调查弱引证是否如期进入相关的引证行列,是则阐明未产生走漏,不然阐明产生走漏(无用目标被强引证持有,导致无法收回,即走漏)。

详细的源码剖析下文内容。


2. 了解 LeakCanary 的作业流程

尽管 LeakCanary 的运用办法十分简略,可是并不意味着 LeakCanary 的作业流程也十分简略。在了解 LeakCanary 的运用办法和深化 LeakCanary 的源码之前,咱们先了解 LeakCanary 的中心作业流程,我将其归纳为以下 5 个阶段:

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

  • 1、注册无用目标监听: 在 Android Framework 中注册监听器,感知五种 Android 内存走漏场景中产生无用目标的机遇(例如在 Activity#onDestroy() 后,产生一个无用 Activity 目标);
  • 2、监控内存走漏: 为无用目标相关弱引证目标,假如一段时刻后引证目标没有按预期进入引证行列,则以为目标产生内存走漏。因为剖析堆快照是耗时作业,所以 LeakCanary 不会每次发现内存走漏目标都进行剖析作业,而是内存走漏目标计数到达阈值才会触发剖析作业。在计数未到达阈值的进程中,LeakCanary 会发送一条体系告诉,你也能够点击该告诉提前触发剖析作业;

收集进程中的体系告诉音讯

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

提示: LeakCanary 为不同的 App 状况设置了不同默许阈值:App 可见时阈值为 5 个走漏目标,App 不可见时阈值为 1 个走漏目标。举个比如,假如 App 在前台可见而且现已收集了 4 个走漏的目标,此时 App 退到后台,LeakCanary 会在五秒后触发剖析作业。

  • 3、Java Heap Dump: 当走漏目标计数到达阈值时,会触发 Java Heap Dump 并生成 .hprof 文件存储到文件体系中。Heap Dump 的进程中会锁堆,会使运用冻住一段时刻;

Heap Dump 进程中的大局对话框

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

  • 4、剖析堆快照: LeakCanary 会依据运用的依靠项,挑选 WorkManager 多进程、WorkManager 异步使命或 Thread 异步使命其间一种战略来履行剖析(例如,LeakCanary 会查看运用有 leakcanary-android-process 依靠项,才会运用 WorkManager 多进程战略)。剖析进程 LeakCanary 运用 Shark 剖析 .hprof 文件,替换了 LeakCanary 1.0 运用的 haha
  • 5、输出剖析陈述: 当剖析作业完结后,LeakCanary 会在 Logcat 打印剖析成果,也会发送一条体系告诉音讯。点击告诉音讯能够跳转到可视化剖析陈述页面,也能够点击 LeakCanary 生成的桌面方便办法进入。

剖析结束后的体系告诉音讯

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

新增的发动图标

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

可视化剖析陈述

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

至此,LeakCanary 一次内存走漏剖析作业流程履行结束。


3. LeakCanary 的根本用法

这一节,咱们来介绍 LeakCanary 的根底用法。

3.1 将 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'
}

3.2 手动初始化 LeakCanary

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>

3.3 自界说 LeakCanary 装备

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 事情监听器 多个内部监听器

4. 解读 LeakCanary 剖析陈述

内存走漏剖析陈述是 LeakCanary 一切监控和剖析作业后输出的目标产品,要依据修复内存走漏,首要就要求开发者能够读懂 LeakCanary 的剖析陈述。我将 LeakCanary 的剖析陈述总结为以下 4 个关键:

4.1 走漏目标的引证链

走漏目标的引证链是剖析陈述的中心信息,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() 直接监控的目标。

4.2 按引证链签名分组

用减少重复的排查作业,LeakCanary 会将相同问题重复触发的内存走漏进行分组,分组办法是按引证链的签名。引证链签名是对引证链上经过的每个目标的类型拼接后取哈希值,已然运用链完全相同,就没必要重复排查了。

为什么各大厂自研的内存泄漏检测框架都要参考 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

4.3 运用 ~~~ 符号置疑目标

为了进步排查内存走漏的功率,LeakCanary 会主动协助咱们依据目标的生命周期信息或状况信息缩小排查范围,扫除原本就具有大局生命周期的目标,剩下的用 ~~~ 下划线符号为置疑目标。

例如,在以下内存走漏陈述中,ExampleApplication 目标被 FontsContract.sContext 静态变量持有,外表看起来是 sContext 静态变量导致内存走漏。其实不是,因为 ExampleApplication 的生命周期是大局的且永远不会被废物收回的,所以内存走漏的根本原因必定不是因为 sContext 持有 ExampleApplication 引起的,sContext 这条引证能够扫除,所以它不会用 ~~~ 下划线符号。

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

4.4 按 Application Leaks 和 Library Leaks 分类

为了进步排查内存走漏的功率,LeakCanary 会主动将走漏陈述划分为 2 类:

  • Application Leaks: 运用层代码产生的内存走漏,包含项目代码和第三方库代码;
  • Library Leaks: Android Framework 产生的内存走漏,开发者简直无法做什么,能够忽略。

其实,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?因为它是真强啊!


5. LeakCanary 的进阶用法

5.1 运用 App Startup 初始化 LeakCanary

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<*>>>()
}

5.2 在子进程履行 LeakCanary 剖析作业

因为 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 进程选项

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

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

5.3 运用快手 Koom 加快 Dump 速度

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();

5.4 自界说符号引证信息

LeakCanary 装备项能够自界说 ObjectInspector 目标检索器,在引证链上的节点中符号必要的信息和状况。符号信息会显现在剖析陈述中,而且会影响陈述中的提示。

  • notLeakingReasons 符号: 符号非走漏原因后,节点为 NOT_LEAKING 状况,并在剖析陈述中会显现 Leaking: NO (notLeakingReasons)
  • leakingReasons 符号: 符号走漏原因后,节点为 LEAKING 状况,在剖析陈述中会显现 Leaking: YES (leakingReasons)
  • 缺省: 节点为 UNKNOWN 状况,在剖析陈述中会显现 Leaking: UNKNOWN

示例程序如下:

示例程序

// 自界说 LeakCanary 装备
LeakCanary.config = LeakCanary.config.copy(
    // 自界说目标检索器
    objectInspectors = LeakCanary.config.objectInspectors + ObjectInspector { reporter ->
        // reporter.notLeakingReasons += "非走漏原因"
        // reporter.leakingReasons += "走漏原因"
    } + AppSingletonInspector(
        // 符号大局类的类名即可
    )
)

别的,引证链 LEAKING 节点以后到第一个 NOT_LEAKING 节点中间的节点,才会用 ~~~ 下划线符号为置疑目标。例如:

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!


6. LeakCanary 完结原理剖析

运用一张示意图表明 LeakCanary 的根本架构:

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

6.1 LeakCanary 怎么完结主动初始化?

旧版别的 LeakCanary 需求在 Application 中调用相关初始化 API,而在 LeakCanary v2 版别中却不再需求手动初始化,为什么呢?—— 这是因为 LeakCanary 运用了 ContentProvider 的初始化机制来直接调用初始化 API。

ContentProvider 的常规用法是供给内容服务,而另一个特殊的用法是供给无侵入的初始化机制,这在第三方库中很常见,Jetpack 中供给的轻量级初始化结构 App Startup 也是依据 ContentProvider 的计划。

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

MainProcessAppWatcherInstaller.kt

internal class MainProcessAppWatcherInstaller : ContentProvider() {
    override fun onCreate(): Boolean {
        // 初始化 LeakCanary
        val application = context!!.applicationContext as Application
        AppWatcher.manualInstall(application)
        return true
    }
    ...
}

6.2 LeakCanary 初始化进程剖析

LeakCanary 的初始化工程能够归纳为 2 项内容:

  • 1、初始化 LeakCanary 内部剖析引擎;
  • 2、在 Android Framework 上注册五种 Android 走漏场景的监控。

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 走漏场景的监控原理剖析:

  • 1、Activity 监控: 经过 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")
    }
}
  • 2、Fragment 与 Fragment View 监控: 经过 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")
    }
}
  • 3、ViewModel 监控: 因为 Android Framework 未供给设置 ViewModel#onClear() 大局监听的办法,所以 LeakCanary 是经过 Hook 的办法完结。即:在 Activity#onCreate 和 Fragment#onCreate 事情中实例化一个自界说ViewModel,在进入 ViewModel#onClear() 办法时,经过反射获取当时效果域中一切的 ViewModel 目标交给 ObjectWatcher 监控。

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)
        }
    }
}
  • 4、Service 监控: 因为 Android Framework 未供给设置 Service#onDestroy() 大局监听的办法,所以 LeakCanary 是经过 Hook 的办法完结的。

Service 监控这部分源码比较复杂了,需求经过 2 步 Hook 来完结:

  • 1、Hook 主线程音讯循环的 mH.mCallback 回调,监听其间的 STOP_SERVICE 音讯,将行将 Destroy 的 Service 目标暂存起来(因为 ActivityThread.H 中没有 DESTROY_SERVICE 音讯,所以不能直接监听到 onDestroy() 事情,需求第 2 步);
  • 2、运用动态署理 Hook AMS 与 App 通讯的的 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!!)
}
  • 5、RootView 监控: 因为 Android Framework 未供给设置大局监听 RootView 从 WindowManager 中移除的办法,所以 LeakCanary 是经过 Hook 的办法完结的,这一块是经过 squareup 另一个开源库 curtains 完结的。

RootView 监控这部分源码也比较复杂了,需求经过 2 步 Hook 来完结:

  • 1、Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表,获取 RootView 新增和移除的机遇;
  • 2、查看 View 对应的 Window 类型,假如是 Dialog 或 DreamService 等类型,则在注册 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 的部分结构:

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

6.3 LeakCanary 怎么断定目标走漏?

在以上步骤中,当目标的运用生命周期结束后,会交给 ObjectWatcher 监控,现在咱们来具体看下它是怎么判别目标产生走漏的。主要逻辑归纳为 3 步:

  • 第 1 步: 为被监控目标 watchedObject 创立一个 KeyedWeakReference 弱引证,并存储到 <UUID, KeyedWeakReference> 的映射表中;
  • 第 2 步: postDelay 五秒后查看引证目标是否出现在引证行列中,出现在行列则阐明被监控目标未产生走漏。随后,移除映射表中未走漏的记载,更新走漏的引证目标的 retainedUptimeMillis 字段以符号为走漏;
  • 第 3 步: 经过回调 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
    }
}

6.4 LeakCanary 发现走漏目标后就会触发剖析吗?

ObjectWatcher 断定被监控目标产生走漏后,会经过接口办法 OnObjectRetainedListener#onObjectRetained() 回调到 LeakCanary 内部的管理器 InternalLeakCanary 处理(在前文 AppWatcher 初始化中说到过)。LeakCanary 不会每次发现内存走漏目标都进行剖析作业,而会进行两个阻拦:

  • 阻拦 1:走漏目标计数未到达阈值,或许进入后台时刻未到达阈值;
  • 阻拦 2:核算间隔上一次 HeapDump 未超越 60s。

源码摘要如下:

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()
        }
    }
}

6.5 LeakCanary 在哪个线程剖析堆快照?

在前面的作业中,LeakCanary 现已成功生成 .hprof 堆快照文件,而且发送了一个 LeakCanary 内部事情 HeapDump。那么这个事情在哪里被消费的呢?

一步步跟踪代码能够看到 LeakCanary 的装备项中设置了多个事情顾客 EventListener,其间与 HeapDump 事情有关的是 when{} 代码块中三个顾客。不过,这三个顾客并不是并存的,而是会依据 App 当时的依靠项而挑选最优的履行战略:

  • 战略 1 – WorkerManager 多进程剖析
  • 战略 2 – WorkManager 异步剖析
  • 战略 3 – 异步线程剖析(兜底战略)

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
        }
    ),
    ...
)
  • 战略 1 – WorkerManager 多进程剖析: 判别是否能够类加载 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
    }
}
  • 战略 2 – WorkManager 异步剖析: 判别是否能够类加载 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()
    }
}
  • 战略 3 – 异步线程剖析(兜底战略): 假如以上战略未射中,则直接运用子线程兜底履行。

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)
            }
        }
    }
}

能够看到,不论选用那种履行战略,终究履行的逻辑都是相同的:

  • 1、剖析堆快照;
  • 2、发送剖析进展事情;
  • 3、发送剖析完结事情。

6.5 LeakCanary 怎么剖析堆快照?

在前面的剖析中,咱们现已知道 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、在堆快照中寻觅走漏目标,默许是寻觅 KeyedWeakReference 类型目标;
  • 2、剖析 KeyedWeakReference 目标的最短引证链,并依照引证链签名分组,依照 Application Leaks 和 Library Leaks 分类;
  • 3、回来剖析完结事情。

第 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
}

6.6 LeakCanary 怎么挑选 ~~~ 置疑目标?

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
    }
}

6.7 LeakCanary 剖析完结后的处理

有两个方位处理了 HeapAnalysisSucceeded 事情:

  • Logcat:打印剖析陈述日志;
  • Notification: 发送剖析成功体系告诉音讯。

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 原理剖析结束。


7. 总结

到这里,LeakCanary 的运用和原理剖析就讲完了。不过,LeakCanary 究竟是实验室运用的东西,假如要完结线上内存走漏监控,你知道怎么做吗?要完结 Native 内存走漏监控又要怎么做?重视我,带你了解更多。


参考资料

  • LeakCanary 官网
  • LeakCanary Github 库房
  • How Leakcanary leverages WorkManager multi-process —— Pierre-Yves Ricau 著
  • Matrix Android ResourceCanary —— 腾讯 Matrix 阐明文档
  • KOOM —— 高功能线上内存监控计划 —— 快手 Koom 阐明文档
  • 内存优化(下):内存优化这件事,应该从哪里着手? —— 张绍文 著
  • Android内存走漏检测 LeakCanary 2.0 (Kotlin版) 的完结原理 —— vivo 技能团队 著

你的点赞对我含义严重!微信搜索大众号 [彭旭锐],期望大家能够一起评论技能,找到志同道合的朋友,咱们下次见!

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!