Android LeakCanary 使用 · 原理详解

火影  论坛元老 | 2025-4-10 18:19:42 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1748|帖子 1748|积分 5244

一、简介

LeakCanary 是 Square 公司开源的 Android 内存泄漏检测工具,通过主动化监控和堆转储分析,资助开发者快速定位内存泄漏根源。其焦点设计轻量高效,已成为 Android 开发中必备的调试工具。

二、使用方式

1. 集成步调

在项目的 build.gradle 文件中添加依赖:
  1. ---
  2. dependencies {
  3.   debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
  4. }
  5. ---
复制代码
2. 主动初始化机制

通过 ContentProvider 实现无感初始化:
  1. class MainProcessAppWatcherInstaller : ContentProvider() {
  2.     override fun onCreate(): Boolean {
  3.         val application = context!!.applicationContext as Application
  4.         AppWatcher.manualInstall(application) // 核心初始化入口
  5.         return true
  6.     }
  7. }
复制代码
优势:无需修改 Application 代码,系统主动完成初始化流程

3. 监控对象

默认监控范围:LeakCanary 主动检测以下对象的泄漏:


  • 已烧毁的 Activity 实例
  • Fragment 及其关联视图对象
  • 已清理的 ViewModel 实例
  • Service 实例
手动监控开发者可以对任意对象进行主动监控:
  1. val watchedObject = MyObject()
  2. AppWatcher.objectWatcher.watch(
  3.   watchedObject,
  4.   "MyObject is leaking"
  5. )
复制代码

4. 检察泄漏报告

关照栏提示:检测到泄漏时,LeakCanary 会生成关照栏提示。
具体报告内容


  • 泄漏对象的引用链(从对象到 GC Root 的路径)。
  • 泄漏原因分类(如静态变量、未解绑监听器等)。
  • 可疑代码位置高亮。

5. 自界说配置

在 Application 中修改默认行为:
  1. class MyApp : Application() {
  2.   override fun onCreate() {
  3.     super.onCreate()
  4.     LeakCanary.config = LeakCanary.config.copy(
  5.       dumpHeap = true,              // 是否生成 hprof 文件
  6.       retainedVisibleThreshold = 3,  // 触发堆转储的阈值(默认5秒)
  7.       referenceMatchers = listOf(    // 忽略特定引用
  8.         IgnoredReferenceMatcher(
  9.           pattern = "com.example.MyClass.staticField"
  10.         )
  11.       )
  12.     )
  13.   }
  14. }
复制代码

三、原理分析

1. 主动初始化与生命周期监控

1.1 ContentProvider 主动初始化

LeakCanary 通过 ContentProvider 实现零侵入初始化,焦点逻辑在 MainProcessAppWatcherInstaller 中:
  1. // 源码路径:leakcanary-object-watcher-android/src/main/java/leakcanary/internal/MainProcessAppWatcherInstaller.kt
  2. class MainProcessAppWatcherInstaller : ContentProvider() {
  3.     override fun onCreate(): Boolean {
  4.         // 获取 Application 实例
  5.         val application = context!!.applicationContext as Application
  6.         // 调用 AppWatcher 手动安装
  7.         AppWatcher.manualInstall(application)
  8.         return true
  9.     }
  10.     // 其他方法空实现(query/insert 等)
  11. }
复制代码
触发时机:Android 系统在应用启动时主动初始化所有注册的 ContentProvider,通过 onCreate() 触发 LeakCanary 的初始化。
优势:无需开发者手动在 Application 中调用代码,实现完全主动化。

1.2 生命周期监控组件注册

在 AppWatcher.manualInstall() 中注册默认的监控组件:
  1. // 源码路径:leakcanary-object-watcher-android/src/main/java/leakcanary/AppWatcher.kt
  2. fun manualInstall(
  3.     application: Application,
  4.     watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  5. ) {
  6.     // 1. 初始化 InternalLeakCanary
  7.     LeakCanaryDelegate.loadLeakCanary(application)
  8.     // 2. 注册四大监控组件
  9.     watchersToInstall.forEach { it.install() }
  10. }
  11. private fun appDefaultWatchers(application: Application): List<InstallableWatcher> {
  12.     return listOf(
  13.         ActivityWatcher(application, objectWatcher),
  14.         FragmentAndViewModelWatcher(application, objectWatcher),
  15.         RootViewWatcher(objectWatcher),
  16.         ServiceWatcher(objectWatcher)
  17.     )
  18. }
复制代码
四大焦点监控组件
组件名称监控目的触发时机实现原理ActivityWatcherActivityonDestroy()ActivityLifecycleCallbacksFragmentWatcherFragment/ViewModelonViewDestroyed()FragmentManager 生命周期监听RootViewWatcherDecorViewonDetachedFromWindowView.OnAttachStateListenerServiceWatcherServiceonDestroy()Service 生命周期回调 典型监控流程(以 Activity 为例)
  1. // 源码路径:leakcanary-object-watcher-android/src/main/java/leakcanary/internal/ActivityWatcher.kt
  2. class ActivityWatcher(
  3.     private val application: Application,
  4.     private val reachabilityWatcher: ReachabilityWatcher
  5. ) : InstallableWatcher {
  6.     private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
  7.         override fun onActivityDestroyed(activity: Activity) {
  8.             // 将 Activity 加入泄漏监控队列
  9.             reachabilityWatcher.expectWeaklyReachable(
  10.                 activity,
  11.                 "${activity::class.java.name} received Activity#onDestroy()"
  12.             )
  13.         }
  14.     }
  15.     override fun install() {
  16.         application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  17.     }
  18. }
复制代码
焦点逻辑:通过 registerActivityLifecycleCallbacks 监听 onActivityDestroyed,触发对象泄漏检测。

2. 弱引用追踪系统

对象监控三要素


  • KeyedWeakReference:携带唯一标识的弱引用。
  • ReferenceQueue:关联的采取队列。
  • ObjectWatcher:负责管理被监控对象。
  1. // 源码路径:leakcanary-object-watcher/core/src/main/java/leakcanary/ObjectWatcher.kt
  2. class ObjectWatcher {
  3.     private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
  4.     private val queue = ReferenceQueue<Any>()
  5.     fun watch(target: Any, description: String) {
  6.         val key = UUID.randomUUID().toString()
  7.         val reference = KeyedWeakReference(target, key, description, queue)
  8.         watchedObjects[key] = reference
  9.         scheduleRetainedCheck()
  10.     }
  11.     private fun scheduleRetainedCheck() {
  12.         checkRetainedExecutor {
  13.             removeWeaklyReachableObjects()
  14.             checkRetainedCount()
  15.         }
  16.     }
  17. }
复制代码
KeyedWeakReference:自界说弱引用,关联全局 ReferenceQueue,用于判定对象是否被采取。
  1. class KeyedWeakReference(
  2.     referent: Any,
  3.     val key: String,
  4.     val description: String,
  5.     val watchUptimeMillis: Long,
  6.     queue: ReferenceQueue<Any>
  7. ) : WeakReference<Any>(referent, queue)
复制代码
2.2 采取检测流程

关键步调,双阶段检测流程

  • 轮询队列:通过轮询 ReferenceQueue,移除已被采取的 KeyedWeakReference。
  • 触发泄漏检测:若对象未被采取,调用 onObjectRetained() 关照监听器。
  1. private fun removeWeaklyReachableObjects() {
  2.     do {
  3.         val ref = queue.poll() as? KeyedWeakReference
  4.         ref?.let { watchedObjects.remove(it.key) }
  5.     } while (ref != null)
  6. }
  7. private fun checkRetainedCount() {
  8.     if (watchedObjects.size >= config.retainedVisibleThreshold) {
  9.         onLeakDetected()
  10.     }
  11. }
  12. // onLeakDetected() 是 ObjectWatcher 中的一个方法,用于在检测到内存泄漏时触发后续的处理逻辑。
  13. // onLeakDetected() 方法会遍历 onObjectRetainedListeners 列表,并调用每个监听器的     
  14. // onObjectRetained() 方法。
  15. private fun onLeakDetected() {
  16.     // 1. 触发泄漏通知
  17.     onObjectRetainedListeners.forEach { it.onObjectRetained() }
  18. }
  19. // 源码路径:leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
  20. override fun onObjectRetained() {
  21.     scheduleRetainedObjectCheck()
  22. }
复制代码

3. 堆转储触发与堆分析

3.1 HeapDumpTrigger 调理

泄漏关照最终由 HeapDumpTrigger 处置惩罚:等待5s后,调用gc,如果持有的弱引用没有被扫除,则被监视器认为产生了一个内存泄漏,LeakCanary会将其记录到Logcat中;记录保存对象的计数,到达阈值后,会调用转储堆;
泄漏判定流程

  • 检测潜在泄漏对象。
  • 主动触发 System.gc()。
  • 二次确认存活状态。
  • 生成 hprof 堆转储文件。
  • 启动 Shark 分析引擎。
  1. // 源码路径:leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
  2. class HeapDumpTrigger(
  3.     private val application: Application,
  4.     private val backgroundHandler: Handler,
  5.     private val objectWatcher: ObjectWatcher,
  6.     private val gcTrigger: GcTrigger,
  7.     private val configProvider: () -> Config
  8. ) {
  9.     fun scheduleRetainedObjectCheck() {
  10.         backgroundHandler.postDelayed({
  11.             checkRetainedObjects()
  12.         }, 0)
  13.     }
  14.     private fun checkRetainedObjects() {
  15.         // 1. 检查未被回收的对象数量
  16.         val retainedCount = objectWatcher.retainedObjectCount
  17.         if (retainedCount > 0) {
  18.             // 2. 主动触发 GC
  19.             gcTrigger.runGc()
  20.             // 3. 再次检查,确认泄漏
  21.             if (objectWatcher.retainedObjectCount >= configProvider().retainedVisibleThreshold) {
  22.                 // 4. 生成堆转储
  23.                 dumpHeap(retainedCount, reason = "Retained objects ≥ threshold")
  24.             }
  25.         }
  26.     }
  27. }
复制代码
主动触发 GC
  1. object Default : GcTrigger {
  2.     override fun runGc() {
  3.         Runtime.getRuntime().gc()
  4.         Thread.sleep(100)
  5.         System.runFinalization()
  6.     }
  7. }
复制代码
3.2 堆转储生成

  1. private fun dumpHeap(retainedReferenceCount: Int, reason: String) {
  2.     // 1. 创建堆转储文件
  3.     val heapDumpFile = InternalLeakCanary.createLeakDirectoryProvider(application)
  4.         .newHeapDumpFile()
  5.     // 2. 调用 Android API 生成 hprof 文件
  6.     Debug.dumpHprofData(heapDumpFile.absolutePath)
  7.     // 3. 发送分析任务
  8.     InternalLeakCanary.sendEvent(HeapDump(heapDumpFile, reason))
  9. }
复制代码
3.3 堆分析引擎(Shark 库)

堆分析由 BackgroundThreadHeapAnalyzer 在后台线程实行:
  1. // 源码路径:leakcanary-android-core/src/main/java/leakcanary/internal/BackgroundThreadHeapAnalyzer.kt
  2. object BackgroundThreadHeapAnalyzer : EventListener {
  3.     private val handlerThread = HandlerThread("HeapAnalyzer")
  4.    
  5.     override fun onEvent(event: Event) {
  6.         if (event is HeapDump) {
  7.             handlerThread.handler.post {
  8.                 // 使用 Shark 库解析 hprof
  9.                 val result = SharkHelper.analyze(event.heapDumpFile)
  10.                 // 生成泄漏报告
  11.                 showResult(result)
  12.             }
  13.         }
  14.     }
  15. }
复制代码
Shark 分析引擎四步法

  • 流式剖析:分块读取避免内存溢出。
  • 索引构建:建立快速查找表。
  • 泄漏追踪:BFS 算法查找 GC Root 路径。
  • 结果聚合:生成可视化报告。
 4. 检测流程



四、泄漏报告解读与处置惩罚

1. 报告输出渠道



  • 关照栏:点击检察具体堆栈。
  • Logcat:打印完整引用链信息。
  • Toast:提示内存泄漏发生。
  • 桌面报告文件:/sdcard/Download/leakcanary-{package}/ 目录。
  1. // 源码路径:leakcanary-android-core/src/main/java/leakcanary/EventListener.kt
  2. object LogcatEventListener : EventListener {
  3.     override fun onEvent(event: Event) {
  4.         if (event is HeapAnalysisDone) {
  5.             Log.d("LeakCanary", event.heapAnalysis.toString())
  6.         }
  7.     }
  8. }
  9. object NotificationEventListener : EventListener {
  10.     override fun onEvent(event: Event) {
  11.         if (event is HeapAnalysisDone) {
  12.             showNotification(event.heapAnalysis)
  13.         }
  14.     }
  15. }
复制代码

2. 典型报告结构

  1. ┬───
  2. │ GC Root: 静态变量 com.example.AppConfig.sInstance
  3. ├─ com.example.UserManager 实例
  4. │    ↓ 静态 UserManager.currentActivity
  5. ├─ com.example.MainActivity 实例
  6. │    ↓ 匿名内部类持有外部引用
  7. ╰→ 泄漏点: MainActivity$1.class
复制代码

3. 常见泄漏模式

泄漏类型典型场景解决方案静态引用单例持有 Activity 引用改用 WeakReference匿名内部类Handler 未及时移除使用静态内部类 + 弱引用未解绑监听注册系统服务未反注册生命周期配对解除资源未关闭文件流/Cursor 未关闭使用 try-with-resources
五、高级优化战略

1. 生产环境配置

  1. LeakCanary.config = LeakCanary.config.copy(
  2.     dumpHeap = BuildConfig.DEBUG,
  3.     analysisPeriodMillis = 120_000,
  4.     referenceMatchers = listOf(
  5.         IgnoredReferenceMatcher(
  6.             className = "com.example.SDKManager",
  7.             fieldName = "mContext"
  8.         )
  9.     )
  10. )
复制代码

2. 性能优化技巧



  • 采样检测:随机检测部分关键对象。
  • 延时分析:空闲时段实行堆剖析。
  • 白名单机制:过滤已知伪泄漏。

六、焦点设计总结



  • 主动化监控:通过 Android 系统机制(ContentProvider、LifecycleCallbacks)实现零侵入集成。
  • 精准判定:弱引用 + 引用队列确保对象采取状态判定的准确性。
  • 高效分析:Shark 库的流式剖析和最短路径算法提拔分析效率。
  • 机动扩展:支持自界说监控对象、排除已知泄漏、调解检测阈值。
通过源码级分析,可深入理解 LeakCanary 的底层机制,快速定位复杂内存问题,提拔应用稳定性。


推荐&参考: 
1.  Android StrictMode 使用与原理深度剖析
2. 《Android应用性能优化全剖析:常见问题与解决方案》
3. Android体系课之--LeakCanary内存泄漏检测原理剖析-阿里云开发者社区
4. 《RxJava 深度剖析:工作原理、焦点操作符与高效实践指南》


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表