LeakCanary原理示例讲解
咱们用一个例子来具体走一遍 LeakCanary 的检测原理。
场景设定
ActivityA → 启动 → ActivityB。
在
ActivityB
里写了一个模拟泄漏:class ActivityB : AppCompatActivity() {companion object {var leaked: ActivityB? = null}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)leaked = this // ❌ 静态持有,制造泄漏} }
用户在 ActivityB 页面点返回 → 回到 ActivityA。
1. 生命周期触发监控
当
ActivityB
调用onDestroy()
时,LeakCanary 内部的ActivityWatcher
会收到回调。它调用:
objectWatcher.expectWeaklyReachable(activityB, "Activity destroyed")
这一步:
ActivityB
被放进一个 WeakReference。并关联到一个 ReferenceQueue,等待 GC 回收。
2. 延时 + GC 检测
LeakCanary 内部有个定时任务,等 5 秒。
期间触发一次 GC:
如果
ActivityB
只被弱引用持有 → 应该被回收 → WeakReference 会进队列。但这里我们写了
companion object leaked = this
→ 强引用还在。
结果:ActivityB
没有被回收,WeakReference
仍指向它。
3. 判定为疑似泄漏
LeakCanary 检查 ReferenceQueue 发现
ActivityB
还活着 → 标记为 “suspected leak”。接下来触发 堆快照 dump:
Debug.dumpHprofData(filePath)
HPROF 文件保存了整个进程的对象关系。
4. Shark 分析 HPROF
LeakCanary 启动 HeapAnalyzerService(单独进程)用 Shark 来分析:
找到所有 GC Roots(静态变量、JNI 引用、线程栈等)。
从 GC Root 出发,看看是谁引用着
ActivityB
。算出最短引用链。
在这个例子里,Shark 会发现:
GC Root: System class
↓
com.example.ActivityB.Companion (static field)
↓
com.example.ActivityB (leaked instance)
5. 报告展示
最终报告大概是:
┬───
│ GC Root: static field com.example.ActivityB.Companion.leaked
│
├─ com.example.ActivityB instance
│ Leaking: YES (Activity#mDestroyed = true)
│ Retained by: static reference
└────────────────────────────
你点开通知就能看到这个引用链 → 清楚地说明了是 Companion
的静态引用导致 ActivityB 泄漏。
6. 总结这个例子流程
ActivityB onDestroy → 被 WeakReference 监控。
GC 后没回收 → 怀疑泄漏。
Dump HPROF → 分析对象关系。
Shark 找到静态引用链 → 报告给你。