LeakCanary — 是 Square 开发的一款开源库,用于自动检测 Android 应用中的内存泄漏。它集成到开发过程中,实时跟踪 Activity、Fragment、ViewModel 及其他组件的生命周期,在泄漏发生后立即发出信号。根据 Square Open Source 的数据,该库被用于数千个项目,被认为是 Android 内存诊断的事实标准。
要点
LeakCanary — 是由 Square 公司开发的用于自动检测 Android 应用中内存泄漏的库。它嵌入到应用程序的构建过程中,自动跟踪应被销毁的对象(Activity、Fragment、View)是否仍留在内存中。检测到泄漏时,LeakCanary 会创建堆转储并分析持有该对象的引用链。
该库已成为 Android 社区的标准:根据 GitHub 数据,该项目已获得超过 28,000 颗星,并被 Google、Uber、Airbnb 和 Facebook 的应用所使用。LeakCanary 有两个主要版本:经典版 1.x(手动配置)和现代版 2.x(通过 ContentProvider 自动集成)。版本 2.x 无需修改 Application 类 — 一个依赖就足以实现完整功能。
LeakCanary 的主要任务是检测对象在其生命周期结束后仍存在于内存中的情况。这通常是通过静态字段、单例、未注册的回调、匿名类和捕获外部对象的闭包导致的泄漏。
由于移动设备上的 RAM 容量有限,Android 中的内存泄漏比桌面端更为严重。每次屏幕切换时即使只有 5–10 MB 的泄漏,也可能在应用程序使用 30–40 分钟后导致 OutOfMemoryError。LeakCanary 在开发阶段就能发现此类问题,无需等待生产环境崩溃。
LeakCanary 使用弱引用(WeakReference)结合强制调用垃圾回收器。当 Activity 或 Fragment 调用 onDestroy 时,LeakCanary 为此对象创建一个 WeakReference,并在短暂延迟后(默认为 5 秒)触发 GC。如果 GC 之后该对象仍可通过 WeakReference 访问,则说明它被强引用持有 — 记录为泄漏。
检测到泄漏后,LeakCanary 会进行堆转储(以 HPROF 格式保存的应用程序内存完整快照)。然后内置分析器(对于 2.x 版本是 Shark)从 GC Roots 到泄露对象构建可达性图,并找到最短路径 — 在内存中持有该对象的引用链。
// 简化的 LeakCanary 检测逻辑
class ObjectWatcher {
private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()
fun watch(watchedObject: Any, description: String) {
val reference = KeyedWeakReference(watchedObject, description)
watchedReferences.add(reference)
BackgroundHandler.postDelayed({
checkForLeaks()
}, 5000)
}
private fun checkForLeaks() {
GcTrigger.runGc() // 强制 GC
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // 对象在 GC 后存活 — 这是泄漏
}
}
}
}
关键点在于强制调用 GcTrigger.runGc()。没有它就不可能区分真正泄漏的对象和 GC 尚未收集的对象。LeakCanary 最多执行三次:如果经过三次 GC 循环后对象仍留在内存中 — 泄漏被确认。
Shark — 是 LeakCanary 2.x 内置的堆转储分析器,使用 Kotlin 编写。与之前的 HAHA 分析器不同,Shark 不会将整个 HPROF 文件加载到内存中,而是以最小的分配量遍历其对象图。这将分析期间的 RAM 消耗从 50 MB 降低到 2–5 MB,并将分析时间从 30 秒缩短到 1–3 秒。
在现代 Android 项目中安装 LeakCanary 2.x 只需在 build.gradle 中添加一行。该库使用 ContentProvider 进行自动初始化 — 无需修改 Application 类或在 MainActivity 中添加代码。连接仅针对 debug-build 进行,以确保发布版 APK 中没有多余代码。
// build.gradle (app/module)
dependencies {
// debugImplementation — 仅用于 debug-build 的库
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
添加依赖并重建项目后,LeakCanary 会自动出现在应用中。首次启动时,该库会显示系统激活通知。所有检测到的泄漏都以通知形式显示 — 点击通知将打开包含详细报告(LeakTrace)的页面。
为进行自定义,您可以创建自己的 AppWatcherInstaller 并覆盖参数:GC 等待超时、跟踪的对象类型列表、启用将堆转储保存到磁盘。然而,对于 90% 的项目,默认配置已经是最优的。
从 2.12 版本开始,LeakCanary 支持自动跟踪 ViewModel、协程作用域和 Compose State 对象。无需额外依赖 — 该库会自动检测项目中使用了哪些 Jetpack 组件,并激活相应的检测器。
LeakCanary 报告(LeakTrace) — 是从 GC Root 到泄露对象的多行引用链。每一行显示强引用通过的类和字段。开发人员应从下往上阅读该链:最下方一行 — 泄露对象,最上方一行 — 入口点(GC Root)。
典型的 LeakTrace 如下所示:GC Root → Application 静态字段 → 单例 → 回调 → Activity。如果开发人员看到这样的链,问题就很清楚:单例持有捕获了对 Activity 引用的回调。解决方案 — 在单例中将强引用替换为弱引用。
┬
├─ android.app.Application
│ Leaking: NO (Application — singleton)
│ ↓ Application.leakedActivities
├─ java.util.ArrayList
│ Leaking: NO (ArrayList — normal)
│ ↓ ArrayList[0]
├─ com.example.MainActivity
│ Leaking: YES (Activity destroyed but still in memory)
│ ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│ Leaking: UNKNOWN
│ ↓ CallbackWrapper.mListener
│ ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│ Leaking: UNKNOWN
│ ↓ MyCallback.this$0
├─ com.example.MainActivity
│ Leaking: YES (MainActivity is the leak)
╰
在此示例中,LeakCanary 显示 MainActivity 通过以下链持有:Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → 返回 MainActivity。this$0 箭头表示匿名类 MyCallback 捕获了外部对 Activity 的引用。解决方案 — 将回调设为弱引用或在 onDestroy 中取消它。
LeakCanary 还会为链中的每个元素显示泄漏状态:NO(无泄漏 — 这是根元素)、YES(对象应被销毁)、UNKNOWN(无法确定状态)。UNKNOWN 状态并不意味着问题 — 这是一个 LeakCanary 无法明确分类的中间对象。
从 1.x 版本到 2.x 版本的过渡是彻底的:开发人员从头重写了该库,用 Kotlin 编写的自有引擎 Shark 取代了过时的 HAHA 分析器。Shark 运行速度更快,分析所需内存更少,并能更准确地确定泄漏的根本原因。
| 参数 | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| 分析器语言 | Java(HAHA — Android SDK 的分支) | Kotlin(Shark — 自有引擎) |
| 安装 | 在 Application 中手动配置 AppWatcher | 通过 ContentProvider 自动 |
| 速度 | 分析堆转储需要 10–30 秒 | 分析堆转储需要 1–5 秒 |
| 性能 | 分析时占用 10–50 MB RAM | 分析时占用 2–10 MB RAM |
Shark 的主要优势在于 — 它不会将整个堆转储加载到内存,而是以最小的分配量遍历其引用图。这使得 LeakCanary 2.x 适用于 RAM 较低的设备,在分析期间没有 OutOfMemoryError 的风险。
在 2.x 版本中还增加了将堆转储导出到文件以便在 Android Studio Memory Profiler 中进行后续分析的功能。为此,需要在 AppWatcher 配置中启用 dumpHeapWhenLeakFound 设置。
LeakCanary 能有效检测 Android 特有的几类泄漏。最常见的是通过静态引用指向 Activity 的泄漏 — 开发人员将 Activity 上下文的引用存储在单例中,导致 Activity 在其生命周期结束后无法被 GC 回收。
第二常见类别 — 通过未注册的监听器造成的泄漏。如果在 onStart 中调用了 registerListener,但在 onStop/onDestroy 中未调用 unregisterListener — 即使活动被销毁,监听器对象仍被系统持有。LeakCanary 清楚地显示哪个监听器以及在哪个系统服务中保持活动状态。
// 典型泄漏:Activity 被单例的回调捕获
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // 对回调的强引用
}
fun unregister() {
callback = null // 别忘了在 onDestroy 中调用!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda 捕获了 this
}
// 如果在 onDestroy 中未调用 unregister → Activity 泄漏
}
}
第三类别 — 通过 BackStack 中的 Fragment 造成的泄漏。如果在返回时未删除 Fragment 的情况下调用 FragmentTransaction.addToBackStack(),旧的 Fragment 实例会留在内存中。LeakCanary 帮助在早期开发阶段发现此类隐藏泄漏。
对于每个检测到的泄漏,LeakCanary 都会提供描述和修复建议。在 2.14 版本中添加了与 Android Lint 的集成 — 该库可以在 CI 中检测到泄漏时自动在 issue tracker 中创建任务。
常见问题
是的,必须移除。 LeakCanary 通过 build.gradle 中的 debugImplementation 连接,这会自动将其排除在发布版构建之外。如果通过 implementation 连接,该库会进入发布版 APK 并向最终用户显示泄漏 — 这是不允许的。
对性能的影响很小。LeakCanary 仅在组件执行 onDestroy 后激活,不会干扰 UI 渲染或触摸处理。唯一的代价 — 强制 GC 的短暂暂停(约 100 毫秒)和泄漏时的堆转储写入(百分之一秒)。
LeakCanary 自动以 HPROF 格式将堆转储保存在应用程序文件夹中。该文件可以通过 Android Studio 导出:Device File Explorer → data/data/com.example/files/leakcanary/。如需查看,请通过 Capture → Open Heap Dump 在 Memory Profiler 中打开该文件。
是的,从 2.12 版本开始,LeakCanary 完全支持 Jetpack Compose。该库会跟踪 Composition 上下文和 State 对象,自动检测 Composable 函数中的泄漏。无需单独配置 — 开箱即用。
误报是可能的,但很少见。LeakCanary 在声明泄漏之前使用三次 GC 调用,这消除了大多数误报。如果您认为某次触发是误报 — 请在配置中为特定类创建一个 IgnoredReference。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。