源码阅读 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)
  }
}

文档

相关推荐
黄林晴6 小时前
Android 17 再曝猛料:通知栏和快捷设置终于分家了,这操作等了十年
android
有位神秘人6 小时前
Android获取设备中本地音频
android·音视频
JMchen1236 小时前
Android网络安全实战:从HTTPS到双向认证
android·经验分享·网络协议·安全·web安全·https·kotlin
CS创新实验室6 小时前
Pandas 3 的新功能
android·ide·pandas
ujainu6 小时前
护眼又美观:Flutter + OpenHarmony 鸿蒙记事本一键切换夜间模式(四)
android·flutter·harmonyos
三少爷的鞋6 小时前
为什么我不在 Android ViewModel 中直接处理异常?
android
草莓熊Lotso7 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
恋猫de小郭8 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
工程师老罗14 小时前
如何在Android工程中配置NDK版本
android
Libraeking17 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack