leakcanary是一个Android的内存泄漏检测库

LeakCanary 🐤


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

LeakCanary 是 Android 的内存泄漏检测库

LeakCanary 对 Android 框架内部的了解使其具有独特的能力来缩小每次泄漏的原因,帮助开发人员大幅减少卡顿、应用程序无响应冻结和 OutOfMemoryError 崩溃。

入门

要使用 LeakCanary,请将 leakcanary-android 依赖项添加到应用程序的 build.gradle 文件中:

language 复制代码
dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}

就这样,无需更改代码!

通过在 Logcat 中筛选 LeakCanary 标签来确认 LeakCanary 在启动时运行:

language 复制代码
D LeakCanary: LeakCanary is running and ready to detect leaks

LeakCanary 自动检测以下对象的泄漏:

  • 已销毁的 Activity 实例
  • 已销毁的 Fragment 实例
  • 已销毁的 Fragment View 实例
  • 已清除的 ViewModel 实例
  • 已销毁的 Service 实例

什么是内存泄漏?

在基于 Java 的运行时中,内存泄漏是一种编程错误,它会导致应用程序保留对不再需要的对象的引用。因此,分配给该对象的内存无法回收。

例如,Android Activity 实例在调用其 onDestroy() 方法后就不再需要,而将该实例的引用存储在静态字段中会阻止其被垃圾回收。

内存泄漏的常见原因

大多数内存泄漏是由与对象生命周期相关的错误引起的。以下是一些常见的 Android 错误:

  • 将 Fragment 实例添加到返回栈中,但没有在 Fragment.onDestroyView() 中清除该 Fragment 的视图字段(更多详情请参阅此 StackOverflow 答案)。
  • 将 Activity 实例作为 Context 字段存储在由于配置更改而导致 Activity 重建后仍保留的对象中。
  • 注册引用具有生命周期对象的监听器、广播接收器或 RxJava 订阅,并在生命周期结束时忘记注销。

为什么要使用 LeakCanary?

内存泄漏在 Android 应用中非常常见。随着小内存泄漏的累积,内存使用量不断增长,垃圾收集器 (GC) 运行频率也会随之增加,从而消耗更多 CPU,导致卡顿、界面冻结和应用程序无响应 (ANR) 报告,最终导致内存溢出 (OOME) 崩溃。LeakCanary 将帮助您在开发过程中查找并修复这些内存泄漏。Square 工程师首次在 Square Point Of Sale 应用中启用 LeakCanary 时,他们成功修复了多个泄漏,并将 OOM 崩溃率降低了 94%。

LeakCanary 的工作原理

LeakCanary 安装后,它会通过 4 个步骤自动检测并报告内存泄漏:

  1. 检测保留对象。
  2. 转储堆。
  3. 分析堆。
  4. 泄漏分类。

1. 检测保留对象

LeakCanary 钩子函数嵌入 Android 生命周期,自动检测 Activity 和 Fragment 何时被销毁并应被垃圾回收。这些被销毁的对象会被传递给 ObjectWatcher,后者持有对它们的弱引用。LeakCanary 会自动检测以下对象的泄漏:

  • 已销毁的 Activity 实例
  • 已销毁的 Fragment 实例
  • 已销毁的 Fragment View 实例
  • 已清除的 ViewModel 实例

您可以监视任何不再需要的对象,例如已分离的视图或已销毁的 Presenter:

language 复制代码
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 等待保留对象的数量达到阈值后再转储堆,并显示包含最新数量的通知。

LeakCanary 发现了 4 个保留对象

图 1. LeakCanary 发现了 4 个保留对象。

language 复制代码
D LeakCanary: Rescheduling check for retained objects in 2000ms because found
  only 4 retained objects (< 5 while app visible)

2. 转储堆

当保留对象的数量达到阈值时,LeakCanary 会将 Java 堆转储到 .hprof 文件(堆转储)中,并存储在 Android 文件系统上(请参阅 LeakCanary 将堆转储存储在哪里?)。转储堆会导致应用程序短暂冻结,在此期间 LeakCanary 会显示以下提示:

LeakCanary 在转储堆时显示提示信息

图 2. LeakCanary 在转储堆时显示提示信息。

3. 分析堆

LeakCanary 使用 Shark 解析 .hprof 文件,并在堆转储中找到保留的对象。

LeakCanary 在堆转储中查找保留对象

图 3. LeakCanary 在堆转储中查找保留对象。

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

LeakCanary 计算每个保留对象的泄漏轨迹

图 4. LeakCanary 计算每个保留对象的泄漏轨迹。

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

4 条泄漏痕迹变成了 2 个不同的泄漏特征

图 5. 4 条泄漏痕迹变成了 2 个不同的泄漏特征。

language 复制代码
====================================
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 启动器图标返回:

LeakCanary 为每个安装 LeakCanary 的应用添加一个启动器图标

图 6. LeakCanary 为每个安装 LeakCanary 的应用添加一个启动器图标。

每行对应一组具有相同签名的泄漏。当应用首次触发具有该签名的泄漏时,LeakCanary 会将相应行标记为“New”。

4 个泄漏分组为两行,每行代表一个不同的泄漏特征

图 7. 4 个泄漏分组为两行,每行代表一个不同的泄漏特征。

点击泄漏即可打开包含泄漏轨迹的屏幕。您可以通过下拉菜单在保留的对象及其泄漏轨迹之间切换。

屏幕显示了 3 个泄漏,并按其共同的泄漏签名进行分组

图 8. 屏幕显示了 3 个泄漏,并按其共同的泄漏签名进行分组。

泄漏签名是每个疑似导致泄漏的引用的串联哈希值,即每个引用都带有红色下划线:

泄漏追踪包含 3 个可疑引用

图 9. 泄漏追踪包含 3 个可疑引用。

当泄漏追踪以文本形式共享时,这些相同的可疑引用会带有 ~~~ 下划线:

language 复制代码
...
│  
├─ 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)
...

在上面的例子中,泄漏的签名将计算如下:

language 复制代码
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

除非适用法律要求或双方书面同意,否则,根据本许可证分发的软件均按“原样”分发,不附带任何明示或暗示的保证或条件。请参阅许可证,了解本许可证下特定语言的权限和限制。

关于项目

leakcanary是一个Android的内存泄漏检测库,它对 Android 框架内部的了解使其具有独特的能力来缩小每次泄漏的原因,帮助开发人员大幅减少卡顿、应用程序无响应冻结和 OutOfMemoryError 崩溃。
Apache-2.0
Kotlin
29,838
3978
974
2015-04-30
2025-08-05

增长趋势 - stars