第一章 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.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 对象的泄漏链——这是蓝牙开发中一个非常典型又隐蔽的问题。