第4章  Activity 泄漏检测:默认监控、自定义泄漏阈值、忽略特定 Activity

4.1  原理:Activity 泄漏检测的核心机制

LeakCanary 对 Activity 的泄漏检测建立在 Android Activity 生命周期回调 之上。当 Activity 调用 onDestroy() 后,LeakCanary 会为该 Activity 创建一个弱引用(WeakReference),并关联到一个后台 ReferenceQueue。如果 Activity 在预定的等待时间内没有被垃圾回收器回收,则判定为可能泄漏,并触发 heap dump 进行分析。

整个检测流程分为四个阶段:注册、生命周期监听、弱引用入队检查、阈值超时判定。默认情况下,LeakCanary 自动注册所有 Activity 的 onDestroy 回调,无需开发者手动调用。

核心理解: 泄漏检测不是实时发生的,而是依赖 弱引用 + 延迟检查 的组合。默认等待 5 秒(5 秒内若 GC 未回收则触发 dump),这个等待时间就是“泄漏阈值”。

4.2  默认监控:零配置自动接入

在 Kotlin 工程中通过 LeakCanary 初始化后,默认即开启对所有 Activity 的监控。以下是最简配置示例:

class DebugExampleApp : Application() {
    override fun onCreate() {
        super.onCreate()
        LeakCanary.config = LeakCanary.config.copy(
            // 保持默认即可自动监控所有 Activity
        )
    }
}

默认监控的完整行为清单如下表:

行为维度默认值说明
监控范围所有 Activity通过 Application.ActivityLifecycleCallbacks 自动注册
泄漏阈值5 秒Activity 销毁后等待 5 秒,若弱引用未入队则触发 dump
GC 策略自动触发一次 GC在阈值超时前执行一次 System.gc() 以排除误判
忽略规则无默认监控全部 Activity,不跳过任何类

4.3  自定义泄漏阈值:调整检测灵敏度

不同场景下默认的 5 秒阈值可能过长或过短。例如,在某些低端设备上 GC 耗时较大,5 秒可能频繁误报;而在高频 Activity 切换的场景下,5 秒又可能导致检测延迟。通过修改 LeakCanary.config 的 watchDurationMillis 属性可以自定义阈值。

LeakCanary.config = LeakCanary.config.copy(
    // 将泄漏阈值调整为 10 秒,减少低频设备上的误报
    watchDurationMillis = 10_000L
)

阈值调整需遵循以下影响矩阵:

阈值设置优点缺点适用场景
3 秒检测更及时低端设备易误报高端设备 / 严格测试
5 秒(默认)平衡误报与时效极低端设备仍有误报通用开发调试
10 秒显著减少误报泄漏发现延迟低端设备 / 线上监控
15 秒以上几乎无误报泄漏后等待过长发布版灰度监控
注意: 不要为了消除误报而把阈值设得过大(如 > 30 秒),否则泄漏 Activity 会长时间滞留内存,可能引发 OOM 或卡顿。阈值只是判定等待时间,不是泄漏解决手段。

4.4  忽略特定 Activity:精确控制监控范围

工程中总有一些 Activity 在销毁后无法立即被 GC 回收(例如被系统缓存、被第三方 SDK 持有引用)。此时需要显式忽略这些 Activity,避免无效报警。LeakCanary 支持两种忽略方式。

4.4.1  通过注解忽略

在目标 Activity 上添加 @IgnoreForLeakCanary 注解:

import leakcanary.IgnoreForLeakCanary

@IgnoreForLeakCanary
class SplashActivity : AppCompatActivity() {
    // 该类将完全不被 LeakCanary 监控
}

4.4.2  通过配置文件忽略

在 LeakCanary.config 中设置 ignoredClasses 列表,支持类名前缀匹配:

LeakCanary.config = LeakCanary.config.copy(
    ignoredClasses = setOf(
        "com.example.legacy.LegacyActivity",
        "com.example.cache."  // 忽略 cache 包下所有 Activity
    )
)

两种忽略方式的对比:

方式范围控制粒度修改成本推荐场景
@IgnoreForLeakCanary单类精准忽略需修改源码明确已知的特殊 Activity
ignoredClasses包级/前缀批量忽略仅改配置第三方 SDK 或遗留模块

4.5  调试与验证:确认忽略与阈值生效

配置完成后,通过 Logcat 过滤 LeakCanary 标签验证行为:

// Logcat 输出示例 —— 阈值生效
D/LeakCanary: Watching Activity com.example.MainActivity (lastDestroyedActivity)
    // 等待 10 秒后…
D/LeakCanary: Activity com.example.MainActivity has been retained.
    // 说明阈值 10 秒已生效

// Logcat 输出示例 —— 忽略生效
D/LeakCanary: Ignoring class com.example.legacy.LegacyActivity
    // 该 Activity 不会被监控

你也可以手动触发一次泄漏验证:

// 在测试 Activity 中模拟泄漏
class LeakTestActivity : AppCompatActivity() {
    companion object {
        lateinit var leakHolder: LeakTestActivity
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 将当前实例赋给静态变量,阻止 GC 回收
        leakHolder = this
    }

    override fun onDestroy() {
        super.onDestroy()
        // 即使 onDestroy 调用,leakHolder 仍持有引用,LeakCanary 会检测到泄漏
    }
}
调试建议: 使用 LeakCanary.showLeakDisplayActivityLauncher 或 LeakCanary.dumpHeapAndLaunchResultActivity() 在非 UI 环境下手动触发 dump 并查看结果,便于集成到自动化测试用例中。

4.6  部署与集成检查清单

  1. 确认默认监控生效: 初始化后不修改 config,导航到任意 Activity 并返回,观察 5 秒后 Logcat 是否有 Watching Activity 日志。
  2. 评估阈值合理性: 在目标测试设备上运行 30 分钟,统计误报次数,按需调整 watchDurationMillis。
  3. 整理忽略列表: 走查项目中所有 Activity,对明显被系统/第三方 SDK 持有的类添加 @IgnoreForLeakCanary 或加入 ignoredClasses。
  4. 验证忽略效果: 针对已忽略的 Activity 手动制造泄漏,确认 LeakCanary 不产生报警。
  5. 线上发布前回归: 在 release 构建中移除或关闭 LeakCanary(通过 LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)),避免在用户设备上执行 dump。

4.7  常见错误与性能优化

常见错误 1: 在 ignoredClasses 中填写了类对象(SomeActivity::class.java),但 LeakCanary 期望的是 字符串全限定类名 或 包名前缀。务必写成 "com.example.SomeActivity" 形式。
常见错误 2: 错误地认为设置 watchDurationMillis = 0 可以立即检测泄漏。实际上 LeakCanary 内部有最小等待时间(约 1 秒),设为 0 会退化为默认值 5 秒。建议最小值设为 3 秒。
性能优化: 如果工程中 Activity 数量超过 50 个,且大部分在短时间内频繁创建销毁(如页面栈较深的资讯类 App),建议将阈值适当提高到 10–12 秒,避免高频 GC 和 heap dump 操作消耗 CPU。同时为确实无泄漏风险的基类 Activity(如 BaseWebViewActivity)统一添加 @IgnoreForLeakCanary,减少监控对象的数量。

4.8  本章核心流程 SVG

下图展示了从 Activity 销毁到泄漏判定的完整检测链路,包含默认监控、自定义阈值和忽略机制三个分支路径:

Activity 泄漏检测流程:默认监控、自定义阈值与忽略机制 Activity 泄漏检测完整流程 Activity 执行 onDestroy() 创建弱引用 + 入队监视 是否在忽略列表? (注解 / ignoredClasses) 忽略,不监控 等待 watchDurationMillis 弱引用已入队? 无泄漏,结束 触发 Heap Dump 生成泄漏报告 📌 默认阈值 = 5 秒 可通过 copy(watchDurationMillis) 调整 🔕 忽略方式 @IgnoreForLeakCanary 或 ignoredClasses 是 否 是 否

4.9  本章小结

Activity 泄漏检测是 LeakCanary 最核心的默认能力。通过本章内容,你应当掌握:

  • LeakCanary 基于弱引用 + 延迟阈值判定泄漏的原理;
  • 默认 5 秒阈值的生效机制和零配置启动方式;
  • 通过 watchDurationMillis 自定义泄漏判定灵敏度;
  • 使用 @IgnoreForLeakCanary 或 ignoredClasses 精确控制监控范围;
  • 结合 Logcat 日志验证配置是否生效,并遵循部署清单避免线上误报。