第4章 Activity 泄漏检测:默认监控、自定义泄漏阈值、忽略特定 Activity
4.1 原理:Activity 泄漏检测的核心机制
LeakCanary 对 Activity 的泄漏检测建立在 Android Activity 生命周期回调 之上。当 Activity 调用 onDestroy() 后,LeakCanary 会为该 Activity 创建一个弱引用(WeakReference),并关联到一个后台 ReferenceQueue。如果 Activity 在预定的等待时间内没有被垃圾回收器回收,则判定为可能泄漏,并触发 heap dump 进行分析。
整个检测流程分为四个阶段:注册、生命周期监听、弱引用入队检查、阈值超时判定。默认情况下,LeakCanary 自动注册所有 Activity 的 onDestroy 回调,无需开发者手动调用。
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 秒以上 | 几乎无误报 | 泄漏后等待过长 | 发布版灰度监控 |
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 部署与集成检查清单
- 确认默认监控生效: 初始化后不修改 config,导航到任意 Activity 并返回,观察 5 秒后 Logcat 是否有
Watching Activity日志。 - 评估阈值合理性: 在目标测试设备上运行 30 分钟,统计误报次数,按需调整
watchDurationMillis。 - 整理忽略列表: 走查项目中所有 Activity,对明显被系统/第三方 SDK 持有的类添加
@IgnoreForLeakCanary或加入ignoredClasses。 - 验证忽略效果: 针对已忽略的 Activity 手动制造泄漏,确认 LeakCanary 不产生报警。
- 线上发布前回归: 在 release 构建中移除或关闭 LeakCanary(通过
LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)),避免在用户设备上执行 dump。
4.7 常见错误与性能优化
ignoredClasses 中填写了类对象(SomeActivity::class.java),但 LeakCanary 期望的是 字符串全限定类名 或 包名前缀。务必写成 "com.example.SomeActivity" 形式。
watchDurationMillis = 0 可以立即检测泄漏。实际上 LeakCanary 内部有最小等待时间(约 1 秒),设为 0 会退化为默认值 5 秒。建议最小值设为 3 秒。
BaseWebViewActivity)统一添加 @IgnoreForLeakCanary,减少监控对象的数量。
4.8 本章核心流程 SVG
下图展示了从 Activity 销毁到泄漏判定的完整检测链路,包含默认监控、自定义阈值和忽略机制三个分支路径:
4.9 本章小结
Activity 泄漏检测是 LeakCanary 最核心的默认能力。通过本章内容,你应当掌握:
- LeakCanary 基于弱引用 + 延迟阈值判定泄漏的原理;
- 默认 5 秒阈值的生效机制和零配置启动方式;
- 通过
watchDurationMillis自定义泄漏判定灵敏度; - 使用
@IgnoreForLeakCanary或ignoredClasses精确控制监控范围; - 结合 Logcat 日志验证配置是否生效,并遵循部署清单避免线上误报。