android LeakCanary 工作原理 详解

一、LeakCanary 是什么

LeakCanary 是由 Square 开源的 Android 内存泄漏检测工具,专门用于在开发阶段自动识别 Java 层的内存泄漏问题。它通过监控对象生命周期 + 弱引用检测 + 堆转储分析的组合方案,帮助开发者定位内存泄漏根因。


二、核心工作原理

2.1 检测基石:WeakReference + ReferenceQueue

LeakCanary 的核心检测机制建立在 Java 的弱引用(WeakReference) 和**引用队列(ReferenceQueue)**之上:

  • WeakReference:指向被监控对象,当对象仅被弱引用持有时,GC 会回收该对象

  • ReferenceQueue:当 WeakReference 指向的对象被 GC 回收后,该 WeakReference 会自动进入关联的 ReferenceQueue

检测逻辑

  1. 为被销毁的 Activity 等对象创建 WeakReference,并绑定一个 ReferenceQueue

  2. 延迟 5 秒后检查该 WeakReference 是否已进入队列

  3. 若未进入,手动触发 GC 后再检查一次

  4. 若仍未进入队列,则判定该对象发生内存泄漏

2.2 完整工作流程

LeakCanary 的工作流程可分为 5 个阶段

阶段 说明
1. 注册监听 通过生命周期回调或 Hook 技术,感知对象进入"无用"状态的时机
2. 监控泄漏 为无用对象创建弱引用,延迟检查是否被回收;泄漏对象计数达到阈值才触发分析
3. Heap Dump 将 Java 堆转储为 .hprof 文件(会短暂冻结应用)
4. 堆分析 使用 Shark 解析 .hprof,找出阻止 GC 的引用链(leak trace)
5. 泄漏分类 将泄漏分为 Application LeaksLibrary Leaks 两类展示

三、支持的监控类型与实现方式

LeakCanary 自动监控以下 6 种对象的泄漏:

监控对象 监听方式 关键源码文件
Activity Application.registerActivityLifecycleCallbacks() 监听 onActivityDestroyed ActivityWatcher.kt
Fragment 同上 + FragmentManager.registerFragmentLifecycleCallbacks() FragmentAndViewModelWatcher.kt
Fragment View 在 Fragment 生命周期回调中监听 View 销毁 FragmentAndViewModelWatcher.kt
ViewModel 自定义 ViewModel + 反射获取 ViewModelStore.mMap,在 onCleared() 中遍历检测 ViewModelClearedWatcher.kt
Service Hook ActivityThread.mH.mCallback 监听 STOP_SERVICE + 动态代理 IActivityManager.serviceDoneExecuting() ServiceWatcher.kt
RootView (Dialog等) Hook WindowManagerGlobal.mViews,监听 onViewDetachedFromWindow RootViewWatcher.kt

关键源码示例

Activity 监控

kotlin

复制代码
private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
            activity,
            "${activity::class.java.name} received Activity#onDestroy() callback"
        )
    }
}

ViewModel 监控(通过反射 Hook):

kotlin

复制代码
internal class ViewModelClearedWatcher(
    storeOwner: ViewModelStoreOwner,
    private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

    private val viewModelMap: Map<String, ViewModel>? = try {
        val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
        mMapField.isAccessible = true
        mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) { null }

    override fun onCleared() {
        viewModelMap?.values?.forEach { viewModel ->
            reachabilityWatcher.expectWeaklyReachable(viewModel, "...")
        }
    }
}

四、核心源码分析

4.1 ObjectWatcher ------ 泄漏判定中心

ObjectWatcher 是 LeakCanary 的核心类,负责判定对象是否泄漏:

kotlin

复制代码
class ObjectWatcher(
    private val clock: Clock,
    private val checkRetainedExecutor: Executor,
    private val isEnabled: () -> Boolean
) {
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
    private val queue = ReferenceQueue<Any>()

    fun watch(watchedObject: Any, description: String) {
        val key = UUID.randomUUID().toString()
        // 创建带 key 的弱引用,绑定引用队列
        val weakReference = KeyedWeakReference(watchedObject, key, description, queue)
        watchedObjects[key] = weakReference
        
        // 延迟 5 秒后检查
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }

    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects() // 清理已回收的对象
        
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            // 对象未被回收,触发泄漏通知
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    private fun removeWeaklyReachableObjects() {
        var ref: Reference<*>?
        do {
            ref = queue.poll()
            if (ref != null) {
                val key = (ref as KeyedWeakReference).key
                watchedObjects.remove(key)
            }
        } while (ref != null)
    }
}

判定逻辑

  1. 为对象创建 KeyedWeakReference(带唯一 key 的弱引用),存入 watchedObjects 映射表

  2. postDelay 5 秒后检查引用队列

  3. 若对象已被 GC,其弱引用会进入 queue,从 watchedObjects 移除

  4. 若对象仍在 watchedObjects 中,说明未被回收,标记为泄漏

4.2 HeapDumpTrigger ------ 触发堆转储的"守门员"

LeakCanary 不会每次发现泄漏都立即 Dump ,而是通过 HeapDumpTrigger 进行多层拦截:

kotlin

复制代码
private fun checkRetainedObjects() {
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    if (retainedReferenceCount > 0) {
        gcTrigger.runGc()  // 主动触发 GC
        retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    // 拦截 1:泄漏对象数未达阈值
    if (retainedKeysCount < retainedVisibleThreshold) {
        if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
            showRetainedCountNotification("App visible, waiting...")
            scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
            return
        }
    }

    // 拦截 2:距离上次 HeapDump 未超过 60s
    val elapsedSinceLastDump = SystemClock.uptimeMillis() - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDump < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
        scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDump)
        return
    }

    // 通过拦截,执行 Heap Dump
    dumpHeap(...)
}

阈值策略

  • App 前台可见 :阈值为 5 个泄漏对象

  • App 后台不可见 :阈值为 1 个泄漏对象

4.3 GcTrigger ------ 主动触发 GC

kotlin

复制代码
object Default : GcTrigger {
    override fun runGc() {
        Runtime.getRuntime().gc()  // 比 System.gc() 更可能触发 GC
        Thread.sleep(100)        // 等待 GC 完成
        System.runFinalization()   // 执行 Finalizer
    }
}

4.4 Shark ------ 堆分析引擎

Dump 生成的 .hprof 文件由 Shark(LeakCanary 自研的堆分析库)解析,主要工作:

  1. 定位泄漏对象:在堆快照中找到 retained 对象

  2. 计算引用链:使用 ** dominator tree** 算法找到从 GC Root 到泄漏对象的最短引用路径

  3. 生成签名:将引用链上的类名 + 字段名串联哈希,相同签名的泄漏归为一组

  4. 标记怀疑对象 :从第一个 LEAKING 节点到第一个 NOT_LEAKING 节点之间的引用标记为 ~~~ 怀疑对象


五、泄漏报告解读

LeakCanary 的分析结果分为两类:

  • Application Leaks:应用自身代码导致的泄漏,需要开发者修复

  • Library Leaks:第三方库已知的泄漏,LeakCanary 内置了常见库(如 Android Framework、Support Library)的已知泄漏规则

报告示例

复制代码
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS

Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
├─ com.example.LeakingSingleton class
│    Leaking: NO (a class is never leaking)
│    ↓ static LeakingSingleton.leakedViews
│                              ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    ↓ Object[].elementData[0]
│               ~~~
├─ android.widget.TextView instance
│    Leaking: YES (View.mContext references a destroyed activity)
...

六、整体架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    App 生命周期层                        │
│  ActivityWatcher │ FragmentWatcher │ ServiceWatcher │ ...  │
└────────────────────┬────────────────────────────────────┘
                     │ expectWeaklyReachable(object)
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                  ObjectWatcher                          │
│  ┌─────────────┐    ┌─────────────┐    ┌────────┐ │
│  │KeyedWeakRef │───▶│ ReferenceQueue│◀──│  GC    │ │
│  │  (watched) │    │  (poll检查)  │    │        │ │
│  └─────────────┘    └─────────────┘    └────────┘ │
│         │ 5秒后未回收                              │
│         ▼                                          │
│   onObjectRetained() ────────────────────────────────┘
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                HeapDumpTrigger                          │
│  阈值检查(前台5/后台1)│ 60s间隔检查 │ 主动GC确认    │
└────────────────────┬────────────────────────────────────┘
                     │ 通过检查
                     ▼
┌─────────────────────────────────────────────────────────────┐
│              Heap Dump (.hprof)                       │
│         Debug.dumpHprofData(heapDumpFile)             │
└────────────────────┬────────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                   Shark 分析引擎                         │
│  解析堆文件 → 找最短引用链 → 生成签名 → 分类展示        │
└─────────────────────────────────────────────────────────────┘

七、总结

要点 说明
检测核心 WeakReference + ReferenceQueue
触发时机 Activity/Fragment/ViewModel 等销毁后延迟 5 秒检查
防误报机制 手动触发 GC + 双重确认 + 阈值拦截
堆分析 使用 Shark 自研库解析 .hprof,计算 dominator tree
性能保护 前台阈值 5、后台阈值 1、60s 内不重复 Dump
扩展性 支持自定义 ObjectWatcher.watch() 检测任意对象

LeakCanary 的设计精髓在于:用弱引用做轻量筛查,用阈值策略控制 Dump 频率,用 Shark 做精准分析

相关推荐
AFinalStone3 小时前
Android 7系统无障碍服务(一)全景图与架构概览
android·无障碍服务
Android-Flutter4 小时前
android 自定义view 详解
android·kotlin
WAsbry4 小时前
协程任务的失败控制:取消、异常传播与Supervisor
android
Android打工仔4 小时前
从 finally 理解程序的控制流:它为什么不是 catch 后面的代码?
android·kotlin
随遇丿而安4 小时前
第15周:Service 全功能 + 后台优化
android
YXL1111YXL4 小时前
续体和状态机 —— suspend 函数的 CPS 变换
android·kotlin
WAsbry4 小时前
Flow数据模型:冷流、状态、事件与共享策略
android
WAsbry4 小时前
深入理解 Kotlin suspend:挂起语义、状态机与恢复调度
android
WAsbry4 小时前
Flow在Android中的完整落地:异常、重试与生命周期
android