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 原理图解:触发时序流程图

LeakCanary 触发时序(Activity 示例) Activity.onDestroy() ObjectWatcher.watch() 弱引用 引用队列 延迟 5s(默认) 主动触发 GC 检查引用队列+判断泄漏 在队列中 → 可回收 未入队 → 泄漏

4.3 类比:邮轮码头安检系统

  • Activity.onDestroy() —— 邮轮离港广播:“所有非必需乘客请下船”。
  • ObjectWatcher.watch() —— 安检员在出口处给每位下船乘客发放一枚“可回收标签”(弱引用),并记录其姓名(引用队列)。
  • 延迟 5 秒 + GC —— 安检员等待 5 秒,让乘客完全走出码头,然后吹哨(触发 GC)命令所有人举起标签。能举起的说明还没走远(可回收),举不起来的说明已经混入下一班船(已回收)。
  • 检查引用队列 —— 名册上标记“已出港”的为安全;名册上仍存在且举不起标签的,说明该乘客实际还滞留在码头调度室(内存泄漏)。

🔍 应用边界提示:此类比中“延迟 5 秒”是为了等待 GC 保守周期。实际 Android 上,System.gc() 并不保证立即触发,LeakCanary 会尝试最多 5 次 GC 并等待弱引用入队。

4.4 常见误区

  1. 误区:LeakCanary 在 onDestroy 后立刻进行 GC 检测。
    事实:它使用 Handler.postDelayed 延迟默认 5 秒,给 GC 和主线程空闲时间。过早 GC 会导致误判(对象尚在短暂存活期)。
  2. 误区:所有未被 GC 回收的对象都算泄漏。
    事实:LeakCanary 只对 watched reference(通过 ObjectWatcher 注册的引用)进行检测。未注册的对象即使泄漏也不会被上报。
  3. 误区:延迟时间越长越准确。
    事实:延迟过长会降低用户体验(泄漏提示过晚),过短会增加误报。LeakCanary 默认 5 秒是经验平衡值,可通过 watchDurationMillis 自定义。
  4. 误区:Fragment 的生命周期与 Activity 完全同步。
    事实:LeakCanary 分别监听 Fragment.onDestroy() 和 Activity.onDestroy(),且 Fragment 可能先于 Activity 销毁,需要独立的 ObjectWatcher 实例。

4.5 练习题(自测)

📝 选择题

  1. LeakCanary 在 Activity.onDestroy() 被调用后,默认等待多长时间才触发 GC 检测?
    A. 立即触发   B. 1 秒   C. 5 秒   D. 10 秒
  2. ObjectWatcher 内部通过什么机制跟踪已销毁 Activity 的引用?
    A. 强引用 + HashSet   B. 软引用 + 引用队列   C. 弱引用 + 引用队列   D. 虚引用 + 全局 Map
  3. 以下哪种情况不会触发 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 个”策略缓解,但极端情况下仍可能影响主线程。

4.7 小结