4 LeakCanary 自定义配置优化
LeakCanary 2.x 版本提供了高度灵活的配置机制,允许开发者根据项目需求调整内存泄漏检测的触发策略、资源存储路径、用户交互方式以及已知误报过滤规则。本章将详细阐述这些关键配置参数的定义、调优原理及具体实现。
4.1 四类核心配置概览
LeakCanary 的配置涉及两大类:全局默认配置和基于 AppWatcher 的实例配置。所有自定义优化均通过构建 LeakCanary.Config 对象实现。下表列出了本节要优化的四个核心领域。
| 配置类别 | 关键参数/方法 | 默认值 | 优化目标 |
|---|---|---|---|
| 触发阈值 | retryableAnalysisTimeoutMillis | 30s | 平衡检测灵敏度与耗时,防止过慢分析阻塞队列 |
| 堆转储路径 | heapDumpDirectoryProvider | 应用缓存目录 | 指定外部存储或云存储挂载点,避免占满内部空间 |
| 通知与静默模式 | screenshots, silentListener | 启用通知 | 在CI/Jenkins环境完全静默,仅输出机器可读日志 |
| 排除已知泄漏 | knownLeaks | 空列表 | 屏蔽框架/第三方库的已知误报,减少干扰 |
4.2 触发阈值设置
4.2.1 原理
LeakCanary 发现疑似泄漏后不会立即转储堆(Heap Dump),而是等待一个可配置的超时窗口。若在窗口内发生 GC 且对象被回收,则取消转储;否则强制执行。这个超时窗口即 retryableAnalysisTimeoutMillis。
- 设置过短(如 5s):频繁触发堆转储,导致应用卡顿,且分析结果不稳定。
- 设置过长(如 120s):泄漏对象在内存储存时可能已被二次回收,导致漏报。
4.2.2 实现代码
在应用入口 Application.onCreate() 中,通过自定义 LeakCanary.Config 覆盖默认值。
// Application.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
// 分析进程不需要重复初始化
return
}
LeakCanary.config = LeakCanary.config.copy(
// CI环境下使用更保守的超时时间(20s),留足重试时间
retryableAnalysisTimeoutMillis = 20_000L,
// 在CI流水线中,关闭截图以减少IO
screenshots = false
)
// 务必在所有配置之后调用 install
LeakCanary.install(this)
}
}
4.3 堆转储路径定制
4.3.1 问题场景
默认堆转储文件存储在应用内部缓存目录 (context.cacheDir)。在 CI 容器中,该目录可能随容器销毁而丢失,且体积大小不受控制(单次高达 30-50MB)。优化目标是将堆转储文件定向至 CI 挂载的持久化卷或自定义外部目录。
4.3.2 实现 Provider
val customDumpDirectoryProvider = HeapDumpDirectoryProvider { context ->
// 优先使用环境变量指定的路径,兼容Jenkins/K8s挂载
val ciDumpDir = System.getenv("LEAKCANARY_DUMP_DIR")
if (!ciDumpDir.isNullOrBlank()) {
val file = File(ciDumpDir)
if (!file.exists()) file.mkdirs()
Log.i("LeakCanary", "堆转储目录切换为CI挂载路径: $ciDumpDir")
return@HeapDumpDirectoryProvider file
}
// 回退:使用应用外部存储,并附带时间戳命名子目录
val extDir = context.getExternalFilesDir("leakcanary_dumps")
if (extDir != null) {
val datedDir = File(extDir, SimpleDateFormat("yyyy-MM-dd_HH", Locale.US).format(Date()))
if (!datedDir.exists()) datedDir.mkdirs()
return@HeapDumpDirectoryProvider datedDir
}
// 最终默认
File(context.cacheDir, "heap_dumps")
}
// 在 config 中应用
LeakCanary.config = LeakCanary.config.copy(
heapDumpDirectoryProvider = customDumpDirectoryProvider
)
4.4 通知与静默模式
4.4.1 静默模式路径
在 CI 环境中,系统级通知无效且干扰终端输出。必须切换到“静默模式”:只记录结构化日志(JSON格式),不弹出通知、不截屏、不震动。
4.4.2 配置静默监听器
val ciSilentLeakListener = object : LeakListener {
override fun onLeakDetected(heapDump: HeapDump, result: AnalysisResult) {
// 不发送通知,只输出可解析JSON日志
val logEntry = buildString {
append("LEAKCANARY_DETECTED")
append(" | timestamp=").append(System.currentTimeMillis())
append(" | heapDumpFile=").append(heapDump.heapDumpFile.absolutePath)
append(" | leakTraceHash=").append(result.signature?.toHexString() ?: "null")
append(" | retainedHeapSize=").append(result.retainedHeapBytes)
append(" | analysisDurationMs=").append(result.analysisDurationMs)
}
Log.i("LeakCanaryCI", logEntry)
}
override fun onLeakNotDetected(heapDump: HeapDump) {
// 静默忽略未检测到泄漏的场景
Log.i("LeakCanaryCI", "LEAKCANARY_NOT_DETECTED | file=${heapDump.heapDumpFile.name}")
}
}
// 集成进Config
LeakCanary.config = LeakCanary.config.copy(
// 关闭系统通知
screenshots = false,
// 替换默认Listener为静默版本
leakListeners = listOf(ciSilentLeakListener)
)
4.5 排除已知泄漏
4.5.1 已知泄漏对象
Android 框架和主流第三方库(如 Glide、OkHttp、Retrofit)存在一些已知的非问题泄漏或无法修复的泄漏。LeakCanary 允许通过 KnownLeak 对象将其加入白名单,从而不在报告中标记。
4.5.2 排除列表配置
val knownLeaks = listOf(
KnownLeak(
knownLeak = "已知Android系统单例泄漏",
referenceMatchers = listOf(
ReferenceMatcher(
referenceMatchers = listOf(
IsInstanceOfMatcher("android.app.ActivityThread\$ActivityClientRecord")
),
pattern = ReferenceMatchPattern.EXACT
)
),
comment = "Android P及以下版本系统bug,已由AOSP标记为不会修复。"
),
KnownLeak(
knownLeak = "Glide Bitmap复用池",
referenceMatchers = listOf(
ReferenceMatcher(
referenceMatchers = listOf(
IsInstanceOfMatcher("com.bumptech.glide.load.engine.BitmapPoolAdapter")
),
pattern = ReferenceMatchPattern.ANY
)
),
comment = "Glide内部内存复用机制,非真正泄漏。"
)
)
// 写入 config
LeakCanary.config = LeakCanary.config.copy(
// 也可以从 assets 文件加载,或通过 CI 配置文件动态注入
knownLeaks = knownLeaks
)
4.6 自定义配置优化完整流程图
下图展示了 LeakCanary 从检测到配置生效的完整决策流,红色分支对应本章所优化的路径。
4.7 常见错误与调试
常见错误 1:在
解决方法:始终将
onCreate() 中修改 LeakCanary.Config 后忘记调用 LeakCanary.install(),导致配置不生效。解决方法:始终将
LeakCanary.install(this) 作为配置修改后的最后一行。
调试提示:在 CI 日志中 grep
LEAKCANARY_DETECTED 可快速定位泄漏事件。若希望强制触发一次堆转储测试配置是否生效,可在测试用例中模拟内存泄漏:// 测试代码片段
val leakedList = mutableListOf<Activity>()
val act = Robolectric.buildActivity(MyActivity::class.java).create().get()
leakedList.add(act)
// 手动触发 GC 并等待 LeakCanary 检测
Runtime.getRuntime().gc()
Thread.sleep(30_000)
然后检查自定义路径下是否生成 .hprof 文件。
性能优化建议:
- 将
- 在 CI 配置中始终设置
- 排除列表应定期审计,废弃的已知泄漏条目应及时移除,防止真正泄漏被掩盖。
- 将
retryableAnalysisTimeoutMillis 设置为 20s-30s 之间,既避免频繁转储,又保证分析时效。- 在 CI 配置中始终设置
screenshots = false,可减少约 150-300ms 的 IO 开销。- 排除列表应定期审计,废弃的已知泄漏条目应及时移除,防止真正泄漏被掩盖。