LeakCanary 是 Android 的内存泄漏检测库。

LeakCanary 对 Android 框架内部的了解使其具有独特的能力来缩小每次泄漏的原因,帮助开发人员大幅减少卡顿、应用程序无响应冻结和 OutOfMemoryError 崩溃。
要使用 LeakCanary,请将 leakcanary-android 依赖项添加到应用程序的 build.gradle 文件中:
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}
通过在 Logcat 中筛选 LeakCanary 标签来确认 LeakCanary 在启动时运行:
D LeakCanary: LeakCanary is running and ready to detect leaks
在基于 Java 的运行时中,内存泄漏是一种编程错误,它会导致应用程序保留对不再需要的对象的引用。因此,分配给该对象的内存无法回收。
例如,Android Activity 实例在调用其 onDestroy() 方法后就不再需要,而将该实例的引用存储在静态字段中会阻止其被垃圾回收。
大多数内存泄漏是由与对象生命周期相关的错误引起的。以下是一些常见的 Android 错误:
内存泄漏在 Android 应用中非常常见。随着小内存泄漏的累积,内存使用量不断增长,垃圾收集器 (GC) 运行频率也会随之增加,从而消耗更多 CPU,导致卡顿、界面冻结和应用程序无响应 (ANR) 报告,最终导致内存溢出 (OOME) 崩溃。LeakCanary 将帮助您在开发过程中查找并修复这些内存泄漏。Square 工程师首次在 Square Point Of Sale 应用中启用 LeakCanary 时,他们成功修复了多个泄漏,并将 OOM 崩溃率降低了 94%。
LeakCanary 安装后,它会通过 4 个步骤自动检测并报告内存泄漏:
LeakCanary 钩子函数嵌入 Android 生命周期,自动检测 Activity 和 Fragment 何时被销毁并应被垃圾回收。这些被销毁的对象会被传递给 ObjectWatcher,后者持有对它们的弱引用。LeakCanary 会自动检测以下对象的泄漏:
您可以监视任何不再需要的对象,例如已分离的视图或已销毁的 Presenter:
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
如果 ObjectWatcher 持有的弱引用在等待 5 秒并运行垃圾回收后仍未清除,则该对象将被视为保留,并可能发生泄漏。LeakCanary 将此记录到 Logcat 中:
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
LeakCanary 等待保留对象的数量达到阈值后再转储堆,并显示包含最新数量的通知。

图 1. LeakCanary 发现了 4 个保留对象。
D LeakCanary: Rescheduling check for retained objects in 2000ms because found
only 4 retained objects (< 5 while app visible)
当保留对象的数量达到阈值时,LeakCanary 会将 Java 堆转储到 .hprof 文件(堆转储)中,并存储在 Android 文件系统上(请参阅 LeakCanary 将堆转储存储在哪里?)。转储堆会导致应用程序短暂冻结,在此期间 LeakCanary 会显示以下提示:

图 2. LeakCanary 在转储堆时显示提示信息。
LeakCanary 使用 Shark 解析 .hprof 文件,并在堆转储中找到保留的对象。

图 3. LeakCanary 在堆转储中查找保留对象。
对于每个保留对象,LeakCanary 都会查找阻止该保留对象被垃圾回收的引用路径:即其泄漏痕迹。您将在下一部分学习如何分析泄漏痕迹:修复内存泄漏。

图 4. LeakCanary 计算每个保留对象的泄漏轨迹。
分析完成后,LeakCanary 会显示一条包含摘要的通知,并在 Logcat 中打印结果。请注意,下图中 4 个保留对象被分组为 2 个不同的泄漏。LeakCanary 为每个泄漏轨迹创建一个签名,并将具有相同签名的泄漏(即由同一错误导致的泄漏)归为一组。

图 5. 4 条泄漏痕迹变成了 2 个不同的泄漏特征。
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...
点击通知会启动一个提供更多详细信息的活动。稍后再点击 LeakCanary 启动器图标返回:

图 6. LeakCanary 为每个安装 LeakCanary 的应用添加一个启动器图标。
每行对应一组具有相同签名的泄漏。当应用首次触发具有该签名的泄漏时,LeakCanary 会将相应行标记为“New”。

图 7. 4 个泄漏分组为两行,每行代表一个不同的泄漏特征。
点击泄漏即可打开包含泄漏轨迹的屏幕。您可以通过下拉菜单在保留的对象及其泄漏轨迹之间切换。

图 8. 屏幕显示了 3 个泄漏,并按其共同的泄漏签名进行分组。
泄漏签名是每个疑似导致泄漏的引用的串联哈希值,即每个引用都带有红色下划线:

图 9. 泄漏追踪包含 3 个可疑引用。
当泄漏追踪以文本形式共享时,这些相同的可疑引用会带有 ~~~ 下划线:
...
│
├─ 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
版权所有 2015 Square, Inc.
根据 Apache 许可证 2.0 版(以下简称“许可证”)授权;您不得在未遵守本许可证的情况下使用此文件。您可以在以下网址获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律要求或双方书面同意,否则,根据本许可证分发的软件均按“原样”分发,不附带任何明示或暗示的保证或条件。请参阅许可证,了解本许可证下特定语言的权限和限制。