Android-LeakCanary原理解析

一、前言(了解ReferenceQueue)

在分析LeakCanary原理之前,首先需要了解ReferenceQueue在LeakCanary的作用。
WeakReference在创建时,如果指定一个ReferenceQueue对象,在垃圾回收检测到被引用的对象的可达性更改后,垃圾回收器会将已注册的引用对象添加到ReferenceQueue对象中,等待ReferenceQueue处理。但是如果当GC过后引用对象仍然不被加入ReferenceQueue中,就可能存在内存泄露问题。这里ReferenceQueue对象中,存的其实就是WeakReference对象,而不是WeakReference中引用的要被回收的对象。即GC过后,WeakReference引用的对象被回收了,那么WeakReference引用的对象就是null,那么该WeakReference对象就会被加入到ReferenceQueue队列中。
所以我们可以通过监听 Activity.onDestroy() 回调之后,通过弱引用(WeakReference)对象、ReferenceQueue和 GC来观测Activity引用的内存泄露情况,如果发现了未被回收的Activity对象,在找到该Activity对象是否被其他对象所引用,如果被其他对象引用,就进行 heap dump生成完整的内存引用链(最短引用链),并通过notification等方式展示出来。

二、LeakCanary的启动

LeakCanary2.+的启动,与LeakCanary1.+的不同,1.+版本的启动,需要在Application的onCreate中手动调用LeakCanary.install方法进行启动;而2.+版本的启动则不需要,而是依赖ContentProvider,因为ContentProvider会在Application之前被加载,所以ContentProvider的onCreate方法会在Application的onCreate方法之前被调用,所以在ContentProvider的onCreate方法中完成初始化工作。
在源码中leakcanary-leaksentry中有一个LeakSentryInstaller,LeakSentryInstaller其实就是ContentProvider的一个子类,在其onCreate方法中就会调用InternalLeakSentry.install(application)进行初始化工作。

internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
    InternalLeakSentry.install(application) // 进行初始化工作,核心
    return true
  }
  ...
}

然后在AndroidManifest.xml中注册该ContentProvider。在这里注册,那么打包项目时,会将每个库和library中的AndroidManifest.xml合并到最终的app的androidManifest中。

<application>
  <provider
      android:name="leakcanary.internal.LeakSentryInstaller"
      android:authorities="${applicationId}.leak-sentry-installer"
      android:exported="false"/>
</application>

三、LeakCanary的初始化

LeakCanary的初始化是在InternalLeakSentry的install方法,即在ContentProvider的onCreate中调用。

1.InternalLeakSentry#install

  private val mainHandler = Handler(Looper.getMainLooper())

  init {//构造函数
    listener = try {//InternalLeakCanary是继承自LeakSentryListener,然后这里它是一个kotlin单例模式
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE").get(null) as LeakSentryListener
    } catch (ignored: Throwable) {
      LeakSentryListener.None
    }
  }

  private val checkRetainedExecutor = Executor { // 默认五秒后执行
    mainHandler.postDelayed(it, LeakSentry.config.watchDurationMillis)
  }
  val refWatcher = RefWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      onReferenceRetained = { listener.onReferenceRetained() },
      isEnabled = { LeakSentry.config.enabled }
  )
fun install(application: Application) {
  CanaryLog.d("Installing LeakSentry")
  checkMainThread() // 只能在主线程调用,否则会抛出异常
  if (this::application.isInitialized) {
    return
  }
  InternalLeakSentry.application = application

  val configProvider = { LeakSentry.config }
  // 这里监听页面的销毁
  // 在这里会调用RefWatcher.watch()方法监测Activity的引用
  // 其中RefWatcher在InternalLeakSentry类中创建。
  ActivityDestroyWatcher.install( // 监听 Activity.onDestroy()
      application, refWatcher, configProvider
  )
  // 在这里会创建多个FragmentDestroyWatcher
  // 其内部采用单例的方式,用List列表存储FragmentDestroyWatcher
  // 而具体的FragmentDestroyWatcher其实就是SupportFragmentDestroyWatcher
  // SupportFragmentDestroyWatcher是其接口实现类
  // 这是androidx使用,但是需要fragment可以使用
  // FragmentDestroyWatcher中会监听生命周期的onActivityCreated方法
  // 遍历所有的FragmentDestroyWatcher,调用其watchFragments方法
  // watchFragments方法在SupportFragmentDestroyWatcher的实现,其实就是
  // 注册该fragment的生命周期的监听
  FragmentDestroyWatcher.install( // 监听 Fragment.onDestroy()
      application, refWatcher, configProvider
  )
  // 初始化检测内存泄露过程中需要用到的对象
  listener.onLeakSentryInstalled(application) // Sentry 哨兵
}

这里的listener是LeakSentryListener接口,而实现LeakSentryListener接口的类,其实就是InternalLeakCanary,InternalLeakCanary是在leakcanary-android-core下的,InternalLeakCanary是单例模式的,采用的是kotlin单例,即用object关键字修饰类。

2.InternalLeakCanary#onLeakSentryInstalled

override fun onLeakSentryInstalled(application: Application) {
  this.application = application

  // 用于 heap dump:堆转储
  val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider) 

  val gcTrigger = GcTrigger.Default // 用于手动调用 GC

  val configProvider = { LeakCanary.config } // 配置项

  val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
  handlerThread.start()
  val backgroundHandler = Handler(handlerThread.looper) // 发起内存泄漏检测的线程

  // 堆转存储触发器
  heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, LeakSentry.refWatcher, gcTrigger, heapDumper, configProvider
  )
  application.registerVisibilityListener { applicationVisible ->
    // 这里的applicationVisible其实就是调用扩展函数registerVisibilityListener
    // 的时候,创建的VisibilityTracker对象传入的listener变量
    // 在接收生命周期回调的时候,在onActivityStarted传入true,
    // 在onActivityStopped传入false
    // 这里这里的applicationVisible在onStarted的时候是true
    // 在onStopped的时候是false
    this.applicationVisible = applicationVisible
    // 在applicationVisible是false的时候,其内部才会去检查
    heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
  }
  // 这是添加动态快捷方式,即在手机桌面添加一个LeakCanary的快捷方式,在debug模式下
  addDynamicShortcut(application)
}
// 该方法主要是用于在手机桌面动态生成LeakCanary快捷方式
private fun addDynamicShortcut(application: Application) {
  // 如果系统版本小于25,则不添加动态快捷方式
  if (VERSION.SDK_INT < VERSION_CODES.N_MR1) {
    return
  }
  // 判断是否允许添加动态快捷方式
  if (!application.resources.getBoolean(R.bool.leak_canary_add_dynamic_shortcut)) {
    return
  }

  val shortcutManager = application.getSystemService(ShortcutManager::class.java)!!
  val dynamicShortcuts = shortcutManager.dynamicShortcuts

  val shortcutInstalled =
    dynamicShortcuts.any { shortcut -> shortcut.id == DYNAMIC_SHORTCUT_ID }

  if (shortcutInstalled) {
    return
  }

  val mainIntent = Intent(Intent.ACTION_MAIN, null)
  mainIntent.addCategory(Intent.CATEGORY_LAUNCHER)
  mainIntent.setPackage(application.packageName)
  val activities = application.packageManager.queryIntentActivities(mainIntent, 0)
      .filter {
        it.activityInfo.name != "leakcanary.internal.activity.LeakLauncherActivity"
      }

  if (activities.isEmpty()) {
    return
  }

  val firstMainActivity = activities.first()
      .activityInfo

  // Displayed on long tap on app icon
  val longLabel: String
  // Label when dropping shortcut to launcher
  val shortLabel: String

  val leakActivityLabel = application.getString(R.string.leak_canary_shortcut_label)

  if (activities.isEmpty()) {
    longLabel = leakActivityLabel
    shortLabel = leakActivityLabel
  } else {

    val firstLauncherActivityLabel = if (firstMainActivity.labelRes != 0) {
      application.getString(firstMainActivity.labelRes)
    } else {
      val applicationInfo = application.applicationInfo
      if (applicationInfo.labelRes != 0) {
        application.getString(applicationInfo.labelRes)
      } else {
        applicationInfo.nonLocalizedLabel.toString()
      }
    }
    val fullLengthLabel = "$firstLauncherActivityLabel $leakActivityLabel"
    // short label should be under 10 and long label under 25
    if (fullLengthLabel.length > 10) {
      if (fullLengthLabel.length <= 25) {
        longLabel = fullLengthLabel
        shortLabel = leakActivityLabel
      } else {
        longLabel = leakActivityLabel
        shortLabel = leakActivityLabel
      }
    } else {
      longLabel = fullLengthLabel
      shortLabel = fullLengthLabel
    }
  }

  val componentName = ComponentName(firstMainActivity.packageName, firstMainActivity.name)

  val shortcutCount = dynamicShortcuts.count { shortcutInfo ->
    shortcutInfo.activity == componentName
  } + shortcutManager.manifestShortcuts.count { shortcutInfo ->
    shortcutInfo.activity == componentName
  }

  if (shortcutCount >= shortcutManager.maxShortcutCountPerActivity) {
    return
  }

  val intent = leakDisplayActivityIntent
  intent.action = "Dummy Action because Android is stupid"
  val shortcut = Builder(application, DYNAMIC_SHORTCUT_ID)
      .setLongLabel(longLabel)
      .setShortLabel(shortLabel)
      .setActivity(componentName)
      .setIcon(Icon.createWithResource(application, R.mipmap.leak_canary_icon))
      .setIntent(intent)
      .build()

  try {
    shortcutManager.addDynamicShortcuts(listOf(shortcut))
  } catch (ignored: Throwable) {
    CanaryLog.d(
        ignored,
        "Could not add dynamic shortcut. " +
            "shortcutCount=$shortcutCount, " +
            "maxShortcutCountPerActivity=${shortcutManager.maxShortcutCountPerActivity}"
    )
  }
}

四、FragmentDestroyWatcher.install

这里使用的RefWatcher对象,是在InternalLeakSentry中进行初始化的,然后在调用ActivityDestroyWatcher和FragmentDestroyWatcher的install方法的时候,传入。

internal interface FragmentDestroyWatcher {
    // 实现类是SupportFragmentDestroyWatcher

  fun watchFragments(activity: Activity)

  companion object {

    private const val SUPPORT_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"

    fun install(
      application: Application,
      refWatcher: RefWatcher,
      configProvider: () -> LeakSentry.Config
    ) {
      val fragmentDestroyWatchers = mutableListOf<FragmentDestroyWatcher>()

      if (SDK_INT >= O) { // >= 26,使用 AndroidOFragmentDestroyWatcher
        fragmentDestroyWatchers.add(
            AndroidOFragmentDestroyWatcher(refWatcher, configProvider)
        )
      }

      if (classAvailable(
              SUPPORT_FRAGMENT_CLASS_NAME
          )
      ) {
        fragmentDestroyWatchers.add( 
            // androidx 使用 SupportFragmentDestroyWatcher
            SupportFragmentDestroyWatcher(refWatcher, configProvider)
        )
      }

      if (fragmentDestroyWatchers.size == 0) {
        return
      }

      application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacksAdapter() {
        override fun onActivityCreated(
          activity: Activity,
          savedInstanceState: Bundle?
        ) {
          // 遍历所有的fragmentDestroyWatchers
          // 调用其watchFragments,其实就是调用SupportFragmentDestroyWatcher
          // 中的方法实现
          // 遍历fragmentDestroyWatchers调用watchFragments的时候
          // 其实就是对fragment添加生命周期监听,用于在生命周期回调的时候调用RefWatcher.watch方法
          for (watcher in fragmentDestroyWatchers) {
            watcher.watchFragments(activity)
          }
        }
      })
    }

    private fun classAvailable(className: String): Boolean {
      return try {
        Class.forName(className)
        true
      } catch (e: ClassNotFoundException) {
        false
      }
    }
  }
}
internal class SupportFragmentDestroyWatcher(
  private val refWatcher: RefWatcher,
  private val configProvider: () -> Config
) : FragmentDestroyWatcher {

  // 这是注册给fragment的生命周期监听的
  // 从这里可以看出,fragment销毁的时候,其实也会调用RefWatcher.watch
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        refWatcher.watch(view)
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        refWatcher.watch(fragment)
      }
    }
  }

  override fun watchFragments(activity: Activity) {
    // 这里就是根据传入的FragmentActivity,然后获取supportFragmentManager
    // 对Fragment进行生命周期的监听注册
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
    }
  }
}

五、RefWatcher

在监测Activity和Fragment的生命周期进行内存回收以及是否泄露的过程,就是调用RefWatcher.watch方法进行,该方法是使用Synchronized修饰的同步方法。RefWatcher.watch的方法,一般是在Activity和Fragment生命周期执行到onDestroy的时候调用。根据生命周期监听触发回调,然后调用RefWatcher.watch方法。

class RefWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val onReferenceRetained: () -> Unit,
  /**
   * Calls to [watch] will be ignored when [isEnabled] returns false
   */
  private val isEnabled: () -> Boolean = { true }
) {

  /**
   * References passed to [watch] that haven't made it to [retainedReferences] yet.
   * watch() 方法传进来的引用,尚未判定为泄露
   */
  private val watchedReferences = mutableMapOf<String, KeyedWeakReference>()
  /**
   * References passed to [watch] that we have determined to be retained longer than they should
   * have been.
   * watch() 方法传进来的引用,已经被判定为泄露
   */
  private val retainedReferences = mutableMapOf<String, KeyedWeakReference>()
  // 引用队列,配合弱引用使用,当弱引用中的对象被回收时,接收弱引用对象
  private val queue = ReferenceQueue<Any>() 

  val hasRetainedReferences: Boolean
    @Synchronized get() {
      removeWeaklyReachableReferences()
      return retainedReferences.isNotEmpty()
    }

  val hasWatchedReferences: Boolean
    @Synchronized get() {
      removeWeaklyReachableReferences()
      return retainedReferences.isNotEmpty() || watchedReferences.isNotEmpty()
    }

  val retainedKeys: Set<String>
    @Synchronized get() {
      removeWeaklyReachableReferences()
      return HashSet(retainedReferences.keys)
    }

  /**
   * Identical to [.watch] with an empty string reference name.
   */
  @Synchronized fun watch(watchedReference: Any) {
    watch(watchedReference, "")
  }

  /**
   * Watches the provided references.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  @Synchronized fun watch(
    watchedReference: Any,
    referenceName: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 移除队列中将要被 GC 的引用
    removeWeaklyReachableReferences() 
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 构建当前引用的弱引用对象,并关联引用队列 queue
    // queue是一个ReferenceQueue对象,该对象是用来保存已经回收了的对象的弱引用
    // 即构建给对象构建弱引用对象,当对象被回收的时候,引用该对象的弱引用就会被加入到ReferenceQueue队列的末尾
    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
    // 将引用存入 watchedReferences
    watchedReferences[key] = reference 
    // 这里采用线程池执行
    // 该线程池的赋值是在InternalLeakSentry初始化RefWatcher对象的时候赋值的
    // 该线程池的内部执行是采用mainHandler的方式,切换到主线程进行执行
    /*
    private val checkRetainedExecutor = Executor { // 默认五秒后执行
      mainHandler.postDelayed(it, LeakSentry.config.watchDurationMillis)
    }
    LeakSentry.config.watchDurationMillis的定义是在LeakSentry
    val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
    */
    checkRetainedExecutor.execute {
      moveToRetained(key) // 如果当前引用未被移除,仍在 watchedReferences  队列中,
                          // 说明仍未被 GC,移入 retainedReferences 队列中,暂时标记为泄露
    }
  }

  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableReferences() // 再次调用,防止遗漏
    val retainedRef = watchedReferences.remove(key)
    if (retainedRef != null) {//说明可能存在内存泄漏
      retainedReferences[key] = retainedRef
      onReferenceRetained()
    }
  }

  @Synchronized fun removeRetainedKeys(keysToRemove: Set<String>) {
    retainedReferences.keys.removeAll(keysToRemove)
  }

  @Synchronized fun clearWatchedReferences() {
    watchedReferences.clear()
    retainedReferences.clear()
  }

  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.
    // 弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference? // 队列 queue 中的对象都是会被 GC 的
      if (ref != null) {//说明被释放了
        val removedRef = watchedReferences.remove(ref.key)//获取被释放的引用的key
        if (removedRef == null) {
          retainedReferences.remove(ref.key)
        }
        // 移除 watchedReferences 队列中的会被 GC 的 ref 对象,剩下的就是可能泄露的对象
      }
    } while (ref != null)
  }
}
  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.
    // 弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference? // 队列 queue 中的对象都是会被 GC 的
      if (ref != null) {
        //说明queue中有弱引用对象,说明该弱引用对象引用的对象被释放了
        // 获取被释放的引用的key
        // 从两个map集合中进行释放
        val removedRef = watchedReferences.remove(ref.key)
        // 这里判断为空的原因是,如果在watchedReferences中没有该对象
        // 那么说明该对象已经被判定为泄露,但是这个时候该对象又被回收了,
        // 所以得从这个判定泄露的集合中移除该判定
        if (removedRef == null) {
          retainedReferences.remove(ref.key)
        }
        // 移除 watchedReferences 队列中的会被 GC 的 ref 对象,剩下的就是可能泄露的对象
      }
    } while (ref != null)
  }
  @Synchronized private fun moveToRetained(key: String) {
    // 再次调用,防止遗漏
    removeWeaklyReachableReferences() 
    // 如果移除ReferenceQueue队列中的弱引用之后,
    // 在watchedReferences队列中依然还有该对象,说明该弱引用引用的对象在此时依然不是弱可达
    // 此时就不会移除watchedReferences中的弱引用
    // 那么说明此时就存在内存泄露的可能,则需要将watchedReferences中key对应的弱引用
    // 加入到retainedReferences中,判断该弱引用引用的对象是可能泄露的对象
    val retainedRef = watchedReferences.remove(key)
    if (retainedRef != null) {//说明可能存在内存泄漏
      retainedReferences[key] = retainedRef
      onReferenceRetained()
    }
  }

六、VisibilityTracker

VisibilityTracker其实就是在InternalLeakCanary.onLeakSentryInstalled方法中通过调用application.registerVisibilityListener方法的时候,添加的Application.ActivityLifecycleCallbacks,这里采用适配器模式,使用适配器模式的目的,其实就是不需要重写所有方法,只在VisibilityTracker中重写需要使用的方法。
VisibilityTracker的目的其实就是监听Activity的生命周期变化,即是否是执行到了onStart和onStop,如果是onStop的时候,则做内存泄露监测工作。
VisibilityTracker与ActivityDestroyWatcher有点区别,ActivityDestroyWatcher是最终Activity执行onDestroy的时候进行内存泄露分析

internal class VisibilityTracker(
  private val listener: (Boolean) -> Unit
) :
    ActivityLifecycleCallbacksAdapter() {

  private var startedActivityCount = 0

  /**
   * Visible activities are any activity started but not stopped yet. An activity can be paused
   * yet visible: this will happen when another activity shows on top with a transparent background
   * and the activity behind won't get touch inputs but still need to render / animate.
   */
  private var hasVisibleActivities: Boolean = false

  override fun onActivityStarted(activity: Activity) {
    startedActivityCount++
    if (!hasVisibleActivities && startedActivityCount == 1) {
      hasVisibleActivities = true
      listener.invoke(true)
    }
  }

  override fun onActivityStopped(activity: Activity) {
    // This could happen if the callbacks were registered after some activities were already
    // started. In that case we effectively considers those past activities as not visible.
    if (startedActivityCount > 0) {
      startedActivityCount--
    }
    if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
      hasVisibleActivities = false
      // 这里就是给InternalLeakCanary.onLeakSentryInstalled中注册的application.registerVisibilityListener
      // 传入的listener的回调传入参数为false,表示需要进行内存泄露检测
      listener.invoke(false)
    }
  }
}

internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
  // 当生命周期回调的时候,就会调用VisibilityTracker中重写的方法
  // 而在VisibilityTracker中重写了started和stopped两个方法
  // 在started中调用listener的时候传入的参数是true
  // 在stopped中调用listener的时候传入的参数是false
  // 然后在listener这个接口实现的回调中就会接收该listener的参数
  // 在根据该参数判断是否需要进行内存泄露监测,这里就是回调到了
  // InternalLeakCanary.onLeakSentryInstalled中注册的application.registerVisibilityListener
  // 进而调用到了HeapDumpTrigger.onApplicationVisibilityChanged
  registerActivityLifecycleCallbacks(VisibilityTracker(listener))
}

七、HeapDumpTrigger#onApplicationVisibilityChanged

本方法是在InternalLeakCanary.onLeakSentryInstalled给application添加生命周期回调的时候,根据onStart和onStop生命周期的变化来进行Heap Dump(heap dump文件(.hprof))
当生命周期执行到onStop的时候,会向该Application的扩展函数registerVisibilityListener的参数listener这个高阶函数传入boolean参数为false
看InternalLeakCanary#onLeakSentryInstalled方法中对application添加的生命周期监听,这是调用了application的扩展函数,该扩展函数是在VisibilityTracker中定义的。

    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }

其实registerVisibilityListener方法内部调用的就是application的registerActivityLifecycleCallbacks方法,传入的是Application.ActivityLifecycleCallbacks对象,这里传入的是VisibilityTracker,其实VisibilityTracker就是Application.ActivityLifecycleCallbacks的子类实现。

internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
  registerActivityLifecycleCallbacks(VisibilityTracker(listener))
}

HeapDumpTrigger.onApplicationVisibilityChanged方法的调用,就是根据上述传给VisibilityTracker的listener函数来回调调用的,listener接收的是false的时候,就会调用scheduleRetainedInstanceCheck,接收的是false的时候是生命周期执行到onStop的时候。

  fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
      applicationInvisibleAt = -1L
    } else {
      applicationInvisibleAt = SystemClock.uptimeMillis()
      scheduleRetainedInstanceCheck("app became invisible", LeakSentry.config.watchDurationMillis)
    }
  }

这里的delayMillis默认是5s,因为该参数接收的是LeakSentry.config.watchDurationMillis,这个值初始默认值是5s。

  private fun scheduleRetainedInstanceCheck(
    reason: String,
    delayMillis: Long // 默认 5 s
  ) {
    if (checkScheduled) {
      return
    }
    checkScheduled = true
    // 通过handler切换到主线程调用,确保是在主线程执行,并且延迟5S执行。
    backgroundHandler.postDelayed({
      checkScheduled = false
      checkRetainedInstances(reason)
    }, delayMillis)
  }
  private fun checkRetainedInstances(reason: String) {
    CanaryLog.d("Checking retained instances because %s", reason)
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    if (!config.dumpHeap) {
      return
    }
    // RefWatcher.retainedKeys是一个Set集合,该Set集合是从
    // RefWatcher.retainedReferences中获取的数据
    // RefWatcher.retainedReferences存储的就是已经被判定为泄露的
    var retainedKeys = refWatcher.retainedKeys

    // 当前泄露实例个数小于 5 个,不进行 heap dump
    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountWithDebuggerAttached(retainedKeys.size)
      scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
      CanaryLog.d(
          "Not checking for leaks while the debugger is attached, will retry in %d ms",
          WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    // 可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。
    // 这时候应该主动调用一次 GC,可能可以避免一次 heap dump
    // 即被判定为内存泄露的队列中可能有些引用将要变成弱可达
    // 这个时候就是将被判定为泄露的一些对象,进行再一次回收。
    gcTrigger.runGc()

    retainedKeys = refWatcher.retainedKeys

    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return
    // 为heap dump设置被判定为内存泄露的对应的key集合
    HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys)

    CanaryLog.d("Found %d retained references, dumping the heap", retainedKeys.size)
    HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis()
    dismissNotification()
    // 输出一个heap dump 文件
    val heapDumpFile = heapDumper.dumpHeap() // AndroidHeapDumper
    if (heapDumpFile == null) {
      CanaryLog.d("Failed to dump heap, will retry in %d ms", WAIT_AFTER_DUMP_FAILED_MILLIS)
      scheduleRetainedInstanceCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
      showRetainedCountWithHeapDumpFailed(retainedKeys.size)
      return
    }

    refWatcher.removeRetainedKeys(retainedKeys) // 移除已经 heap dump 的 retainedKeys

    HeapAnalyzerService.runAnalysis(application, heapDumpFile) // 分析 heap dump 文件
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容