為什么各大廠(chǎng)自研的內(nèi)存泄漏檢測(cè)框架都要參考 LeakCanary?因?yàn)樗钦鎻?qiáng)??!

前言

LeakCanary 是我們非常熟悉內(nèi)存泄漏檢測(cè)工具,它能夠幫助開(kāi)發(fā)者非常高效便捷地檢測(cè) Android 中常見(jiàn)的內(nèi)存泄漏。在各大廠(chǎng)自研的內(nèi)存泄漏檢測(cè)框架(如騰訊 Matrix 和快手 Koom)的幫助文檔中,也會(huì)引述 LeakCanary 原理分析。

不吹不黑,LeakCanary 源碼中除了實(shí)現(xiàn)內(nèi)存泄漏的監(jiān)控方案外,還有非常多值得學(xué)習(xí)的編程技巧,只有沉下心去閱讀的人才能夠真正體會(huì)到。在這篇文章里,我將帶你從入門(mén)開(kāi)始掌握 LeakCanary 的使用場(chǎng)景以及使用方法,再介紹 LeakCanary 的工作流程和高級(jí)用法,最后通過(guò)源碼解析深入理解原理。本文示例程序已上傳到 Github: DemoHall · HelloLeakCanary ,有用請(qǐng)給 Star 支持,謝謝。

提示: 本文源碼分析基于 2022 年 4 月發(fā)布的 LeakCanary 2.9.1。


本文原理分析涉及的 Java 虛擬機(jī)內(nèi)存管理基礎(chǔ):

本文源碼分析涉及的 Android 原理基礎(chǔ):


學(xué)習(xí)路線(xiàn)圖:


1. 認(rèn)識(shí) LeakCanary

1.1 什么是內(nèi)存泄漏?

內(nèi)存泄露(Memory Leaks)指不再使用的對(duì)象或數(shù)據(jù)沒(méi)有被回收,隨著內(nèi)存泄漏的堆積,應(yīng)用性能會(huì)逐漸變差,甚至發(fā)生 OOM 奔潰。在 Android 應(yīng)用中的內(nèi)存泄漏可以分為 2 類(lèi):

  • Java 內(nèi)存泄露: 不再使用的對(duì)象被生命周期更長(zhǎng)的 GC Root 引用,無(wú)法被判定為垃圾對(duì)象而導(dǎo)致內(nèi)存泄漏(LeakCanary 只能監(jiān)控 Java 內(nèi)存泄漏);
  • Native 內(nèi)存泄露: Native 內(nèi)存沒(méi)有垃圾回收機(jī)制,未手動(dòng)回收導(dǎo)致內(nèi)存泄漏。

1.2 為什么要使用 LeakCanary?

LeakCanray 是 Square 開(kāi)源的 Java 內(nèi)存泄漏分析工具,用于在實(shí)驗(yàn)室階段檢測(cè) Android 應(yīng)用中常見(jiàn)中的內(nèi)存泄漏。

LeakCanary 的特點(diǎn)或優(yōu)勢(shì)在于提前預(yù)判出 Android 應(yīng)用中最常見(jiàn)且影響較大的內(nèi)存泄漏場(chǎng)景,并對(duì)此做針對(duì)性的監(jiān)測(cè)手段。 這使得 LeakCanary 相比于其他排查內(nèi)存泄漏的方案(如分析 OOM 異常時(shí)的堆棧日志、MAT 分析工具)更加高效。因?yàn)楫?dāng)內(nèi)存泄漏堆積而內(nèi)存不足時(shí),應(yīng)用可能從任何一次無(wú)關(guān)緊要的內(nèi)存分配中拋出 OOM,堆棧日志只能體現(xiàn)最后一次內(nèi)存分配的堆棧信息,而無(wú)法體現(xiàn)出導(dǎo)致發(fā)生 OOM 的主要原因。

目前,LeakCanary 支持以下五種 Android 場(chǎng)景中的內(nèi)存泄漏監(jiān)測(cè):

  • 1、已銷(xiāo)毀的 Activity 對(duì)象(進(jìn)入 DESTROYED 狀態(tài));
  • 2、已銷(xiāo)毀的 Fragment 對(duì)象和 Fragment View 對(duì)象(進(jìn)入 DESTROYED 狀態(tài));
  • 3、已清除的的 ViewModel 對(duì)象(進(jìn)入 CLEARED 狀態(tài));
  • 4、已銷(xiāo)毀的的 Service 對(duì)象(進(jìn)入 DESTROYED 狀態(tài));
  • 5、已從 WindowManager 中移除的 RootView 對(duì)象;

1.3 LeakCanary 怎么實(shí)現(xiàn)內(nèi)存泄漏監(jiān)控?

LeakCanary 通過(guò)以下 2 點(diǎn)實(shí)現(xiàn)內(nèi)存泄漏監(jiān)控:

  • 1、在 Android Framework 中注冊(cè)無(wú)用對(duì)象監(jiān)聽(tīng): 通過(guò)全局監(jiān)聽(tīng)器或者 Hook 的方式,在 Android Framework 上監(jiān)聽(tīng) Activity 和 Service 等對(duì)象進(jìn)入無(wú)用狀態(tài)的時(shí)機(jī)(例如在 Activity#onDestroy() 后,產(chǎn)生一個(gè)無(wú)用 Activity 對(duì)象);
  • 2、利用引用對(duì)象可感知對(duì)象垃圾回收的機(jī)制判定內(nèi)存泄漏: 為無(wú)用對(duì)象包裝弱引用,并在一段時(shí)間后(默認(rèn)為五秒)觀察弱引用是否如期進(jìn)入關(guān)聯(lián)的引用隊(duì)列,是則說(shuō)明未發(fā)生泄漏,否則說(shuō)明發(fā)生泄漏(無(wú)用對(duì)象被強(qiáng)引用持有,導(dǎo)致無(wú)法回收,即泄漏)。

詳細(xì)的源碼分析下文內(nèi)容。


2. 理解 LeakCanary 的工作流程

雖然 LeakCanary 的使用方法非常簡(jiǎn)單,但是并不意味著 LeakCanary 的工作流程也非常簡(jiǎn)單。在了解 LeakCanary 的使用方法和深入 LeakCanary 的源碼之前,我們先理解 LeakCanary 的核心工作流程,我將其概括為以下 5 個(gè)階段:

  • 1、注冊(cè)無(wú)用對(duì)象監(jiān)聽(tīng): 在 Android Framework 中注冊(cè)監(jiān)聽(tīng)器,感知五種 Android 內(nèi)存泄漏場(chǎng)景中產(chǎn)生無(wú)用對(duì)象的時(shí)機(jī)(例如在 Activity#onDestroy() 后,產(chǎn)生一個(gè)無(wú)用 Activity 對(duì)象);
  • 2、監(jiān)控內(nèi)存泄漏: 為無(wú)用對(duì)象關(guān)聯(lián)弱引用對(duì)象,如果一段時(shí)間后引用對(duì)象沒(méi)有按預(yù)期進(jìn)入引用隊(duì)列,則認(rèn)為對(duì)象發(fā)生內(nèi)存泄漏。由于分析堆快照是耗時(shí)工作,所以 LeakCanary 不會(huì)每次發(fā)現(xiàn)內(nèi)存泄漏對(duì)象都進(jìn)行分析工作,而是內(nèi)存泄漏對(duì)象計(jì)數(shù)到達(dá)閾值才會(huì)觸發(fā)分析工作。在計(jì)數(shù)未到達(dá)閾值的過(guò)程中,LeakCanary 會(huì)發(fā)送一條系統(tǒng)通知,你也可以點(diǎn)擊該通知提前觸發(fā)分析工作;

收集過(guò)程中的系統(tǒng)通知消息

!](https://upload-images.jianshu.io/upload_images/10107787-ce29f36a9f3fbe92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

提示: LeakCanary 為不同的 App 狀態(tài)設(shè)置了不同默認(rèn)閾值:App 可見(jiàn)時(shí)閾值為 5 個(gè)泄漏對(duì)象,App 不可見(jiàn)時(shí)閾值為 1 個(gè)泄漏對(duì)象。舉個(gè)例子,如果 App 在前臺(tái)可見(jiàn)并且已經(jīng)收集了 4 個(gè)泄漏的對(duì)象,此時(shí) App 退到后臺(tái),LeakCanary 會(huì)在五秒后觸發(fā)分析工作。

  • 3、Java Heap Dump: 當(dāng)泄漏對(duì)象計(jì)數(shù)達(dá)到閾值時(shí),會(huì)觸發(fā) Java Heap Dump 并生成 .hprof 文件存儲(chǔ)到文件系統(tǒng)中。Heap Dump 的過(guò)程中會(huì)鎖堆,會(huì)使應(yīng)用凍結(jié)一段時(shí)間;

Heap Dump 過(guò)程中的全局對(duì)話(huà)框

  • 4、分析堆快照: LeakCanary 會(huì)根據(jù)應(yīng)用的依賴(lài)項(xiàng),選擇 WorkManager 多進(jìn)程、WorkManager 異步任務(wù)或 Thread 異步任務(wù)其中一種策略來(lái)執(zhí)行分析(例如,LeakCanary 會(huì)檢查應(yīng)用有 leakcanary-android-process 依賴(lài)項(xiàng),才會(huì)使用 WorkManager 多進(jìn)程策略)。分析過(guò)程 LeakCanary 使用 Shark 分析 .hprof 文件,替換了 LeakCanary 1.0 使用的 haha ;
  • 5、輸出分析報(bào)告: 當(dāng)分析工作完成后,LeakCanary 會(huì)在 Logcat 打印分析結(jié)果,也會(huì)發(fā)送一條系統(tǒng)通知消息。點(diǎn)擊通知消息可以跳轉(zhuǎn)到可視化分析報(bào)告頁(yè)面,也可以點(diǎn)擊 LeakCanary 生成的桌面快捷方式進(jìn)入。

分析結(jié)束后的系統(tǒng)通知消息

新增的啟動(dòng)圖標(biāo)

可視化分析報(bào)告

至此,LeakCanary 一次內(nèi)存泄漏分析工作流程執(zhí)行完畢。


3. LeakCanary 的基本用法

這一節(jié),我們來(lái)介紹 LeakCanary 的基礎(chǔ)用法。

3.1 將 LeakCanary 添加到項(xiàng)目中

在 build.gradle 中添加 LeakCanary 依賴(lài),此外不需要調(diào)用任何初始化 API(LeakCanary 內(nèi)部默認(rèn)使用了 ContentProvider 實(shí)現(xiàn)無(wú)侵入初始化)。另外,因?yàn)?LeakCanary 是只在實(shí)驗(yàn)室環(huán)境使用的工具,所以這里要記得使用 debugImplementation 依賴(lài)配置。

build.gradle

dependencies {
    // debugImplementation because LeakCanary should only run in debug builds.
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

3.2 手動(dòng)初始化 LeakCanary

LeakCanary 2.0 默認(rèn)采用了 ContentProvider 機(jī)制實(shí)現(xiàn)了無(wú)侵入初始化,為了給予開(kāi)發(fā)者手動(dòng)初始化 LeakCanary 的可能性,LeakCanary 在 ContentProvider 中設(shè)置了布爾值開(kāi)關(guān):

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>

開(kāi)發(fā)者只需要在資源文件里覆寫(xiě) @bool/eak_canary_watcher_auto_install 布爾值來(lái)關(guān)閉自動(dòng)初始化,并在合適的時(shí)機(jī)手動(dòng)調(diào)用 AppWatcher#manualInstall 。

values.xml

<resources>
    <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>

3.3 自定義 LeakCanary 配置

LeakCanary 為開(kāi)發(fā)者提供了便捷的配置 API,并且這個(gè)配置 API 在初始化前后都允許調(diào)用。

示例程序

// Java 語(yǔ)法
LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
    .retainedVisibleThreshold(3)
    .build();
LeakCanary.setConfig(config);
// Kotlin 語(yǔ)法
LeakCanary.config = LeakCanary.config.copy(
    retainedVisibleThreshold = 3
)

以下用一個(gè)表格總結(jié) LeakCanary 主要的配置項(xiàng):

配置項(xiàng) 描述 默認(rèn)值
dumpHeap: Boolean Heap Dump 分析開(kāi)關(guān) true
dumpHeapWhenDebugging: Boolean 調(diào)試時(shí) Heap Dump 分析開(kāi)關(guān) false
retainedVisibleThreshold: Int App 可見(jiàn)時(shí)泄漏計(jì)數(shù)閾值 5
objectInspectors: List<ObjectInspector> 對(duì)象檢索器 AndroidObjectInspectors.appDefaults
computeRetainedHeapSize: Boolean 是否計(jì)算泄漏內(nèi)存空間 true
maxStoredHeapDumps: Int 最大堆快照存儲(chǔ)數(shù)量 7
requestWriteExternalStoragePermission: Boolean 是否請(qǐng)求文件存儲(chǔ)權(quán)限 true
leakingObjectFinder: LeakingObjectFinder 引用鏈分析器 KeyedWeakReferenceFinder
heapDumper: HeapDumper Heap Dump 執(zhí)行器 Debug.dumpHprofData
eventListeners: List<EventListener> 事件監(jiān)聽(tīng)器 多個(gè)內(nèi)部監(jiān)聽(tīng)器

4. 解讀 LeakCanary 分析報(bào)告

內(nèi)存泄漏分析報(bào)告是 LeakCanary 所有監(jiān)控和分析工作后輸出的目標(biāo)產(chǎn)物,要根據(jù)修復(fù)內(nèi)存泄漏,首先就要求開(kāi)發(fā)者能夠讀懂 LeakCanary 的分析報(bào)告。我將 LeakCanary 的分析報(bào)告總結(jié)為以下 4 個(gè)要點(diǎn):

4.1 泄漏對(duì)象的引用鏈

泄漏對(duì)象的引用鏈?zhǔn)欠治鰣?bào)告的核心信息,LeakCanary 會(huì)收集泄漏對(duì)象到 GC Root 的完整引用鏈信息。例如,以下示例程序在 static 變量中持有一個(gè) Helper 對(duì)象,當(dāng) Helper 被期望被垃圾回收時(shí)用 AppWatcher 監(jiān)測(cè)該對(duì)象,如果未按預(yù)期被回收,則會(huì)輸出以下分析報(bào)告:

示例程序

class Helper {
}

class Utils {
    public static Helper helper = new Helper();
}

// Helper 無(wú)用后監(jiān)測(cè)
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 字段,它是一個(gè) Object 數(shù)組
├─ java.lang.Object[] array
│    ↓ Object[].[43] // 表示 Object 數(shù)組的第 43 位,它是一個(gè) Utils 類(lèi)型引用
├─ com.example.Utils class
│    ↓ static Utils.helper // 表示 Utils 的 static 字段,它是一個(gè) Helper 類(lèi)型引用
╰→ java.example.Helper

解釋一下其中的符號(hào):

  • 代表一個(gè) Java 對(duì)象;
  • │ ↓ 代表一個(gè) Java 引用,關(guān)聯(lián)的實(shí)際對(duì)象在下一行;
  • ╰→ 代表泄漏的對(duì)象,即 AppWatcher.objectWatcher.watch() 直接監(jiān)控的對(duì)象。

4.2 按引用鏈簽名分組

用減少重復(fù)的排查工作,LeakCanary 會(huì)將相同問(wèn)題重復(fù)觸發(fā)的內(nèi)存泄漏進(jìn)行分組,分組方法是按引用鏈的簽名。引用鏈簽名是對(duì)引用鏈上經(jīng)過(guò)的每個(gè)對(duì)象的類(lèi)型拼接后取哈希值,既然應(yīng)用鏈完全相同,就沒(méi)必要重復(fù)排查了。

例如,對(duì)于泄漏對(duì)象 instance,對(duì)應(yīng)的泄漏簽名計(jì)算公式如下:

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)

對(duì)應(yīng)的簽名計(jì)算公式

val leakSignature = sha1Hash(
    "com.example.leakcanary.LeakingSingleton.leakedView" +
    "java.util.ArrayList.elementData" +
    "java.lang.Object[].[x]"
)
println(leakSignature)
// dbfa277d7e5624792e8b60bc950cd164190a11aa

4.3 使用 ~~~ 標(biāo)記懷疑對(duì)象

為了提高排查內(nèi)存泄漏的效率,LeakCanary 會(huì)自動(dòng)幫助我們根據(jù)對(duì)象的生命周期信息或狀態(tài)信息縮小排查范圍,排除原本就具有全局生命周期的對(duì)象,剩下的用 ~~~ 下劃線(xiàn)標(biāo)記為懷疑對(duì)象。

例如,在以下內(nèi)存泄漏報(bào)告中,ExampleApplication 對(duì)象被 FontsContract.sContext 靜態(tài)變量持有,表面看起來(lái)是 sContext 靜態(tài)變量導(dǎo)致內(nèi)存泄漏。其實(shí)不是,因?yàn)?ExampleApplication 的生命周期是全局的且永遠(yuǎn)不會(huì)被垃圾回收的,所以?xún)?nèi)存泄漏的根本原因一定不是因?yàn)?sContext 持有 ExampleApplication 引起的,sContext 這條引用可以排除,所以它不會(huì)用 ~~~ 下劃線(xiàn)標(biāo)記。

4.4 按 Application Leaks 和 Library Leaks 分類(lèi)

為了提高排查內(nèi)存泄漏的效率,LeakCanary 會(huì)自動(dòng)將泄漏報(bào)告劃分為 2 類(lèi):

  • Application Leaks: 應(yīng)用層代碼產(chǎn)生的內(nèi)存泄漏,包括項(xiàng)目代碼和第三方庫(kù)代碼;
  • Library Leaks: Android Framework 產(chǎn)生的內(nèi)存泄漏,開(kāi)發(fā)者幾乎無(wú)法做什么,可以忽略。

其實(shí),Library Leaks 這個(gè)名詞起得并不好,應(yīng)該叫作 Framework Leaks。 小彭最初在閱讀官方文檔后,以為 Library Leaks 是只第三方庫(kù)代碼產(chǎn)生的內(nèi)存泄漏,LeakCanary 還提到開(kāi)發(fā)者對(duì)于 Library Leaks 幾乎無(wú)法做什么,讓我一度很好奇 LeakCanary 是如何定義二方庫(kù)和三方庫(kù)。最后還是通過(guò)源碼才得知,Library Leaks 原來(lái)是指 Android Framework 中產(chǎn)生的內(nèi)存泄漏,例如什么 TextView、InputMethodManager 之類(lèi)的。

Logcat 中的 Library Leak 標(biāo)記

====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS

====================================
1 LIBRARY LEAK

...
┬───
│ GC Root: Local variable in native code
│
...

5. LeakCanary 的進(jìn)階用法

5.1 使用 App Startup 初始化 LeakCanary

LeakCanary 2.8 提供了對(duì) Jetpack · App Startup 的支持。如果想使用 App Startup 初始化 LeakCanary,只需要替換為另一個(gè)依賴(lài)。不過(guò),畢竟 LeakCanary 是主要在實(shí)驗(yàn)室環(huán)境使用的工具,這個(gè)優(yōu)化的意義并不大。

build.gradle

dependencies {
    // 替換為另一個(gè)依賴(lài)
    // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.9.1'
}

對(duì)應(yīng)的 App Startup 啟動(dòng)器源碼:

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 在子進(jìn)程執(zhí)行 LeakCanary 分析工作

由于 LeakCanary 分析堆快照的過(guò)程存在一定的內(nèi)存消耗,整個(gè)分析過(guò)程一般會(huì)持續(xù)幾十秒,對(duì)于一些性能差的機(jī)型會(huì)造成明顯的卡頓甚至 ANR。為了優(yōu)化內(nèi)存占用和卡頓問(wèn)題,LeakCanary 2.8 提供了對(duì)多進(jìn)程的支持。開(kāi)發(fā)者只需要依賴(lài) LeakCanary 的多進(jìn)程依賴(lài)項(xiàng),LeakCanary 會(huì)自動(dòng)將分析工作轉(zhuǎn)移到子進(jìn)程中(基于 androidX.work.multiprocess):

build.gradle

dependencies {
    // 官方文檔對(duì)多進(jìn)程功能的介紹有矛盾,經(jīng)過(guò)測(cè)試,以下兩個(gè)依賴(lài)都需要
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.9.1'
}

同時(shí),開(kāi)發(fā)者需要在自定義 Application 中檢查當(dāng)前進(jìn)程信息,避免在 LeakCanary 的子進(jìn)程中執(zhí)行不必要的初始化操作:

ExampleApplication.kt

class ExampleApplication : Application() {
    override fun onCreate() {
        if (LeakCanaryProcess.isInAnalyzerProcess(this)) {
            return
        }
        super.onCreate()
        // normal init goes here, skipped in :leakcanary process.
    }
}

Logcat 進(jìn)程選項(xiàng)

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 默認(rèn)的 Java Heap Dump 使用的是 Debug.dumpHprofData() ,在 Dump 的過(guò)程中會(huì)有較長(zhǎng)時(shí)間的應(yīng)用凍結(jié)時(shí)間。 快手技術(shù)團(tuán)隊(duì)在開(kāi)源框架 Koom 中提出了優(yōu)化方案:利用 Copy-on-Write 思想,fork 子進(jìn)程再進(jìn)行 Heap Dump 操作。

LeakCanary 配置項(xiàng)可以修改 Heap Dump 執(zhí)行器,示例程序如下:

示例程序

// 依賴(lài): 
debugImplementation "com.kuaishou.koom:koom-java-leak:2.2.0"

// 使用默認(rèn)配置初始化 Koom
DefaultInitTask.init(application)
// 自定義 LeakCanary 配置
LeakCanary.config = LeakCanary.config.copy(
    // 自定義 Heap Dump 執(zhí)行器
    heapDumper = {
        ForkJvmHeapDumper.getInstance().dump(it.absolutePath)
    }
)

Logcat 日志對(duì)比

// 使用默認(rèn)的 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 自定義標(biāo)記引用信息

LeakCanary 配置項(xiàng)可以自定義 ObjectInspector 對(duì)象檢索器,在引用鏈上的節(jié)點(diǎn)中標(biāo)記必要的信息和狀態(tài)。標(biāo)記信息會(huì)顯示在分析報(bào)告中,并且會(huì)影響報(bào)告中的提示。

  • notLeakingReasons 標(biāo)記: 標(biāo)記非泄漏原因后,節(jié)點(diǎn)為 NOT_LEAKING 狀態(tài),并在分析報(bào)告中會(huì)顯示 Leaking: NO (notLeakingReasons) ;
  • leakingReasons 標(biāo)記: 標(biāo)記泄漏原因后,節(jié)點(diǎn)為 LEAKING 狀態(tài),在分析報(bào)告中會(huì)顯示 Leaking: YES (leakingReasons) ;
  • 缺?。?/strong> 節(jié)點(diǎn)為 UNKNOWN 狀態(tài),在分析報(bào)告中會(huì)顯示 Leaking: UNKNOWN 。

示例程序如下:

示例程序

// 自定義 LeakCanary 配置
LeakCanary.config = LeakCanary.config.copy(
    // 自定義對(duì)象檢索器
    objectInspectors = LeakCanary.config.objectInspectors + ObjectInspector { reporter ->
        // reporter.notLeakingReasons += "非泄漏原因"
        // reporter.leakingReasons += "泄漏原因"
    } + AppSingletonInspector(
        // 標(biāo)記全局類(lèi)的類(lèi)名即可
    )
)

另外,引用鏈 LEAKING 節(jié)點(diǎn)以后到第一個(gè) NOT_LEAKING 節(jié)點(diǎn)中間的節(jié)點(diǎn),才會(huì)用 ~~~ 下劃線(xiàn)標(biāo)記為懷疑對(duì)象。例如:


6. LeakCanary 實(shí)現(xiàn)原理分析

使用一張示意圖表示 LeakCanary 的基本架構(gòu):

6.1 LeakCanary 如何實(shí)現(xiàn)自動(dòng)初始化?

舊版本的 LeakCanary 需要在 Application 中調(diào)用相關(guān)初始化 API,而在 LeakCanary v2 版本中卻不再需要手動(dòng)初始化,為什么呢?—— 這是因?yàn)?LeakCanary 利用了 ContentProvider 的初始化機(jī)制來(lái)間接調(diào)用初始化 API。

ContentProvider 的常規(guī)用法是提供內(nèi)容服務(wù),而另一個(gè)特殊的用法是提供無(wú)侵入的初始化機(jī)制,這在第三方庫(kù)中很常見(jiàn),Jetpack 中提供的輕量級(jí)初始化框架 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
    }
    ...
}

6.2 LeakCanary 初始化過(guò)程分析

LeakCanary 的初始化工程可以概括為 2 項(xiàng)內(nèi)容:

  • 1、初始化 LeakCanary 內(nèi)部分析引擎;
  • 2、在 Android Framework 上注冊(cè)五種 Android 泄漏場(chǎng)景的監(jiān)控。

AppWathcer.kt

// LeakCanary 初始化 API
@JvmOverloads
fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
    checkMainThread()
    ...
    // 初始化 InternalLeakCanary 內(nèi)部引擎 (已簡(jiǎn)化為等價(jià)代碼,后文會(huì)提到)
    InternalLeakCanary(application)
    // 注冊(cè)五種 Android 泄漏場(chǎng)景的監(jiān)控 Hook 點(diǎn)
    watchersToInstall.forEach {
        it.install()
    }
}

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
    // 對(duì)應(yīng) 5 種 Android 泄漏場(chǎng)景(后文具體分析)
    return listOf(
        ActivityWatcher(application, reachabilityWatcher),
        FragmentAndViewModelWatcher(application, reachabilityWatcher),
        RootViewWatcher(reachabilityWatcher),
        ServiceWatcher(reachabilityWatcher)
    )
}

下面展開(kāi)具體分析:


初始化內(nèi)容 1 - 初始化 LeakCanary 內(nèi)部分析引擎: 創(chuàng)建 HeapDumpTrigger 觸發(fā)器,并在 Android Framework 上注冊(cè)前后臺(tái)切換監(jiān)聽(tīng)、前臺(tái) Activity 監(jiān)聽(tīng)和 ObjectWatcher 的泄漏監(jiān)聽(tīng)。

InternalLeakCanary.kt

override fun invoke(application: Application) {
    _application = application

    // 1. 檢查是否運(yùn)行在 debug 構(gòu)建變體,否則拋出異常
    checkRunningInDebuggableBuild()

    // 2. 注冊(cè)泄漏回調(diào),在 ObjectWathcer 判定對(duì)象發(fā)生泄漏會(huì)后回調(diào) onObjectRetained() 方法
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    // 3. 垃圾回收觸發(fā)器(用于調(diào)用 Runtime.getRuntime().gc())
    val gcTrigger = GcTrigger.Default
    // 4. 配置提供器
    val configProvider = { LeakCanary.config }
    // 5. (主角) 創(chuàng)建 HeapDump 觸發(fā)器
    heapDumpTrigger = HeapDumpTrigger(...)

    // 6. App 前后臺(tái)切換監(jiān)聽(tīng)
    application.registerVisibilityListener { applicationVisible ->
        this.applicationVisible = applicationVisible
        heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    // 7. 前臺(tái) Activity 監(jiān)聽(tīng)(用于發(fā)送 Heap Dump 進(jìn)行中的全局 Toast)
    registerResumedActivityListener(application)

    // 8. 增加可視化分析報(bào)告的桌面快捷入口
    addDynamicShortcut(application)
}

override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
    heapDumpTrigger.scheduleRetainedObjectCheck()
}

HeapDumpTrigger.kt

// App 前后臺(tái)切換狀態(tài)變化回調(diào)
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
        // App 可見(jiàn)
        applicationInvisibleAt = -1L
    } else {
        // App 不可見(jiàn)
        applicationInvisibleAt = SystemClock.uptimeMillis()
        scheduleRetainedObjectCheck(delayMillis = AppWatcher.retainedDelayMillis)
    } 
}

fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
    // 已簡(jiǎn)化:源碼此處使用時(shí)間戳攔截,避免重復(fù) postDelayed
    backgroundHandler.postDelayed({
        checkScheduledAt = 0
        checkRetainedObjects()
    }, delayMillis)
}

初始化內(nèi)容 2 - 在 Android Framework 中注入對(duì)五種 Android 泄漏場(chǎng)景的監(jiān)控: 實(shí)現(xiàn)在對(duì)象的使用生命周期結(jié)束后,自動(dòng)將對(duì)象交給 ObjectWatcher 進(jìn)行監(jiān)控。

以下為 5 種 Android 泄漏場(chǎng)景的監(jiān)控原理分析:

  • 1、Activity 監(jiān)控: 通過(guò) Application#registerActivityLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Activity#onDestroy 事件,將當(dāng)前 Activity 對(duì)象交給 ObjectWatcher 監(jiān)控;

ActivityWatcher.kt

private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
        // reachabilityWatcher 即 ObjectWatcher
        reachabilityWatcher.expectWeaklyReachable(activity /*被監(jiān)控對(duì)象*/, "${activity::class.java.name} received Activity#onDestroy() callback")
    }
}
  • 2、Fragment 與 Fragment View 監(jiān)控: 通過(guò) FragmentAndViewModelWatcher 實(shí)現(xiàn),首先是通過(guò) Application#registerActivityLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Activity#onCreate 事件,再通過(guò) FragmentManager#registerFragmentLifecycleCallbacks(…) 接口監(jiān)聽(tīng) Fragment 的生命周期:

FragmentAndViewModelWatcher.kt

// fragmentDestroyWatchers 是一個(gè) Lambda 表達(dá)式數(shù)組
// 對(duì)應(yīng)原生、AndroidX 和 Support 三個(gè)版本 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) {
            // 最終調(diào)用到下文的 invokde() 方法
            watcher(activity)
        }
    }
}

以 AndroidX Fragment 為例:

AndroidXFragmentDestroyWatcher.kt

override fun invoke(activity: Activity) {
    // 這里在 Activity#onCreate 狀態(tài)執(zhí)行:
    if (activity is FragmentActivity) {
        val supportFragmentManager = activity.supportFragmentManager
        // 注冊(cè) Fragment 生命周期監(jiān)聽(tīng)
        supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
        // 注冊(cè) Activity 級(jí)別 ViewModel Hook
        ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
}

private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) {
        // 注冊(cè) Fragment 級(jí)別 ViewModel Hook
        ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed(fm: FragmentManager, fragment: Fragment) {
        // reachabilityWatcher 即 ObjectWatcher
        reachabilityWatcher.expectWeaklyReachable(fragment.view /*被監(jiān)控對(duì)象*/, "${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 /*被監(jiān)控對(duì)象*/, "${fragment::class.java.name} received Fragment#onDestroy() callback")
    }
}
  • 3、ViewModel 監(jiān)控: 由于 Android Framework 未提供設(shè)置 ViewModel#onClear() 全局監(jiān)聽(tīng)的方法,所以 LeakCanary 是通過(guò) Hook 的方式實(shí)現(xiàn)。即:在 Activity#onCreate 和 Fragment#onCreate 事件中實(shí)例化一個(gè)自定義ViewModel,在進(jìn)入 ViewModel#onClear() 方法時(shí),通過(guò)反射獲取當(dāng)前作用域中所有的 ViewModel 對(duì)象交給 ObjectWatcher 監(jiān)控。

ViewModelClearedWatcher.kt

// ViewModel 的子類(lèi)
internal class ViewModelClearedWatcher(
    storeOwner: ViewModelStoreOwner,
    private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

    // 反射獲取 ViewModelStore 中的 ViewModel 映射表,即可獲取當(dāng)前作用域所有 ViewModel 對(duì)象
    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() {
        // 遍歷當(dāng)前作用域所有 ViewModel 對(duì)象
        viewModelMap?.values?.forEach { viewModel ->
            // reachabilityWatcher 即 ObjectWatcher
            reachabilityWatcher.expectWeaklyReachable(viewModel /*被監(jiān)控對(duì)象*/, "${viewModel::class.java.name} received ViewModel#onCleared() callback")
        }
    }

    companion object {
        // 直接在 storeOwner 作用域?qū)嵗?ViewModelClearedWatcher 對(duì)象
        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 監(jiān)控: 由于 Android Framework 未提供設(shè)置 Service#onDestroy() 全局監(jiān)聽(tīng)的方法,所以 LeakCanary 是通過(guò) Hook 的方式實(shí)現(xiàn)的。

Service 監(jiān)控這部分源碼比較復(fù)雜了,需要通過(guò) 2 步 Hook 來(lái)實(shí)現(xiàn):

  • 1、Hook 主線(xiàn)程消息循環(huán)的 mH.mCallback 回調(diào),監(jiān)聽(tīng)其中的 STOP_SERVICE 消息,將即將 Destroy 的 Service 對(duì)象暫存起來(lái)(由于 ActivityThread.H 中沒(méi)有 DESTROY_SERVICE 消息,所以不能直接監(jiān)聽(tīng)到 onDestroy() 事件,需要第 2 步);
  • 2、使用動(dòng)態(tài)代理 Hook AMS 與 App 通信的的 IActivityManager Binder 對(duì)象,代理其中的 serviceDoneExecuting() 方法,視為 Service#onDestroy() 的執(zhí)行時(shí)機(jī),拿到暫存的 Service 對(duì)象交給 ObjectWatcher 監(jiān)控。

源碼摘要如下:

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 /*原對(duì)象*/ ->
        // uninstallActivityThreadHandlerCallback:用于取消 Hook
        uninstallActivityThreadHandlerCallback = {
            swapActivityThreadHandlerCallback {
                mCallback
            }
        }
        // 新對(duì)象(lambda 表達(dá)式的末行就是返回值)
        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 繼續(xù)執(zhí)行 Framework 原有邏輯
            mCallback?.handleMessage(msg) ?: false
        }
    }
    // 2. Hook AMS IActivityManager
    swapActivityManager { activityManagerInterface, activityManagerInstance /*原對(duì)象*/ ->
        // uninstallActivityManager:用于取消 Hook
        uninstallActivityManager = {
            swapActivityManager { _, _ ->
                activityManagerInstance
            }
        }
        // 新對(duì)象(lambda 表達(dá)式的末行就是返回值)
        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 監(jiān)控
                        serviceWeakReference.get()?.let { service ->
                            reachabilityWatcher.expectWeaklyReachable(service /*被監(jiān)控對(duì)象*/, "${service::class.java.name} received Service#onDestroy() callback")
                        }
                    }
                }
            }
            // 2.4 繼續(xù)執(zhí)行 Framework 原有邏輯
            method.invoke(activityManagerInstance, *args)
        }
    }
}

override fun uninstall() {
    // 關(guān)閉 mH.mCallback 的 Hook
    uninstallActivityManager?.invoke()
    uninstallActivityThreadHandlerCallback?.invoke()
    uninstallActivityManager = null
    uninstallActivityThreadHandlerCallback = null
}

// 使用反射修改 ActivityThread 的主線(xiàn)程消息循環(huán)的 mH.mCallback
// swap 是一個(gè) lambda 表達(dá)式,參數(shù)為原對(duì)象,返回值為注入的新對(duì)象
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 的返回值作為新對(duì)象,實(shí)現(xiàn) Hook
    mCallbackField[mH] = swap(mCallback)
}

// 使用反射修改 AMS 與 App 通信的 IActivityManager Binder 對(duì)象
// swap 是一個(gè) lambda 表達(dá)式,參數(shù)為 IActivityManager 的 Class 對(duì)象和接口原實(shí)現(xiàn)對(duì)象,返回值為注入的新對(duì)象
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 的返回值作為新對(duì)象,實(shí)現(xiàn) Hook
    mInstanceField[activityManagerSingletonInstance] = swap(iActivityManagerInterface, activityManagerInstance!!)
}
  • 5、RootView 監(jiān)控: 由于 Android Framework 未提供設(shè)置全局監(jiān)聽(tīng) RootView 從 WindowManager 中移除的方法,所以 LeakCanary 是通過(guò) Hook 的方式實(shí)現(xiàn)的,這一塊是通過(guò) squareup 另一個(gè)開(kāi)源庫(kù) curtains 實(shí)現(xiàn)的。

RootView 監(jiān)控這部分源碼也比較復(fù)雜了,需要通過(guò) 2 步 Hook 來(lái)實(shí)現(xiàn):

  • 1、Hook WMS 服務(wù)內(nèi)部的 WindowManagerGlobal.mViews RootView 列表,獲取 RootView 新增和移除的時(shí)機(jī);
  • 2、檢查 View 對(duì)應(yīng)的 Window 類(lèi)型,如果是 Dialog 或 DreamService 等類(lèi)型,則在注冊(cè) View#addOnAttachStateChangeListener() 監(jiān)聽(tīng),在其中的 onViewDetachedFromWindow() 回調(diào)中將 View 對(duì)象交給 ObjectWatcher 監(jiān)控。

LeakCanary 源碼摘要如下:

RootViewWatcher.kt

override fun install() {
    // 1. 注冊(cè) RootView 監(jiān)聽(tīng)
    Curtains.onRootViewsChangedListeners += listener
}

private val listener = OnRootViewAddedListener { rootView ->
    val trackDetached = when(rootView.windowType) {
    PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
            // Activity 類(lèi)型已經(jīng)在 ActivityWatcher 中監(jiān)控了,不需要重復(fù)監(jiān)控
            is Activity -> false
            is Dialog -> {
                // leak_canary_watcher_watch_dismissed_dialogs:Dialog 監(jiān)控開(kāi)關(guān)
                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. 注冊(cè) View#addOnAttachStateChangeListener 監(jiān)聽(tīng)
        rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
            val watchDetachedView = Runnable {
                // 3. 交給 ObjectWatcher 監(jiān)控
                reachabilityWatcher.expectWeaklyReachable(rootView /*被監(jiān)控對(duì)象*/ , "${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>() {
    // 重寫(xiě) ArrayList#add 方法
    override fun add(element: View): Boolean {
        // 回調(diào)
        listeners.forEach { it.onRootViewsChanged(element, true) }
        return super.add(element)
    }

    // 重寫(xiě) ArrayList#removeAt 方法
    override fun removeAt(index: Int): View {
        // 回調(diào)
        val removedView = super.removeAt(index)
        listeners.forEach { it.onRootViewsChanged(removedView, false) }
        return removedView
    }
}

companion object {
    fun install(): RootViewsSpy {
        return RootViewsSpy().apply {
            WindowManagerSpy.swapWindowManagerGlobalMViews { mViews /*原對(duì)象*/ ->
                // 新對(duì)象(lambda 表達(dá)式的末行就是返回值)
                delegatingViewList.apply { addAll(mViews) }
            }
        }
    }
}

WindowManageSpy.kt

// Hook WMS 服務(wù)內(nèi)部的 WindowManagerGlobal.mViews RootView 列表
// swap 是一個(gè) lambda 表達(dá)式,參數(shù)為原對(duì)象,返回值為注入的新對(duì)象
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 的各個(gè)位置安插監(jiān)控,實(shí)現(xiàn)對(duì) Activity 和 Service 等對(duì)象進(jìn)入無(wú)用狀態(tài)的監(jiān)聽(tīng)。我們可以用一張示意圖描述 LeakCanary 的部分結(jié)構(gòu):

6.3 LeakCanary 如何判定對(duì)象泄漏?

在以上步驟中,當(dāng)對(duì)象的使用生命周期結(jié)束后,會(huì)交給 ObjectWatcher 監(jiān)控,現(xiàn)在我們來(lái)具體看下它是怎么判斷對(duì)象發(fā)生泄漏的。主要邏輯概括為 3 步:

  • 第 1 步: 為被監(jiān)控對(duì)象 watchedObject 創(chuàng)建一個(gè) KeyedWeakReference 弱引用,并存儲(chǔ)到 <UUID, KeyedWeakReference> 的映射表中;
  • 第 2 步: postDelay 五秒后檢查引用對(duì)象是否出現(xiàn)在引用隊(duì)列中,出現(xiàn)在隊(duì)列則說(shuō)明被監(jiān)控對(duì)象未發(fā)生泄漏。隨后,移除映射表中未泄露的記錄,更新泄漏的引用對(duì)象的 retainedUptimeMillis 字段以標(biāo)記為泄漏;
  • 第 3 步: 通過(guò)回調(diào) onObjectRetained 告知 LeakCanary 內(nèi)部發(fā)生新的內(nèi)存泄漏。

源碼摘要如下:

AppWatcher.kt

val objectWatcher = ObjectWatcher(
    // lambda 表達(dá)式獲取當(dāng)前系統(tǒng)時(shí)間
    clock = { SystemClock.uptimeMillis() },
    // lambda 表達(dá)式實(shí)現(xiàn) Executor SAM 接口
    checkRetainedExecutor = {
        mainHandler.postDelayed(it, retainedDelayMillis)
    },
    // lambda 表達(dá)式獲取監(jiān)控開(kāi)關(guān)
    isEnabled = { true }
)

ObjectWatcher.kt

class ObjectWatcher constructor(
    private val clock: Clock,
    private val checkRetainedExecutor: Executor,
    private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

    if (!isEnabled()) {
        // 監(jiān)控開(kāi)關(guān)
        return
    }

    // 被監(jiān)控的對(duì)象映射表 <UUID,KeyedWeakReference>
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

    // KeyedWeakReference 關(guān)聯(lián)的引用隊(duì)列,用于判斷對(duì)象是否泄漏
    private val queue = ReferenceQueue<Any>()

    // 1. 為 watchedObject 對(duì)象增加監(jiān)控
    @Synchronized 
    override fun expectWeaklyReachable(
        watchedObject: Any,
        description: String
    ) {
        // 1.1 移除 watchedObjects 中未泄漏的引用對(duì)象
        removeWeaklyReachableObjects()
        // 1.2 新建一個(gè) KeyedWeakReference 引用對(duì)象
        val key = UUID.randomUUID().toString()
        val watchUptimeMillis = clock.uptimeMillis()
        watchedObjects[key] = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
        // 2. 五秒后檢查引用對(duì)象是否出現(xiàn)在引用隊(duì)列中,否則判定發(fā)生泄漏
        // checkRetainedExecutor 相當(dāng)于 postDelay 五秒后執(zhí)行 moveToRetained() 方法
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }

    // 2. 五秒后檢查引用對(duì)象是否出現(xiàn)在引用隊(duì)列中,否則說(shuō)明發(fā)生泄漏
    @Synchronized 
    private fun moveToRetained(key: String) {
        // 2.1 移除 watchedObjects 中未泄漏的引用對(duì)象
        removeWeaklyReachableObjects()
        // 2.2 依然存在的引用對(duì)象被判定發(fā)生泄漏
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            // 3. 回調(diào)通知 LeakCanary 內(nèi)部處理
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    // 移除未泄漏對(duì)象對(duì)應(yīng)的 KeyedWeakReference
    private fun removeWeaklyReachableObjects() {
        var ref: KeyedWeakReference?
        do {
            ref = queue.poll() as KeyedWeakReference?
            if (ref != null) {
                // KeyedWeakReference 出現(xiàn)在引用隊(duì)列中,說(shuō)明未發(fā)生泄漏
                watchedObjects.remove(ref.key)
            }
        } while (ref != null)
    }

    // 4. Heap Dump 后移除所有監(jiān)控時(shí)間早于 heapDumpUptimeMillis 的引用對(duì)象
    @Synchronized 
    fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
        val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
        weakRefsToRemove.values.forEach { it.clear() }
        watchedObjects.keys.removeAll(weakRefsToRemove.keys)
    }

    // 獲取是否有內(nèi)存泄漏對(duì)象
    val hasRetainedObjects: Boolean
    @Synchronized get() {
        // 移除 watchedObjects 中未泄漏的引用對(duì)象
        removeWeaklyReachableObjects()
        return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
    }

    // 獲取內(nèi)存泄漏對(duì)象計(jì)數(shù)
    val retainedObjectCount: Int
    @Synchronized get() {
        // 移除 watchedObjects 中未泄漏的引用對(duì)象
        removeWeaklyReachableObjects()
        return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
    }
}

被監(jiān)控對(duì)象 watchedObject 關(guān)聯(lián)的弱引用對(duì)象:

KeyedWeakReference.kt

class KeyedWeakReference(
    // 被監(jiān)控對(duì)象
    referent: Any,
    // 唯一 Key,根據(jù)此字段匹配映射表中的記錄
    val key: String,
    // 描述信息
    val description: String,
    // 監(jiān)控開(kāi)始時(shí)間,即引用對(duì)象創(chuàng)建時(shí)間
    val watchUptimeMillis: Long,
    // 關(guān)聯(lián)的引用隊(duì)列
    referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(referent, referenceQueue) {
  
    // 記錄實(shí)際對(duì)象 referent 被判定為泄漏對(duì)象的時(shí)間
    // -1L 表示非泄漏對(duì)象,或者還未判定完成
    @Volatile
    var retainedUptimeMillis = -1L

    override fun clear() {
        super.clear()
        retainedUptimeMillis = -1L
    }

    companion object {
        // 記錄最近一次觸發(fā) Heap Dump 的時(shí)間
        @Volatile
        @JvmStatic var heapDumpUptimeMillis = 0L
    }
}

6.4 LeakCanary 發(fā)現(xiàn)泄漏對(duì)象后就會(huì)觸發(fā)分析嗎?

ObjectWatcher 判定被監(jiān)控對(duì)象發(fā)生泄漏后,會(huì)通過(guò)接口方法 OnObjectRetainedListener#onObjectRetained() 回調(diào)到 LeakCanary 內(nèi)部的管理器 InternalLeakCanary 處理(在前文 AppWatcher 初始化中提到過(guò))。LeakCanary 不會(huì)每次發(fā)現(xiàn)內(nèi)存泄漏對(duì)象都進(jìn)行分析工作,而會(huì)進(jìn)行兩個(gè)攔截:

  • 攔截 1:泄漏對(duì)象計(jì)數(shù)未達(dá)到閾值,或者進(jìn)入后臺(tái)時(shí)間未達(dá)到閾值;
  • 攔截 2:計(jì)算距離上一次 HeapDump 未超過(guò) 60s。

源碼摘要如下:

InternalLeakCanary.kt

// 從 ObjectWatcher 回調(diào)過(guò)來(lái)
override fun onObjectRetained() = scheduleRetainedObjectCheck()

private lateinit var heapDumpTrigger: HeapDumpTrigger

fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
        heapDumpTrigger.scheduleRetainedObjectCheck()
    }
}

HeapDumpTrigger.kt

fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
    // 已簡(jiǎn)化:源碼此處使用時(shí)間戳攔截,避免重復(fù) postDelayed
    backgroundHandler.postDelayed({
        checkRetainedObjects()
    }, delayMillis)
}

private fun checkRetainedObjects() {
    val config = configProvider()

    // 泄漏對(duì)象計(jì)數(shù)
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    if (retainedReferenceCount > 0) {
        // 主動(dòng)觸發(fā) GC,并等待 100 ms
        gcTrigger.runGc()
        // 重新獲取泄漏對(duì)象計(jì)數(shù)
        retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    // 攔截 1:泄漏對(duì)象計(jì)數(shù)未達(dá)到閾值,或者進(jìn)入后臺(tái)時(shí)間未達(dá)到閾值
    if (retainedKeysCount < retainedVisibleThreshold) {
        // App 位于前臺(tái)或者剛剛進(jìn)入后臺(tái)
        if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
            // 發(fā)送通知提醒
            showRetainedCountNotification("App visible, waiting until %d retained objects")
            // 延遲 2 秒再檢查
            scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
            return;
        }
    }

    // 攔截 2:計(jì)算距離上一次 HeapDump 未超過(guò) 60s
    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
        // 發(fā)送通知提醒
        showRetainedCountNotification("Last heap dump was less than a minute ago")
        // 延遲 (60 - elapsedSinceLastDumpMillis)s 再檢查
        scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis)
        return
    }
    
    // 移除通知提醒
    dismissRetainedCountNotification()
    // 觸發(fā) HeapDump(此時(shí),應(yīng)用有可能在后臺(tái))
    dumpHeap(...)
}

// 真正開(kāi)始執(zhí)行 Heap Dump
private fun dumpHeap(...) {
    // 1. 獲取文件存儲(chǔ)提供器
    val directoryProvider = InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)

    // 2. 創(chuàng)建 .hprof File 文件
    val heapDumpFile = directoryProvider.newHeapDumpFile()

    // 3. 執(zhí)行 Heap Dump
    // Heap Dump 開(kāi)始時(shí)間戳
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    // heapDumper.dumpHeap:最終調(diào)用 Debug.dumpHprofData(heapDumpFile.absolutePath) 
    configProvider().heapDumper.dumpHeap(heapDumpFile)

    // 4. 清除 ObjectWatcher 中過(guò)期的監(jiān)控
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)

    // 5. 分析堆快照
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
}

請(qǐng)求 GC 的源碼可以看一眼:

GcTrigger.kt

fun interface GcTrigger {

    fun runGc()

    object Default : GcTrigger {
        override fun runGc() {
            // Runtime.gc() 相比于 System.gc() 更有可能觸發(fā) GC
            Runtime.getRuntime().gc()
            // 暫停等待 GC 
            Thread.sleep(100)
            System.runFinalization()
        }
    }
}

6.5 LeakCanary 在哪個(gè)線(xiàn)程分析堆快照?

在前面的工作中,LeakCanary 已經(jīng)成功生成 .hprof 堆快照文件,并且發(fā)送了一個(gè) LeakCanary 內(nèi)部事件 HeapDump。那么這個(gè)事件在哪里被消費(fèi)的呢?

一步步跟蹤代碼可以看到 LeakCanary 的配置項(xiàng)中設(shè)置了多個(gè)事件消費(fèi)者 EventListener,其中與 HeapDump 事件有關(guān)的是 when{} 代碼塊中三個(gè)消費(fèi)者。不過(guò),這三個(gè)消費(fèi)者并不是并存的,而是會(huì)根據(jù) App 當(dāng)前的依賴(lài)項(xiàng)而選擇最優(yōu)的執(zhí)行策略:

  • 策略 1 - WorkerManager 多進(jìn)程分析
  • 策略 2 - WorkManager 異步分析
  • 策略 3 - 異步線(xiàn)程分析(兜底策略)

LeakCanary 配置項(xiàng)中的事件消費(fèi)者:

LeakCanary.kt

data class Config(
    val eventListeners: List<EventListener> = listOf(
        LogcatEventListener,
        ToastEventListener,
        LazyForwardingEventListener {
            if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
        },
        when {
            // 策略 1 - WorkerManager 多進(jìn)程分析
            RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->RemoteWorkManagerHeapAnalyzer
            // 策略 2 - WorkManager 異步分析
            WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
            // 策略 3 - 異步線(xiàn)程分析(兜底策略)
            else -> BackgroundThreadHeapAnalyzer
        }
    ),
    ...
)
  • 策略 1 - WorkerManager 多進(jìn)程分析: 判斷是否可以類(lèi)加載 RemoteLeakCanaryWorkerService ,這個(gè)類(lèi)位于前文提到的 com.squareup.leakcanary:leakcanary-android-process:2.9.1 依賴(lài)中。如果可以類(lèi)加載成功則視為有依賴(lài),使用 WorkerManager 多進(jìn)程分析;

RemoteWorkManagerHeapAnalyzer.kt

object RemoteWorkManagerHeapAnalyzer : EventListener {

    // 通過(guò)類(lèi)加載是否成功,判斷是否存在依賴(lài)
    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) {
            // 創(chuàng)建并分發(fā) WorkManager 多進(jìn)程請(qǐng)求
            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 發(fā)送分析進(jìn)度事件
                if (!result.isCancelled) {
                    InternalLeakCanary.sendEvent(progressEvent)
                }
            }
            // 1.3 發(fā)送分析完成事件
            InternalLeakCanary.sendEvent(doneEvent)
            result.set(Result.success())
        }
        return result
    }
}
  • 策略 2 - WorkManager 異步分析: 判斷是否可以類(lèi)加載 androidx.work.WorkManager ,如果可以,則使用 WorkManager 異步分析;

WorkManagerHeapAnalyzer.kt

internal val validWorkManagerInClasspath by lazy {
    // 判斷 WorkManager 依賴(lài),代碼略
}

override fun onEvent(event: Event) {
    if (event is HeapDump) {
        // 創(chuàng)建并分發(fā) WorkManager 請(qǐng)求
        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 發(fā)送分析進(jìn)度事件
            InternalLeakCanary.sendEvent(event)
        }
        // 2.3 發(fā)送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
        return Result.success()
    }
}
  • 策略 3 - 異步線(xiàn)程分析(兜底策略): 如果以上策略未命中,則直接使用子線(xiàn)程兜底執(zhí)行。

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 請(qǐng)求
            heapAnalyzerThreadHandler.post {
                // 3.1 分析堆快照
                val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
                    // 3.2 發(fā)送分析進(jìn)度事件
                    InternalLeakCanary.sendEvent(event)
                }
                // 3.3 發(fā)送分析完成事件
                InternalLeakCanary.sendEvent(doneEvent)
            }
        }
    }
}

可以看到,不管采用那種執(zhí)行策略,最終執(zhí)行的邏輯都是一樣的:

  • 1、分析堆快照;
  • 2、發(fā)送分析進(jìn)度事件;
  • 3、發(fā)送分析完成事件。

6.5 LeakCanary 如何分析堆快照?

在前面的分析中,我們已經(jīng)知道 LeakCanary 是通過(guò)子線(xiàn)程或者子進(jìn)程執(zhí)行 AndroidDebugHeapAnalyzer.runAnalysisBlocking 方法來(lái)分析堆快照的,并在分析過(guò)程中和分析完成后發(fā)送回調(diào)事件?,F(xiàn)在我們來(lái)閱讀 LeakCanary 的堆快照分析過(guò)程:

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. 將分析報(bào)告持久化到 DB
    val id = HeapAnalysisTable.insert(db, heapAnalysis)
    // 4. 發(fā)送分析完成事件(返回到上一級(jí)進(jìn)行發(fā)送: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(…),繼續(xù)往下走:

AndroidDebugHeapAnalyzer.kt

private fun analyzeHeap(
    heapDumpFile: File,
    progressListener: OnAnalysisProgressListener,
    isCanceled: () -> Boolean
): HeapAnalysis {
    ...
    // Shark 堆快照分析器
    val heapAnalyzer = HeapAnalyzer(progressListener)
    ...
    // 構(gòu)建對(duì)象圖信息
    val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile)
    val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping())
    ...
    // 開(kāi)始分析
    heapAnalyzer.analyze(
    heapDumpFile = heapDumpFile,
    graph = graph,
    leakingObjectFinder = config.leakingObjectFinder, // 默認(rèn)是 KeyedWeakReferenceFinder
    referenceMatchers = config.referenceMatchers, // 默認(rèn)是 AndroidReferenceMatchers
    computeRetainedHeapSize = config.computeRetainedHeapSize, // 默認(rèn)是 true
    objectInspectors = config.objectInspectors, // 默認(rèn)是 AndroidObjectInspectors
    metadataExtractor = config.metadataExtractor // 默認(rèn)是 AndroidMetadataExtractor
    )
}

開(kāi)始進(jìn)入 Shark 組件:

shark.HeapAnalyzer.kt

// analyze -> analyze -> FindLeakInput.analyzeGraph
private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
): HeapAnalysisSuccess {
    ...
    // 1. 在堆快照中尋找泄漏對(duì)象,默認(rèn)是尋找 KeyedWeakReference 類(lèi)型對(duì)象
    // leakingObjectFinder 默認(rèn)是 KeyedWeakReferenceFinder
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    // 2. 分析泄漏對(duì)象的最短引用鏈,并按照應(yīng)用鏈簽名分類(lèi)
    // applicationLeaks: Application Leaks
    // librbuildLeakTracesaryLeaks:Library Leaks
    // unreachableObjects:LeakCanary 無(wú)法分析出強(qiáng)引用鏈,可以提 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 無(wú)法分析出強(qiáng)引用鏈(相當(dāng)于 LeakCanary 的 Bug)
    val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)
    // shortestPaths:最短引用鏈
    val shortestPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
    // inspectedObjectsByPath:標(biāo)記信息
    val inspectedObjectsByPath = inspectObjects(shortestPaths)
    // retainedSizes:泄漏內(nèi)存大小
    val retainedSizes = computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree)
    // 生成單個(gè)泄漏問(wèn)題的分析報(bào)告,并按照應(yīng)用鏈簽名分組,按照 Application Leaks 和 Library Leaks 分類(lèi),按照 Application Leaks 和 Library Leaks 分類(lèi)
    // applicationLeaks: Application Leaks
    // librbuildLeakTracesaryLeaks:Library Leaks
    val (applicationLeaks, librbuildLeakTracesaryLeaks) = buildLeakTraces(shortestPaths, inspectedObjectsByPath, retainedSizes)
    return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects)
}

可以看到,堆快照分析最終是交給 Shark 中的 HeapAnalizer 完成的,核心流程是:

  • 1、在堆快照中尋找泄漏對(duì)象,默認(rèn)是尋找 KeyedWeakReference 類(lèi)型對(duì)象;
  • 2、分析 KeyedWeakReference 對(duì)象的最短引用鏈,并按照引用鏈簽名分組,按照 Application Leaks 和 Library Leaks 分類(lèi);
  • 3、返回分析完成事件。

第 1 步和第 3 步不用說(shuō)了,繼續(xù)分析最復(fù)雜的第 2 步:

shark.HeapAnalyzer.kt

// 生成單個(gè)泄漏問(wèn)題的分析報(bào)告,并按照應(yīng)用鏈簽名分組,按照 Application Leaks 和 Library Leaks 分類(lèi),按照 Application Leaks 和 Library Leaks 分類(lèi)
private fun FindLeakInput.buildLeakTraces(
    shortestPaths: List<ShortestPath> /*最短引用鏈*/ ,
    inspectedObjectsByPath: List<List<InspectedObject>> /*標(biāo)記信息*/ ,
    retainedSizes: Map<Long, Pair<Int, Int>>? /*泄漏內(nèi)存大小*/
): 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 ->
        // 標(biāo)記信息
        val inspectedObjects = inspectedObjectsByPath[pathIndex]
        // 實(shí)例化引用鏈上的每個(gè)對(duì)象快照(非懷疑對(duì)象的 leakingStatus 為 NOT_LEAKING)
        val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes)
        val referencePath = buildReferencePath(shortestPath, leakTraceObjects)
        // 分析報(bào)告
        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) ->
        // 實(shí)例化為 ApplicationLeak 類(lèi)型
        ApplicationLeak(leakTraces)
    }
    val libraryLeaks = libraryLeaksMap.map { (_, pair) ->
        // 實(shí)例化為 LibraryLeak 類(lèi)型
        val (matcher, leakTraces) = pair
        LibraryLeak(leakTraces, matcher.pattern, matcher.description)
    }
    return applicationLeaks to libraryLeaks
}

6.6 LeakCanary 如何篩選 ~~~ 懷疑對(duì)象?

LeakCanary 會(huì)使用 ObjectInspector 對(duì)象檢索器在引用鏈上的節(jié)點(diǎn)中標(biāo)記必要的信息和狀態(tài),標(biāo)記信息會(huì)顯示在分析報(bào)告中,并且會(huì)影響報(bào)告中的提示。而引用鏈 LEAKING 節(jié)點(diǎn)以后到第一個(gè) NOT_LEAKING 節(jié)點(diǎn)中間的節(jié)點(diǎn),才會(huì)用 ~~~ 下劃線(xiàn)標(biāo)記為懷疑對(duì)象。

在第 6.5 節(jié)中,LeakCanary 通過(guò) leakingObjectFinder 標(biāo)記引用信息,leakingObjectFinder 默認(rèn)是 AndroidObjectInspectors.appDefaults ,也可以在配置項(xiàng)中自定義。

// inspectedObjectsByPath:篩選出非懷疑對(duì)象(分析報(bào)告中 ~~~ 標(biāo)記的是懷疑對(duì)象)
val inspectedObjectsByPath = inspectObjects(shortestPaths)

看一下可視化報(bào)告中相關(guān)源碼:

DisplayLeakAdapter.kt

...
val reachabilityString = when (leakingStatus) {
    UNKNOWN -> extra("UNKNOWN")
    NOT_LEAKING -> "NO" + extra(" (${leakingStatusReason})")
    LEAKING -> "YES" + extra(" (${leakingStatusReason})")
}
...

LeakTrace.kt

// 是否為懷疑對(duì)象
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 分析完成后的處理

有兩個(gè)位置處理了 HeapAnalysisSucceeded 事件:

  • Logcat:打印分析報(bào)告日志;
  • Notification: 發(fā)送分析成功系統(tǒng)通知消息。

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
    }
    // 點(diǎn)擊通知消息打開(kāi)可視化分析報(bào)告
    val pendingIntent = PendingIntent.getActivity(appContext, 1,  event.showIntent, flags)
    showHeapAnalysisResultNotification(contentTitle,pendingIntent)
    ...
}

至此,LeakCanary 原理分析完畢。


7. 總結(jié)

到這里,LeakCanary 的使用和原理分析就講完了。不過(guò),LeakCanary 畢竟是實(shí)驗(yàn)室使用的工具,如果要實(shí)現(xiàn)線(xiàn)上內(nèi)存泄漏監(jiān)控,你知道怎么做嗎?要實(shí)現(xiàn) Native 內(nèi)存泄漏監(jiān)控又要怎么做?關(guān)注我,帶你了解更多。


參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容