1. LeakCanary 内存泄漏检测原理

Android 应用中的内存泄漏是导致 OOM、卡顿和异常退出的主要诱因。LeakCanary 通过自动检测、堆转储(Dump)与引用链分析,将泄漏点精确到类和对象实例,帮助开发者在 CI 流水线中快速捕获回归问题。本章从泄漏根源、工作流程、Dump 与堆分析机制三个层次展开,为后续集成 CI 提供理论基础。

1.1 Android 内存泄漏根源

内存泄漏的本质是短生命周期对象被长生命周期对象持有引用,导致 GC 无法回收。Android 中常见模式:

  • Activity 泄漏:静态变量、匿名内部类、Handler、线程或监听器间接持有 Activity 引用。
  • Fragment 泄漏:未及时从 FragmentManager 移除的回调或 View 引用。
  • 资源未关闭:BroadcastReceiver、ContentObserver、Cursor、Socket 注册后未注销。
  • 集合静态化:静态 List、Map 不断添加对象却不清除。
  • 单例模式误用:单例传入 Context 作为成员,导致 Context 无法释放。
泄漏类型典型场景GC 根 Root
静态引用static 变量持有 ActivityClassLoader → static field
内部类隐式引用非静态内部类实例持有外部类 thisEnclosingMethod / EnclosingClass
注册未注销registerReceiver 未调用 unregisterSystem Service 回调引用
Thread / AsyncTask后台任务持有 Activity 引用Thread 引用链
提示:LeakCanary 默认只监控 Activity 与 Fragment 的 onDestroy() 后是否被回收。若需监控自定义对象,可通过 RefWatcher.watch() 手动加入观察。

1.2 LeakCanary 工作流程

LeakCanary 的核心工作流分为五个阶段:监控、检测、Dump、分析、报告。整体流程如下图所示:

LeakCanary 内存泄漏检测流程图 监控 Activity/Fragment 加入引用队列等待 触发 GC 并判断 是否泄漏? Dump 堆转储 (hprof) Shark 分析泄漏链 通知 / CI 报告 泄漏对象被 GC Root 强引用链保留

检测过程中,LeakCanary 内部创建 RefWatcher 并绑定 Android 的 Activity 生命周期。当 onDestroy() 被调用后,对应对象的弱引用进入引用队列。随后 LeakCanary 通过 Runtime.getRuntime().gc() 建议触发 GC,再检查对象是否仍被引用。如果存在强引用路径,则判定为泄漏并启动堆转储。

1.3 Dump 与堆分析机制

LeakCanary 使用 Shark 库(自 v2.0 起内置)代替旧的 hprof 解析引擎,大幅降低内存与分析耗时。Dump 阶段生成 Android heap dump(.hprof)文件,Shark 通过以下步骤定位泄漏:

  1. 查找泄漏对象:从 Dump 中的 Roots 出发遍历对象图。
  2. 构建最短 GC Root 路径:使用广度优先搜索找到从泄漏对象到 GC Root 的最短强引用链。
  3. 排除系统泄漏:过滤掉 Android 框架已知的无害泄漏(如 InputMethodManager 等)。
  4. 生成报告:输出包含类名、字段、引用类型、行号(ProGuard 时可能丢失)的泄漏链文本。
性能优化:CI 环境内存受限时,可配置 LeakCanary 使用 heap-analysis-progressive 模式或设置最大 Dump 大小阈值,避免长耗时分析导致流水线超时。
// build.gradle (app) 添加依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.14'

// Application 初始化
class LeakCanaryConfig : Application() {
    override fun onCreate() {
        super.onCreate()
        if (!LeakCanary.isInAnalyzerProcess(this)) {
            LeakCanary.config.apply {
                dumpDurationMillis = 5000L   // Dump 超时 5 秒
                maxDownFileFactor = 0.3f     // Dump 文件不超过可用堆的 30%
                // 自定义手动观察引用对象
                refWatcher?.watch(someLongLivedObject)
            }
        }
    }
}
注意:LeakCanary 的 Dump 操作会暂时冻结应用,CI 环境中建议在测试用例运行完成后再统一分析,避免干扰 UI 自动化测试。可在 LeakCanary.config 中关闭自动 Dump 并调用 RefWatcher.get().triggerManualDump() 按需触发。

在 CI 流水线中,可通过监听 HeapDumpListener 或解析 LeakCanary 保存的 LeakResult 文件,将泄漏信息转成 JUnit 报告,实现“泄漏即失败”的门禁规则。