3. 首次触发泄漏:我写个最简单的Activity泄漏,看LeakCanary怎么报出来
说到定位 BLE 泄漏之前,咱先得把 LeakCanary 玩明白。我个人习惯,不拿一个活生生的泄漏样本去喂它,你心里就没底。所以今天先手搓一个最简单的 Activity 泄漏,看看 LeakCanary 会怎么吼出来。
3.1 为什么非得主动造一个泄漏?
你想想看,要是项目里本来就有泄漏,但你压根没留意过 LeakCanary 的报错长啥样,等真出问题时你会慌的。我也一样,刚开始接触 LeakCanary 时,找了好久才找到一个小 demo 来触发。后来我发现——其实用静态变量挂住 Activity 就能秒出效果。嗯,这里要记住,静态变量是泄漏的万恶之源。
关键认知:LeakCanary 的核心是监听 Activity 的 onDestroy 调用,如果过了默认 5 秒后该 Activity 实例仍然被 GC Root 强引用持有,就判定为泄漏。
3.2 我们写一个泄漏的 Activity
新建一个非常简单的 SingleLeakActivity,啥 UI 都不要,就放一个静态变量来 hold 住自己。
class SingleLeakActivity : AppCompatActivity() {
companion object {
// 罪魁祸首:静态引用持有 Activity
var leakedRef: Activity? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_empty)
// 让静态变量指向自己,造成泄漏
leakedRef = this
}
}
就两行关键代码。你在真实项目里要是看到有人这么写,赶紧喷。我一个同事曾经在工具类里放了 Context 的静态引用,结果 OOM 查了半天。这次咱们拿这个 demo 来干正事。
3.3 集成 LeakCanary 并开始监测
在 app 模块的 build.gradle 里加上依赖:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
不需要额外初始化,LeakCanary 2.x 自带 ContentProvider 自动启动。你装好 apk 后,它就在后台默默干活了。
3.4 动手触发泄漏
打开 SingleLeakActivity,然后按返回键退出。按理说 Activity 应该被回收,但因为静态变量 leakedRef 还强引用着它,它活得好好的。LeakCanary 在 5 秒后发现了——好家伙,你竟然没死?
接着你会在通知栏看到一条红色的 LeakCanary 通知,点进去就进入 LeakCanary 的 dump 页面。以下是它报出来的泄漏路径示例:
─── LeakCanary ───
1. static SingleLeakActivity.leakedRef
└── SingleLeakActivity instance
└── Activity 被 GC Root (static) 持有
结论:泄漏 100% 确认
我额外把真实 dump 的 GC 根链用表格拆开,方便你看明白是谁连到了谁:
| GC Root 类型 | 对象/引用 | 说明 |
|---|---|---|
| 静态字段 | SingleLeakActivity.leakedRef | companion object 持有的强引用 |
| 被引用的对象 | SingleLeakActivity 实例 | Activity 本身没有被释放 |
| 最终问题 | Activity 本该在 onDestroy 后回收 | 但静态引用阻止了 GC |
经验之谈:LeakCanary 的 dump 报告里会高亮“泄漏对象”和“GC Root”。你只要记住一点——顺着 GC Root 往下看,哪个对象不该活却活着,那就是问题点。
3.5 整个泄漏检测流程
我画了一张流程示意图,把从 Activity 销毁到通知弹出捋了一遍。你对照着代码看,思路更清晰。
3.6 LeakCanary 通知里能看到什么?
点开通知后,你会进到一个专门的分析页面。上面会显示:
- 泄漏对象:
SingleLeakActivity - GC Root 链:从静态字段开始,一路引到 Activity
- 引用计数:显示有多少个实例泄漏(这里就一个)
- 直接原因:
leakedRef是静态变量,永远不会被 gc
曾经踩过的坑:有一次我发现 LeakCanary 没弹通知,以为它坏了。后来才发现是 debug 构建没打对,release 包不会集成。记住,LeakCanary 只在 debugImplementation 下生效,正式包千万别带上去。
3.7 小结一下这个流程
说白了,你只需要做三件事:
- 写一个带静态引用的泄漏 Activity
- 集成 LeakCanary 依赖
- 打开、关闭 Activity,等着看通知
LeakCanary 替你做的三件事:监听 → GC 检测 → dump + 报告。
动手建议:你现在就用 AS 新建一个 demo 工程,照着上面的代码跑一遍。五分钟就能看到第一张 LeakCanary 的报告,以后再遇到真泄漏就不慌了。