一、LeakCanary 是什么
LeakCanary是由 Square 开源的 Android 内存泄漏检测工具,专门用于在开发阶段自动识别 Java 层的内存泄漏问题。它通过监控对象生命周期 + 弱引用检测 + 堆转储分析的组合方案,帮助开发者定位内存泄漏根因。
二、核心工作原理
2.1 检测基石:WeakReference + ReferenceQueue
LeakCanary 的核心检测机制建立在 Java 的弱引用(WeakReference)和引用队列(ReferenceQueue)之上:
WeakReference:指向被监控对象,当对象仅被弱引用持有时,GC 会回收该对象
ReferenceQueue:当 WeakReference 指向的对象被 GC 回收后,该 WeakReference 会自动进入关联的 ReferenceQueue
检测逻辑:
为被销毁的 Activity 等对象创建
WeakReference,并绑定一个ReferenceQueue延迟 5 秒后检查该 WeakReference 是否已进入队列
若未进入,手动触发 GC 后再检查一次
若仍未进入队列,则判定该对象发生内存泄漏
2.2 完整工作流程
LeakCanary 的工作流程可分为5 个阶段:
| 阶段 | 说明 |
|---|---|
| 1. 注册监听 | 通过生命周期回调或 Hook 技术,感知对象进入"无用"状态的时机 |
| 2. 监控泄漏 | 为无用对象创建弱引用,延迟检查是否被回收;泄漏对象计数达到阈值才触发分析 |
| 3. Heap Dump | 将 Java 堆转储为.hprof文件(会短暂冻结应用) |
| 4. 堆分析 | 使用Shark解析.hprof,找出阻止 GC 的引用链(leak trace) |
| 5. 泄漏分类 | 将泄漏分为Application Leaks和Library 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 | HookActivityThread.mH.mCallback监听 STOP_SERVICE + 动态代理IActivityManager.serviceDoneExecuting() | ServiceWatcher.kt |
| RootView (Dialog等) | HookWindowManagerGlobal.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) } }判定逻辑:
为对象创建
KeyedWeakReference(带唯一 key 的弱引用),存入watchedObjects映射表postDelay5 秒后检查引用队列
若对象已被 GC,其弱引用会进入
queue,从watchedObjects移除若对象仍在
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 自研的堆分析库)解析,主要工作:
定位泄漏对象:在堆快照中找到 retained 对象
计算引用链:使用 ** dominator tree** 算法找到从 GC Root 到泄漏对象的最短引用路径
生成签名:将引用链上的类名 + 字段名串联哈希,相同签名的泄漏归为一组
标记怀疑对象:从第一个
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 做精准分析。