4. LeakCanary 核心触发时机
LeakCanary 的自动检测并非持续扫描堆内存,而是牢牢绑定在 Android 组件的生命周期上。理解“触发时机”是理解 LeakCanary 工作链条的关键起点——它在什么时刻、由什么事件唤醒、按什么顺序执行三个核心步骤,直接决定了检测的准确性和性能开销。
4.1 概念解释:三段式触发链
LeakCanary 的触发链路由三个紧密衔接的阶段组成:
| 阶段 | 触发事件 | 核心动作 | 执行时机 |
|---|---|---|---|
| ① 监听点 | Activity.onDestroy() / Fragment.onDestroy() |
向 ObjectWatcher 注册待观察引用 | 组件被销毁时立即执行 |
| ② 注册 | ObjectWatcher.watch() 被调用 | 创建弱引用 + 引用队列,存储到监控集合 | 同步、无阻塞 |
| ③ 检测 | 延迟 5 秒(默认)后自动触发 GC | 执行 GC → 遍历弱引用队列 → 比对存活对象 | 异步,主线程空闲时执行 |
💡 理解关键:三段式触发确保了“不打扰正常生命周期”和“给 GC 留出足够时间”。LeakCanary 不会在 onDestroy() 调用栈中立即 GC,而是返回主线程循环后,通过 Handler 延迟执行。
4.2 原理图解:触发时序流程图
4.3 类比:邮轮码头安检系统
- Activity.onDestroy() —— 邮轮离港广播:“所有非必需乘客请下船”。
- ObjectWatcher.watch() —— 安检员在出口处给每位下船乘客发放一枚“可回收标签”(弱引用),并记录其姓名(引用队列)。
- 延迟 5 秒 + GC —— 安检员等待 5 秒,让乘客完全走出码头,然后吹哨(触发 GC)命令所有人举起标签。能举起的说明还没走远(可回收),举不起来的说明已经混入下一班船(已回收)。
- 检查引用队列 —— 名册上标记“已出港”的为安全;名册上仍存在且举不起标签的,说明该乘客实际还滞留在码头调度室(内存泄漏)。
🔍 应用边界提示:此类比中“延迟 5 秒”是为了等待 GC 保守周期。实际 Android 上,System.gc() 并不保证立即触发,LeakCanary 会尝试最多 5 次 GC 并等待弱引用入队。
4.4 常见误区
- 误区:LeakCanary 在 onDestroy 后立刻进行 GC 检测。
事实:它使用Handler.postDelayed延迟默认 5 秒,给 GC 和主线程空闲时间。过早 GC 会导致误判(对象尚在短暂存活期)。 - 误区:所有未被 GC 回收的对象都算泄漏。
事实:LeakCanary 只对 watched reference(通过 ObjectWatcher 注册的引用)进行检测。未注册的对象即使泄漏也不会被上报。 - 误区:延迟时间越长越准确。
事实:延迟过长会降低用户体验(泄漏提示过晚),过短会增加误报。LeakCanary 默认 5 秒是经验平衡值,可通过watchDurationMillis自定义。 - 误区:Fragment 的生命周期与 Activity 完全同步。
事实:LeakCanary 分别监听Fragment.onDestroy()和Activity.onDestroy(),且 Fragment 可能先于 Activity 销毁,需要独立的 ObjectWatcher 实例。
4.5 练习题(自测)
📝 选择题
- LeakCanary 在 Activity.onDestroy() 被调用后,默认等待多长时间才触发 GC 检测?
A. 立即触发 B. 1 秒 C. 5 秒 D. 10 秒 - ObjectWatcher 内部通过什么机制跟踪已销毁 Activity 的引用?
A. 强引用 + HashSet B. 软引用 + 引用队列 C. 弱引用 + 引用队列 D. 虚引用 + 全局 Map - 以下哪种情况不会触发 LeakCanary 的 ObjectWatcher.watch()?
A. Activity 执行 onDestroy() B. Fragment 执行 onDestroy() C. Service 执行 onDestroy() D. 用户手动调用 LeakCanary.watch()
✅ 答案:1-C,2-C,3-C(LeakCanary 默认不监听 Service,需手动集成)。
4.6 应用边界
- 适用场景:Activity、Fragment、Dialog、View(通过
AndroidExcludedRefs可扩展)。 - 不适用场景:Service、BroadcastReceiver、ContentProvider(需手动调用
ObjectWatcher.watch()并传入自定义引用名称)。 - 延迟 GC 的限制:在 Android 低版本(API < 19)或某些国产 ROM 上,
System.gc()可能被忽略,LeakCanary 会通过Runtime.getRuntime().gc()并且最多重试 5 次,但仍有极低概率无法触发真正的 GC。 - 性能边界:如果应用在短时间内频繁创建并销毁大量 Activity(如快速翻页),ObjectWatcher 集合中会积压大量待观察引用,导致一次检测循环处理数千个引用。LeakCanary 通过阈值(默认 200)和“只保留最新 X 个”策略缓解,但极端情况下仍可能影响主线程。