第一章 LeakCanary 简介:内存泄漏的侦察兵

做 Android 开发的,谁还没被内存泄漏坑过呢?我记得刚入行那会儿,一个 Activity 泄漏导致 OOM 崩溃,查了两天才找到根源——一个内部类持了外部引用。那时候我就想,要是有个工具能自动帮我揪出这些泄漏点就好了。后来,LeakCanary 出现了。

说白了,LeakCanary 就是一个自动化内存泄漏检测工具。它能在你的 App 运行时自动监测对象是否泄漏,一旦发现,立刻抓取堆快照、分析引用链,并把泄漏路径清晰地展示给你。你想想看,这比手动抓取 heap dump 再用 MAT 分析,效率高了多少倍?

发展历史:从内部工具到行业标配

  • 2015 年:Square 公司开源了 LeakCanary 1.0。当时它只是监控 Activity 的 onDestroy(),用弱引用判断是否泄漏。
  • 2018 年:2.0 版本发布。引入 AppWatcher 机制,支持监控任意对象(Fragment、ViewModel、Retrofit 等)。
  • 2020 年:2.7+ 版本开始支持 HPROF 代理分析,不再依赖单独的 leakcanary-process 进程,堆分析速度提升明显。
  • 现在:LeakCanary 已成为 Android 内存优化的标配工具。Google 官方也推荐将其集成到测试环境。

我经历了一个特别有意思的版本升级。早期 1.x 版本在使用时,每次泄漏都会弹出 toast,导致测试同事总抱怨被打断。2.0 之后改成通知栏静默通知,用户体验好多了。你看,工具的演进其实就是解决我们开发者真实痛点的过程。

核心工作原理:四步走

LeakCanary 的工作流程,我习惯分成四个阶段:监控、检测、分析、报告。

阶段 关键行为 说明
监控 注册生命周期回调 自动监听 Activity、Fragment 等对象的销毁事件
检测 弱引用 + GC 触发 对象销毁后,用弱引用包裹,主动触发一次 GC,检查是否被回收
分析 堆转储 + 引用链解析 如果弱引用未被回收,生成 HPROF 文件,解析出泄漏引用链
报告 通知栏 + Logcat 将泄漏路径以可视化的方式展示给开发者

下面这张图可以更直观地看到整个流程的节点关系:

LeakCanary 工作流程 1. 注册生命周期回调 监听 Activity/Fragment 销毁 监控 2. 弱引用包裹 + 触发 GC 判断弱引用是否回收 已回收 未回收 无泄漏,结束 检测 3. 生成 HPROF 堆快照 解析泄漏引用链 输出 GC Root 路径 分析 4. 通知栏展示 注意 LeakCanary 只在 Debug 包中启用 Release 包无性能影响

每一阶段是怎么做到的?

监控阶段:LeakCanary 1.x 时代用 Application.registerActivityLifecycleCallbacks 监听 Activity 的 onDestroy。2.0 之后抽象出 AppWatcher,你还可以手动监控任意对象:

AppWatcher.INSTANCE.watch(myObject, "自定义描述");

我个人习惯在集成时,除了默认的 Activity 和 Fragment,还会把一些单例中引用的 View 也纳入监控。比如 DialogFragment 中用到了某个自定义 View,但 Dialog 销毁时没有及时置空引用,LeakCanary 很容易抓到。

检测阶段:对象销毁后,LeakCanary 会用弱引用包裹它,然后主动调用 Runtime.getRuntime().gc()。为什么要主动触发?因为 Android 系统 GC 时机不可控,你不主动触发,可能等几分钟才回收,泄漏检测就慢了。另外,注意这里有个「等待机制」——它会等 5 秒,还是未回收,就判定为可能泄漏。

一个小技巧:有时你手动调用 gc() 并不一定能立即回收,因为 Dalvik / ART 的 GC 是分代的。LeakCanary 会反复尝试 5 次,每次间隔 100ms。这个细节我曾踩过坑——项目里改了 GC 触发次数导致检测变慢,后来翻源码才发现的。

分析阶段:一旦确认对象「疑似泄漏」,LeakCanary 会把当前堆转储成 HPROF 文件,然后用自带的 parser 解析。这个过程是异步的,不会卡 UI。解析的核心是找出从 GC Root 到该对象的强引用路径。说白了,就是画一条「为什么这个对象没被回收」的追踪线。

报告阶段:分析完成后,你会收到一条通知。点击通知就能看到泄漏链——从你关注的对象开始,一路追溯到某个静态变量、线程或者 Application 对象。我建议每次看到这样的链,先看最后一个节点,通常就是泄露的「源头」。

LeakCanary 的版本选择建议

目前稳定版是 2.13+。如果你是新建项目,直接上 2.x 系列。如果还在用 1.x,迁移也很简单:

  • 移除旧的 LeakCanary.install(application),改为添加依赖。
  • 自定义配置通过 LeakCanary.config 设定。
  • 通知样式更友好,支持品牌自定义。

我曾经遇到的一个坑

有一次在某个国产手机上,LeakCanary 的分析结果一直显示「泄漏」,但引用链指向了一个系统 Service。我折腾半天,最后发现是那款 ROM 对 Activity 的 finishing 状态判断有延迟。解决方案很简单:增大等待超时时间。这也说明,工具不是万能的,需要我们结合对具体设备的理解去判断。

小结

LeakCanary 的设计哲学很简单:自动发现泄漏,并告诉我们泄漏的路径。它不修复泄漏,但它把最难的部分——定位根源——变得像点开通知一样容易。掌握了这个工具,你就能从「靠感觉猜泄漏」进阶到「靠证据定位泄漏」。嗯,接下来我们会深入看看,如何用 LeakCanary 专门分析 GATT 对象的泄漏链——这是蓝牙开发中一个非常典型又隐蔽的问题。