源码阅读 LeakCanary

LeakCanary源码分析

LeakCanary作为常用的内存泄漏分析工具,那是如何检测内存泄漏的呢?从注册入口分析,最新的LeakCanary 2.14,通过ContentProvider自动获取Context完成注册。

xml 复制代码
<provider
    android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
    android:authorities="${applicationId}.leakcanary-installer"
    android:enabled="@bool/leak_canary_watcher_auto_install"
    android:exported="false" />

一、MainProcessAppWatcherInstaller

leakcanary.internal.MainProcessAppWatcherInstalleronCreate

kotlin 复制代码
internal class MainProcessAppWatcherInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
  ...
}

二、AppWatcher.manualInstall

manualInstall 中 完成 安装:

kotlin 复制代码
// leakcanary.internal.InternalLeakCanary 内部实现
LeakCanaryDelegate.loadLeakCanary(application)

// 相关 Activity/Fragment 组件的 检测类,都初始化
watchersToInstall.forEach {
    it.install()
}
2.1 appDefaultWatchers

watchersToInstall 默认由 AppWatcher.appDefaultWatchers 创建 组件的内存检查对象。

他们都实现 InstallableWatcher 接口,实现 install()uninstall() 方法。

kotlin 复制代码
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
2.2 ActivityWatcher - Activity的泄漏分析

Activity 利用生命周期函数 检测。

  1. 利用 Application.registerActivityLifecycleCallbacks() 监听
  2. onActivityDestroyed()时,调用 objectWatcher.expectWeaklyReachable() 进行 弱可达性分析

三、ObjectWatcher

ObjectWatcher 完成 内存检查对象的 弱引用创建 和 GC处理。

所有 Activity Fragment View 检测的 InstallableWatcher 都有 objectWatcher 成员,

需要检查泄漏时,来调用 expectWeaklyReachable 方法。

3.1 AppWatcher.objectWatcher 创建默认值
kotlin 复制代码
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )

解释: checkRetainedExecutor 作为 Executor 实现,延迟 5s 在 mainHandler 主线程完成。

3.2 expectWeaklyReachable 检查方法
  • KeyedWeakReference
  • watchedObjects[key]
kotlin 复制代码
  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
        (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
        (if (description.isNotEmpty()) " ($description)" else "") +
        " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

相关字段说明:

  1. KeyedWeakReference 继承自 WeakReference,增加了 key 用于判断。
  2. watchedObjects 保存 UUIDkey的弱引用对象
  3. queueReferenceQueue, 用于GC回收时,弱引用会自动进入队列,也就是 未泄漏。
  4. checkRetainedExecutorExecutor,默认实现 延迟5s 执行。
3.3 removeWeaklyReachableObjects

queue中是 准备被回收的 Reference

循环 queue.poll() 获取,并移除 要回收 的引用对象。

kotlin 复制代码
  private fun removeWeaklyReachableObjects() {
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
3.4 moveToRetained

这里会 触发 InternalLeakCanaryonObjectRetainer(),进一步 处理。

kotlin 复制代码
  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

四、InternalLeakCanary

回到 AppWatcher.manualInstall() 方法,对应:

kotlin 复制代码
LeakCanaryDelegate.loadLeakCanary(application)

会通过反射 创建 InternalLeakCanary对象, 并执行 InternalLeakCanary.invoke 方法,注册了上面的OnObjectRetainedListener监听器:

kotlin 复制代码
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
4.1 onObjectRetained

--> scheduleRetainedObjectCheck()

--> heapDumpTrigger.scheduleRetainedObjectCheck()

--> heapDumpTrigger.checkRetainedObjects()

checkRetainedObjects() 源码:

kotlin 复制代码
    ...
    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    ...
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )

这里关键地方:

  1. gcTrigger.runGc() 运行GC,检测泄漏对象数量。
  2. heapDump() 方法,保存栈信息,本质是 Android的 Debug.dumpHprofData()

看源码:HeapDumpTrigger.kt

4.2 GcTrigger

通过 Runtime.getRuntime().gc() 进行 GC 操作。

kotlin 复制代码
  object Default : GcTrigger {
    override fun runGc() {
      Runtime.getRuntime()
        .gc()
      enqueueReferences()
      System.runFinalization()
    }
    ...
  }

源码:GcTrigger.kt

4.3 AndroidDebugHeapDumper

利用 Android Api Debug.dumpHprofData(path) 获取栈信息:

kotlin 复制代码
object AndroidDebugHeapDumper : HeapDumper {
  override fun dumpHeap(heapDumpFile: File) {
    Debug.dumpHprofData(heapDumpFile.absolutePath)
  }
}

文档

相关推荐
用户2018792831673 小时前
为啥现在 Android App 不用手动搞 MultiDex 了?
android
fouryears_234173 小时前
如何将Vue 项目转换为 Android App(使用Capacitor)
android·前端·vue.js
消失的旧时光-19434 小时前
人脸跟随 ( Channel 实现(缓存5条数据 + 2度过滤 + 平滑移动))
android·java·开发语言·kotlin
小王lj4 小时前
画三角形报错bad_Alloc 原因,回调用错
android
xhbh6664 小时前
【实战避坑】MySQL自增主键(AUTO_INCREMENT)全解:从锁机制、间隙问题到分库分表替代方案
android·数据库·mysql·mysql自增主键
TimeFine4 小时前
Android 通过Dialog实现全屏
android
用户2018792831675 小时前
Android Input 的 “快递双车道”:为什么要用 Pair Socket?
android
ajassi20005 小时前
开源 java android app 开发(十八)最新编译器Android Studio 2025.1.3.7
android·java·开源
用户2018792831675 小时前
Java 泛型:快递站老板的 "类型魔法" 故事
android