LeakCanary 源码分析 #1
LeakCanary 是一个检查内存泄漏的库。以下是官网中的定义:
LeakCanary is a memory leak detection library for Android.
相关网站:
square/leakcanary: A memory leak detection library for Android. (github.com)
官网描述
我们先来看看官网中的一些描述
Getting started
我们只需要将 leakcanary-android
依赖添加到你的 build.gradle
中:
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
// 使用 debugImplementation ,只在 debug 环境引入
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
完成了,不需要改变你的任何代码。
可以通过 Logcat 中 tag 为 LeakCanary 的日志来确保 LeakCnary 启动:
D LeakCanary: LeakCanary is running and ready to detect leaks
LeakCanary 会自动检测以下对象的内存泄漏:
- 被销毁的
Activity
实例 - 被销毁的
Fragment
实例 - 被销毁的 fragment 的
View
实例 - 被销毁的
viewModel
实例
How LeakCanary works
LeakCanary 被安装后,会自动检测和报告内存泄漏,具体来说会执行以下四个步骤:
- 检测 被保留 的对象(没有被 GC 回收)
- 生成堆内存快照 Heap dump
- 分析堆内存
- 为泄露分类(判断属于哪一种类型)
1. 检测保留对象
LeakCanary 通过 hook android 中的 lifecycle 对象来自动判断 activity 与 fragment 应该被 GC 的时候。这些需要被摧毁的对象(activity 与 fragment 等)会传入一个 ObjectWatcher,Watcher 会持有该对象的 弱引用来判断是否被回收 。LeakCanary 会自动检测以上说明的支持自动检测的实例,同时你还可以使用 ObjectWatcher 来检测自定义的已经不需要的实例:
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
如果 objectWatcher 持有的弱引用在 5 秒并且执行了一次 GC 之后还存在,则认为该对象为 保留对象。同时会打出 Log:
D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
(Activity received Activity#onDestroy() callback)
... 5 seconds later ...
D LeakCanary: Scheduling check for retained objects because found new object
retained
如果保留对象的数量达到了一个阈值,则会生成对内存快照进行分析。同时等待时会显示带有当前保留对象数量与阈值的通知。
同时可能会出现该 Log:
D LeakCanary: Rescheduling check for retained objects in 2000ms because found
only 4 retained objects (< 5 while app visible)
(Log 中表示在 2000 ms 内保留对象没达到阈值则会重新进行保留对象分析)
2. 生成堆内存快照
当保留对象达到阈值时 LeakCanary 会生成一个堆内存快照,会在安卓的 文件系统 中储存一个 .hprof
文件。
默认会存储到 应用程序目录下( android/data/包名
),如果已经授予了 WRITE_EXTERNAL_STORAGE
权限则会储存在 储存卡/leakcanary-[包名]
目录下。如果需要运行时动态申请,则会弹出一个请求通知,可以通过点击通知运行时授予权限。
生成 堆内存快照 会短暂冻结软件,同时会弹出一个 Toast
3. 分析对内存快照
LeakCanary 会使用 Shark 模块来分析 .hprof
文件,定位保留对象的位置。LeakCanary 会找到每一个保留对象的引用路径。
获取到引用路径后,LeakCanary 会对内存泄漏的原因进行分析,并生成其签名,同时按照其签名进行分组。
分组结束后会弹出通知说明有多少个保留对象被分为多少个组,同时在 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. 为内存泄漏分类
WeakCanary 会将 内存泄漏 分为两类,其中一类为 Application Leaks,为项目源码中的代码的内存泄漏,第二类为 Library Leaks,为该项目引用的第三方库中的内存泄漏。第三方库中的内存泄漏可能不在该项目开发者处理的范畴,因此将其独立出来。
Module 架构
-
leakcanary-android:入口 Module,我们使用一般就依赖该 Module,在由该 Module 依赖其他 模块。同时该 Module 中还有一个 AndroidManifest.xml 文件,其中注册了一个 用于分析内存泄漏的 Service 。
<application> <service android:name="leakcanary.internal.HeapAnalyzerService" android:exported="false" /> </application>
-
leakcanary-android-core:核心代码,包括显示内存泄漏信息的页面,通知等代码。
-
leakcanary-object-watcher:观察者相关代码,核心为一个 Watcher 对象,之前例子中的 objectWatcher 对象。
-
leakcanary-object-watcher-android、leakcanary-object-watcher-androidx、leakcanary-object-watcher-android-support-fragment:各种支持自动分析内存泄漏的对象的 watcher 对象。
-
shark:各种分析模块的父 Module
-
shark-android:分析安卓设备,包括型号安卓版本等信息
-
shark-hprof:分析
.hprof
文件 -
shark-graph:分析图
-
其他 sharkx-xxx:各种分析模块
-
plumber-android:自动修复工具,对于已知的内存泄漏问题,尝试在进行时进行自动修复(例如通过反射直接将其置空),包括 Library Weak,目前只收录了十几个已知问题。
还有一些其他 Module,其中有一部分代码,但难以总结该 Module 的作用,这里不给出。
接下来开始分析代码。
Object-Watcher 保留对象观察
对象观察者,首先就是 ObjectWatcher 对象。首先是构造方法:
class ObjectWatcher constructor(
private val clock: Clock, // 时钟,确保时间一致
private val checkRetainedExecutor: Executor, // 线程池,用于等待检测是否可达的事件
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true } // 如果返回 false,则会忽略对 watch 的调用(关闭功能)
) : ReachabilityWatcher {
// ……
}
关于观察的方法,LeakCanary 使用了 ReachabilityWatcher 接口进行定义:
fun interface ReachabilityWatcher {
/**
* Expects the provided [watchedObject] to become weakly reachable soon. If not,
* [watchedObject] will be considered retained.
*/
fun expectWeaklyReachable(
watchedObject: Any,
description: String
)
}
调用该方法说明我们传入的 watchedObject 很快将会变得弱可达(Watcher 本身也会持有弱引用),如果没有,将会把该对象识别为保留对象。
也就是说调用该方法 LeakCanary 将会在接下来一段时间后判断该对象是否弱可达。我们回到 ObjectWatcher,看看该方法的具体实现:
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
// 是否开启,如果关闭则不作任何处理
if (!isEnabled()) {
return
}
// 删除该 Watcher 观察的所有已经处于弱可达的对象
removeWeaklyReachableObjects()
// UUID
val key = UUID.randomUUID()
.toString()
// 获取时间
val watchUptimeMillis = clock.uptimeMillis()
// 构造 KeyedWeakReference 对象
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
// 放进 watchedObjects 容器中
watchedObjects[key] = reference
// 使用 checkRetainedExecutor 添加检查方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
先来看看 KeyedWeakReference 对象:
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>( // 继承 WeakReference
referent, referenceQueue
) {
/**
* Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't
* been yet.
*/
// 该对象被视为保留对象的时间,如果为 -1 这说明当前不是保留对象
@Volatile
var retainedUptimeMillis = -1L
override fun clear() {
super.clear()
retainedUptimeMillis = -1L
}
companion object {
// 获取堆快照的开始时间
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}
}
可以看到就是将 key、description 与 watchUptimeMillis 信息包装进 WeakReference 对象。
该对象传入了一个 ReferenceQueue<Any>
对象,在弱引用的对象被回收后,将会在该队列中加入弱引用对象。
接下来我们来到 removeWeaklyReachableObjects 方法:
// 将已经被回收的弱引用对象从 watchedObjects 中删除
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
// 如果队列中的对象为 KeyedWeakReference
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// 删除对象
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
该方法就是将已经回收的对象从观察对象中删除,可以看做刷新当前的 watchedObjects 容器。
然后来到 moveToRetained:
@Synchronized private fun moveToRetained(key: String) {
// 在调用一次 removeWeaklyReachableObjects 刷新容器
removeWeaklyReachableObjects()
// 获取对应的 引用
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 如果没被回收,则会看做保留对象,这里调用一下回调和更新时间变量
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
可以看到这里没有涉及延时操作,实际上,延时是通过传入的 Executor 控制的。
例如我们来到 AppWatcher,这是针对安卓的一个单例模式类,其中就构造了一个 ObjectWatcher 对象:
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
// 通过 executor 达到延时
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
// 使用 handler 达到延迟的效果
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
Object-Watcher-Android 使用 ContentProvider 自动启动
首先来到 AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher"
>
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
</manifest>
可以看到这里注册了一个 ContentProvider,这里也是 LeakCanary 能不改变任何代码,直接导入就可以使用的原因,可以看到这个 ContentProvider 的位置为 leakcanary.internal.AppWatcherInstaller$MainProcess
,值得注意的是这里有一个 enabled 字段,引用了 res/values 中的一个字段 leak_canary_watcher_auto_install
,代表是否要自动启用。
回到这个 Provider, 是一个内部类,代码如下:
internal sealed class AppWatcherInstaller : ContentProvider() {
// 自动安装的运行在主进程内部类
internal class MainProcess : AppWatcherInstaller()
// 运行在其他进程的
internal class LeakCanaryProcess : AppWatcherInstaller()
// 程序启动时会执行
override fun onCreate(): Boolean {
// 获取 Application
val application = context!!.applicationContext as Application
// 调用 AppWatcher.manualInstall 方法
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
strings: Array<String>?,
s: String?,
strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(
uri: Uri,
contentValues: ContentValues?
): Uri? {
return null
}
override fun delete(
uri: Uri,
s: String?,
strings: Array<String>?
): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
}
可以看到在 onCreate 里调用了 AppWatcher 的 manualInstall 方法,让我们来看看该对象:
首先是一个 Object 单例模式,然后有三个变量,其中一个为 retainedDelayMillis,表示延迟判断是否回收的时间,还有一个是 installCause ,类型为异常,用于判断是否安装,这里如果安装之后不为 null,有一个自定义异常,为 null 说明未安装,然后就是 objectWatcher:
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
这里与之前一样,不做展开。
我们重点来到 manualInstall 方法:
@JvmOverloads
fun manualInstall(
// Application
application: Application,
// 延迟判断保留对象时间
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
// 要观察的的对象,默认会调用 appDefaultWatchers 方法
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
// 在主线程执行
checkMainThread()
// 只能安装一次
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
// 延迟时间要大于等于 0
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
// 已经安装好
installCause = RuntimeException("manualInstall() first called here")
// 延迟时间
this.retainedDelayMillis = retainedDelayMillis
// 如果是 debug 环境,则打印 Log
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
// (1)
LeakCanaryDelegate.loadLeakCanary(application)
// (2)
watchersToInstall.forEach {
it.install()
}
这里重点在后面的 LeakCanaryDelegate.loadLeakCanary(application)
,与最后的循环,让我们先来看这个循环,其中默认情况下 watchersToInstall 的值为调用 appDefaultWatchers(application) 获取:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
// Activity
ActivityWatcher(application, reachabilityWatcher),
// Fragment 和 ViewModel
FragmentAndViewModelWatcher(application, reachabilityWatcher),
// 跟 View
RootViewWatcher(reachabilityWatcher),
// Service
ServiceWatcher(reachabilityWatcher)
)
}
可以看到默认有四个 InstallableWatcher,这里只分析第一个:
class ActivityWatcher(
// Application
private val application: Application,
// ReachbilityWatcher ,实际上就是 ObjectWatcher 对象
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() { // 使用委托,委托给了 noOpDelegate,已提供拓展性
// 在 Activity onDestroy 时使用 ObjectWatcher 来观察
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
// 注册生命周期监听器
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
然后我们来到 委托,这里有两个地方有委托:
首先是 ActivityWatcher 中的 ActivityLifecycleCallbacks,使用了委托:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
// 委托给 noOpDelegate()
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
这里的 noOpDelegate() 方法:
internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
这里的 noOpDelegate() 方法:
internal inline fun <reified T : Any> noOpDelegate(): T {
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
) as T
}
private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
// no op
}
这里相当于实现了 ActivityLifecycleCallbacks 的所有方法,但是其他方法都是空实现,然后再次重写了 onActivityDestroyed,个人理解是增加了扩展性,我们可以在 NO_OP_HANDLER 中写出其他方法。
其次是 AppWatcher 的 manualInstall 方法中的
LeakCanaryDelegate.loadLeakCanary(application)
这里的 LeakCanaryDelegate 为一个单例模式:
internal object LeakCanaryDelegate {
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
// 通过反射获取 leakcanary.internal.InternalLeakCanary 中的对象
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}
override fun onObjectRetained() {
}
}
}
这里通过反射获取 leakcanary.internal.InternalLeakCanary 中的对象,然后强转成 (Application) -> Unit
接口。
当我们引用这个包之后,只要自己写一个该对象,则可自动执行,
Android-Core 安卓各组件的保留对象分析
实际上,在 Android-Core 中该地方就有一个对象,该对象代码比较多,这里只给出部分:
package leakcanary.internal
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
// 注册对象视为保留对象的回调
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// 初始化 保存堆内存快照的工具类
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
// 初始化 GC 触发器
val gcTrigger = GcTrigger.Default
//……
}
// 将对象视为保留对象之后会执行
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
// 调用 转存堆 工具类的代码
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
}
最后,当我们观察的对象超过时间后还没有被回收时,就会调用其中的 onObjectRetained,最终来到 heapDumpTrigger.scheduleRetainedObjectCheck(),这里也就是判断是否内存泄露的最后一个方法,在该方法里,如果泄露数量大于阈值,则会开始保存快照:
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
// 在后台执行,并延迟
backgroundHandler.postDelayed({
checkScheduledAt = 0
// 执行方法
checkRetainedObjects()
}, delayMillis)
}
最终来到 checkRetainedObjects() 方法:(只给出部分代码,剩下设计通知以及配置文件等的判断和分析)
private fun checkRetainedObjects() {
var retainedReferenceCount = objectWatcher.retainedObjectCount
// ……
// 如果有保留对象,则手动触发 GC
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
// 刷新 Count
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 判断阈值
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
// 生成快照,并进行分析
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
然后来到 dumpHeap 方法
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
){
//……
// 生成堆内存 .href 文件
// 最终会调用 Debug.dumpHprofData(path) 方法生成 .href 文件
// ………
// 分析 .href 文件
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
这里生成 .href 文件的过程省去,最终来到 HeapAnalyzerService.runAnalysis 方法,首先 HeapAnalyzerService 为一个 Service,其中通过伴生对象定义了一个 runAnalysis 方法,如下:
internal class HeapAnalyzerService : ForegroundService(
HeapAnalyzerService::class.java.simpleName,
R.string.leak_canary_notification_analysing,
R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {
companion object {
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
// 启动该 Service
startForegroundService(context, intent)
}
}
}
启动后,对调用其中的 onHandleIntentInForeground 方法,进而调用 analyzeHeap 方法:
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
// 构造一个 HeapAnalyzer 对象
val heapAnalyzer = HeapAnalyzer(this)
val proguardMappingReader = try {
ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
} catch (e: IOException) {
null
}
// 开始分析堆内存文件
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
objectInspectors = config.objectInspectors,
metadataExtractor = config.metadataExtractor,
proguardMapping = proguardMappingReader?.readProguardMapping()
)
}
HeapAnalyzer 为 Shark 这个 Module 中的代码,实际上 Shark 就是 LeakCanary 开源的一个 href 文件分析器,其中使用了图这种数据结构进行储存,并使用广度优先搜索寻找泄露对象的持有链。关于 LeakCanary 的分析暂时到这里,下一篇将仔细分析 Shark 这个 Module。