尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

android LeakCanary 工作原理 详解

android LeakCanary 工作原理 详解 一、LeakCanary 是什么LeakCanary是由 Square 开源的 Android 内存泄漏检测工具专门用于在开发阶段自动识别 Java 层的内存泄漏问题。它通过监控对象生命周期 弱引用检测 堆转储分析的组合方案帮助开发者定位内存泄漏根因。二、核心工作原理2.1 检测基石WeakReference ReferenceQueueLeakCanary 的核心检测机制建立在 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 trace5. 泄漏分类将泄漏分为Application Leaks和Library Leaks两类展示三、支持的监控类型与实现方式LeakCanary 自动监控以下6 种对象的泄漏监控对象监听方式关键源码文件ActivityApplication.registerActivityLifecycleCallbacks()监听onActivityDestroyedActivityWatcher.ktFragment同上 FragmentManager.registerFragmentLifecycleCallbacks()FragmentAndViewModelWatcher.ktFragment View在 Fragment 生命周期回调中监听 View 销毁FragmentAndViewModelWatcher.ktViewModel自定义 ViewModel 反射获取ViewModelStore.mMap在onCleared()中遍历检测ViewModelClearedWatcher.ktServiceHookActivityThread.mH.mCallback监听 STOP_SERVICE 动态代理IActivityManager.serviceDoneExecuting()ServiceWatcher.ktRootView (Dialog等)HookWindowManagerGlobal.mViews监听onViewDetachedFromWindowRootViewWatcher.kt关键源码示例Activity 监控kotlinprivate val lifecycleCallbacks object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { reachabilityWatcher.expectWeaklyReachable( activity, ${activity::class.java.name} received Activity#onDestroy() callback ) } }ViewModel 监控通过反射 Hookkotlininternal class ViewModelClearedWatcher( storeOwner: ViewModelStoreOwner, private val reachabilityWatcher: ReachabilityWatcher ) : ViewModel() { private val viewModelMap: MapString, ViewModel? try { val mMapField ViewModelStore::class.java.getDeclaredField(mMap) mMapField.isAccessible true mMapField[storeOwner.viewModelStore] as MapString, ViewModel } catch (ignored: Exception) { null } override fun onCleared() { viewModelMap?.values?.forEach { viewModel - reachabilityWatcher.expectWeaklyReachable(viewModel, ...) } } }四、核心源码分析4.1 ObjectWatcher —— 泄漏判定中心ObjectWatcher是 LeakCanary 的核心类负责判定对象是否泄漏kotlinclass ObjectWatcher( private val clock: Clock, private val checkRetainedExecutor: Executor, private val isEnabled: () - Boolean ) { private val watchedObjects mutableMapOfString, KeyedWeakReference() private val queue ReferenceQueueAny() 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进行多层拦截kotlinprivate 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 —— 主动触发 GCkotlinobject Default : GcTrigger { override fun runGc() { Runtime.getRuntime().gc() // 比 System.gc() 更可能触发 GC Thread.sleep(100) // 等待 GC 完成 System.runFinalization() // 执行 Finalizer } }4.4 Shark —— 堆分析引擎Dump 生成的.hprof文件由SharkLeakCanary 自研的堆分析库解析主要工作定位泄漏对象在堆快照中找到 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 做精准分析。
返回列表