yunshuipiao / Potato

Read the fucking source code for the Android interview
Apache License 2.0
80 stars 12 forks source link

LeakCanary Principle #54

Open yunshuipiao opened 5 years ago

yunshuipiao commented 5 years ago

LeakCanary Principle

[TOC]

这篇文章介绍 LeakCanary 的原理,基于版本 2.0,kotlin。

基本使用

加入依赖即可,以使用 ContentProvider 进行自动初始化。LifeCycle 库类似, 可用于初始化sdk,三方库等。

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}
internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
    InternalLeakSentry.install(application)
    return true
  }
  ...
}

基本原理

Reference

Reference 把内存分为 4 种状态,Active,Pending,Exqueued, Inactive。

ReferenceQueue

引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中

如果检测一个对象是否被回收

需要采用 Reference + ReferenceQueue

在 Reference 类加载的时候,Java 虚拟机会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC。

Heap Dump

Heap Dump也叫堆存储文件,是一个Java进程在某个时间点上的内存快照。

原理说明

  1. 监听 Activity 的生命周期。
  2. 在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。
  3. 一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。
  4. dump 出 hprof 文件,找到泄漏路径。

初始化

// InternalLeakSentry.kt
fun install(application: Application) {
  CanaryLog.d("Installing LeakSentry")
  checkMainThread()
  if (this::application.isInitialized) {
    return
  }
  InternalLeakSentry.application = application

  val configProvider = { LeakSentry.config }
  ActivityDestroyWatcher.install(
      application, refWatcher, configProvider
  )
  FragmentDestroyWatcher.install(
      application, refWatcher, configProvider
  )
  listener.onLeakSentryInstalled(application)
}
internal class ActivityDestroyWatcher private constructor(
  private val refWatcher: RefWatcher,
  private val configProvider: () -> Config
) {
    // 监听 Activity 的生命周期
  private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
    override fun onActivityDestroyed(activity: Activity) {
      // destroy 进行跟踪分析
      if (configProvider().watchActivities) {
        refWatcher.watch(activity)
      }
    }
  }

  companion object {
    fun install(
      application: Application,
      refWatcher: RefWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(refWatcher, configProvider)
      // 注册监听回调
 application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

上述代码很容易理解,首先是对 Activity 的生命周期进行监听,在 destory 发生的时候进行监听,看是否被回收。

监听过程

核心代码如下:

@Synchronized fun watch(
  watchedReference: Any,
  referenceName: String
) {
  if (!isEnabled()) {
    return
  }
  // 移除被回收的所有的弱应用对象
  removeWeaklyReachableReferences()
  val key = UUID.randomUUID()
      .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  // 对 Activity 创建 弱应用对象
  val reference =
    KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
  if (referenceName != "") {
    CanaryLog.d(
        "Watching instance of %s named %s with key %s", reference.className,
        referenceName, key
    )
  } else {
    CanaryLog.d(
        "Watching instance of %s with key %s", reference.className, key
    )
  }

  watchedReferences[key] = reference
  checkRetainedExecutor.execute {
    // 延时5秒后,如果该对象还未被回收,则发生内存泄漏。
    // 当泄漏的对象5个以上时,dump 出 hprof 文件,找到泄漏路径
    moveToRetained(key)
  }
}
  private fun removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      // 获取引用对象
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        // 如果存在,则表示已经被回收,从观察列表中移除
        val removedRef = watchedReferences.remove(ref.key)
        if (removedRef == null) {
          retainedReferences.remove(ref.key)
        }
      }
    } while (ref != null)
  }
}

回过去看原理,还是很熔体理解。

moveToRetained() 就是后续判断泄漏对象和分析 dump 文件的工作。这里不做过多介绍。

同理, fragment 的观察过程也类似。