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.leakedRefcompanion object 持有的强引用
被引用的对象SingleLeakActivity 实例Activity 本身没有被释放
最终问题Activity 本该在 onDestroy 后回收但静态引用阻止了 GC

经验之谈:LeakCanary 的 dump 报告里会高亮“泄漏对象”和“GC Root”。你只要记住一点——顺着 GC Root 往下看,哪个对象不该活却活着,那就是问题点。

3.5 整个泄漏检测流程

我画了一张流程示意图,把从 Activity 销毁到通知弹出捋了一遍。你对照着代码看,思路更清晰。

LeakCanary 泄漏检测流程 Activity onDestroy 等待5秒(可配置) WeakReference 注册 手动触发 GC 对象是否被回收? 安全,无泄漏 发生泄漏! Dump + 通知 Tips: 对象未回收 → 触发 Heap Dump → 通知用户

3.6 LeakCanary 通知里能看到什么?

点开通知后,你会进到一个专门的分析页面。上面会显示:

  • 泄漏对象:SingleLeakActivity
  • GC Root 链:从静态字段开始,一路引到 Activity
  • 引用计数:显示有多少个实例泄漏(这里就一个)
  • 直接原因:leakedRef 是静态变量,永远不会被 gc

曾经踩过的坑:有一次我发现 LeakCanary 没弹通知,以为它坏了。后来才发现是 debug 构建没打对,release 包不会集成。记住,LeakCanary 只在 debugImplementation 下生效,正式包千万别带上去。

3.7 小结一下这个流程

说白了,你只需要做三件事:

  1. 写一个带静态引用的泄漏 Activity
  2. 集成 LeakCanary 依赖
  3. 打开、关闭 Activity,等着看通知

LeakCanary 替你做的三件事:监听 → GC 检测 → dump + 报告。

动手建议:你现在就用 AS 新建一个 demo 工程,照着上面的代码跑一遍。五分钟就能看到第一张 LeakCanary 的报告,以后再遇到真泄漏就不慌了。