LeakCanary 原理详解

前言

本文是针对 LeakCanary Version 2.7 (2021-03-26) 版本的源码进行的分析。

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

1. 基础知识

  • java 引用之弱引用 WeakReference

弱引用, 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。
构造方法:

  • public WeakReference(T referent)
  • public WeakReference(T referent, ReferenceQueue<? super T> q)

主要说一下第二构造方法的第二个参数 ReferenceQueue。当WeakReference引用的对象referent被回收后,会把这个WeakReference或其子类的对象,放入队列ReferenceQueue中。敲黑板,LeakCanary 原理中的重要一环。

  • gc

gc 的可达性分析算法和引用计数法;
Java中可以作为GC Roots的对象:
1、虚拟机栈(javaStack)(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中JNI(Native方法)引用的对象。

  • ContentProvider

ContentProvider初始化(onCreate)是在Application生命周期方法attachBaseContext之后、onCreate之前。

2. 源码分析

2.1 流程概述

为了脑中有个框架,便于理解。我将源码的分析过程分成三个步骤,接下来会依次按照这三个步骤去进行分析。图中涉及的类是调用的说明,不存在继承关系。


调用流程分析图.jpg
2.1.1 注册观察对象

注册流程就是LeakCanary的初始化过程,我们首先分析入口类 AppWatcherInstaller

  • AppWatcherInstaller
/**
 * Content providers are loaded before the application class is created. [AppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 */
internal sealed class AppWatcherInstaller : ContentProvider() {

  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()
···
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
···

根据源码可知,AppWatcherInstaller是一个ContentProvider,它在app启动的时候就会自动调用onCreate中的代码。

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher>
  <application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false"/>
  </application>
</manifest>

AppWatcherInstaller 在 manifest 中注册源码如上。接着我们分析 onCreate 中的关键代码 AppWatcher.manualInstall(application)

  • AppWatcher
/**
 * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is
 * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to
 * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to
 * watch any other object that you expect to be unreachable.
 */
object AppWatcher {
···
/**
   * The [ObjectWatcher] used by AppWatcher to detect retained objects.
   * Only set when [isInstalled] is true.
   */
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )
···
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    checkMainThread()
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }
   /**
   * Creates a new list of default app [InstallableWatcher], created with the passed in
   * [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
   * these watchers will pass in to [reachabilityWatcher] objects that they expect to become
   * weakly reachable.
   *
   * The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
   * be used to filter out specific instances.
   */
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
···
}

manualInstall 方法有三个参数,application 会在 AppWatcherInstaller 中传参, retainedDelayMillis 和watchersToInstall会走默认值。这里我们着重看下watchersToInstall 这个参数,这个参数的默认值是appDefaultWatchers方法返回的一个InstallableWatcher列表。列表中有ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher和ServiceWatcher四个对象。四个对象构造方法都有一个公共参数reachabilityWatcher,用objectWatcher进行了赋值。这个列表在源码中遍历,并调用 item 的 install()方法。我们用ActivityWatcher 看看 install() 中干了什么。

/**
 * Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
 * callback.
 */
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

ActivityWatcher.install() 注册了Activity 的生命周期回调,在 onActivityDestroyed 回调的时候调用了reachabilityWatcher.expectWeaklyReachable()方法。其实就是调用了AppWatcher中的objectWatcher对象方法。接着继续看ObjectWatcher

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

  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  /**
   * References passed to [watch].
   */
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

  private val queue = ReferenceQueue<Any>()
···
  @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)
    }
  }
···
  private fun removeWeaklyReachableObjects() {
    // 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) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
···
}

ObjectWatcher 中有两个成员变量,watchedObjects 和 queue 。根据上面对弱引用的了解,queue 存储的是已经回收引用对象的 reference。前面 activity的ondestory生命周期回调引发了expectWeaklyReachable 方法的调用,会首先走removeWeaklyReachableObjects方法,去移除watchedObjects 中已经被回收引用对象的reference,保证watchedObjects中的reference所引用的对象没有被回收。接着为当前 watchedObject 生成一个key 值,然后再放入watchedObjects中。objectWatcher是在AppWatcher中创建的,checkRetainedExecutor 是一个线程池,并通过mainHandler延时5秒执行了一个 runnable。这个runnable 执行会调用ObjectWatcher 的 moveToRetained方法。到这里先停一下,前面选了ActivityWatcher 源码梳理到这里,最终确定了LeakCanary 将已经执行了 onDestory生命周期的activity,通过 KeyedWeakReference 保存在了ObjectWatcher的watchedObjects这个map里。和ActivityWatcher一样,前面其他 三个watcher观测的对象按照一定的策略也会保存在这里。

到这里就梳理了LeakCanary的Watcher注册流程,ContentProvider的子类AppWatchInstaller是一切开始的地方。也分析了观察的可能泄漏的对象在一定的时机会被放在ObjectWatcher的watchedObjects中,比如前面的onActivityDestroyed 回调时机。ObjectWatcher是一个核心类,判断是否有内存泄漏,它是重要的一环。

2.1.2 触发内存检查

LeakCanary 中触发内存检查的时机可以分为两种:

  1. 观测对象可能要销毁的时机
    -- activity的onActivityDestroyed
    -- service的onServiceDestroyed
    -- rootview的onViewDetachedFromWindow
    -- fragment的onFragmentViewDestroyed
  2. 未熄屏的前提下,整个应用app对用户不可见的时候

怎么得出的这个结论,我们接着上一小节继续分析。前面我们分析到了ObjectWatcher的moveToRetained()方法被调用,往下看moveToRetained这个方法。

@Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    // 判断应用对象是否被回收
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // 没有回收,回调监听
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

分析代码,最终回调了OnObjectRetainedListener监听,所以要看哪里添加了监听。添加监听的地方是 InternalLeakCanary 类的 invoke 方法中添加,代码如下

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {

  private const val DYNAMIC_SHORTCUT_ID = "com.squareup.leakcanary.dynamic_shortcut"

  private lateinit var heapDumpTrigger: HeapDumpTrigger

···

  override fun invoke(application: Application) {
    _application = application

    checkRunningInDebuggableBuild()
    //添加回调的地方
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
    )
    // 这里注册应用的可见性回调
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    // 这里添加小鸟的快捷图标
    addDynamicShortcut(application)

    // We post so that the log happens after Application.onCreate()
    mainHandler.post {
      // https://github.com/square/leakcanary/issues/1981
      // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
      // which blocks until loaded and that creates a StrictMode violation.
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
  }
···
override fun onObjectRetained() = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
···
}

分析代码,InternalLeakCanary 实现了 OnObjectRetainedListener ,它就是那个监听。找到重载方法onObjectRetained() ,往下看最终是调用了heapDumpTrigger.scheduleRetainedObjectCheck()。敲黑板,分析到这里得停一下,因为不知道是谁调用了invoke 方法,才添加了监听。看代码 InternalLeakCanary还是一个 Function1,参数为Application,返回值为Unit,所以它重载了invoke()。没有直接调用执行InternalLeakCanary.invoke的地方,那是在哪里调用的。其实在 AppWatcher. manualInstall() 分析的时候有一行代码,这行代码通过代理的方式执行了InternalLeakCanary 这个Function1。

LeakCanaryDelegate.loadLeakCanary(application)
internal object LeakCanaryDelegate {

  @Suppress("UNCHECKED_CAST")
  val loadLeakCanary by lazy {
    try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }

  object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {
    }

    override fun onObjectRetained() {
    }
  }
}

invoke方法除了添加监听,还创建了HeapDumpTrigger对象;添加了一个 application.registerVisibilityListener 的一个回调,这个回调就是这一节最开始说的应用不可见引起内存检查的地方,一会儿再看这块的逻辑;invoke 中执行addDynamicShortcut添加了一个动态快捷方式,敲黑板,桌面上小鸟的由来。这些就是invoke做了的事。回到刚才的分析heapDumpTrigger.scheduleRetainedObjectCheck() ,看这个方法做了哪些事情。

internal class HeapDumpTrigger(
  private val application: Application,
  private val backgroundHandler: Handler,
  private val objectWatcher: ObjectWatcher,
  private val gcTrigger: GcTrigger,
  private val heapDumper: HeapDumper,
  private val configProvider: () -> Config
) {
  ···
  private fun checkRetainedObjects() {
    val iCanHasHeap = HeapDumpControl.iCanHasHeap()

    val config = configProvider()

    if (iCanHasHeap is Nope) {
      if (iCanHasHeap is NotifyingNope) {
        // Before notifying that we can't dump heap, let's check if we still have retained object.
        var retainedReferenceCount = objectWatcher.retainedObjectCount

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

        val nopeReason = iCanHasHeap.reason()
        val wouldDump = !checkRetainedCount(
          retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
        )

        if (wouldDump) {
          val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
          onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
          showRetainedCountNotification(
            objectCount = retainedReferenceCount,
            contentText = uppercaseReason
          )
        }
      } else {
        SharkLog.d {
          application.getString(
            R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
          )
        }
      }
      return
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

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

    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
        objectCount = retainedReferenceCount,
        contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> {
        if (retry) {
          SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
          scheduleRetainedObjectCheck(
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
          )
        } else {
          SharkLog.d { "Failed to dump heap, will not automatically retry" }
        }
        showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
            R.string.leak_canary_notification_retained_dump_failed
          )
        )
      }
      is HeapDump -> {
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
        )
      }
    }
  }

  private fun saveResourceIdNamesToMemory() {
    val resources = application.resources
    AndroidResourceIdNames.saveToMemory(
      getResourceTypeName = { id ->
        try {
          resources.getResourceTypeName(id)
        } catch (e: NotFoundException) {
          null
        }
      },
      getResourceEntryName = { id ->
        try {
          resources.getResourceEntryName(id)
        } catch (e: NotFoundException) {
          null
        }
      })
  }

  fun onDumpHeapReceived(forceDump: Boolean) {
    backgroundHandler.post {
      dismissNoRetainedOnTapNotification()
      gcTrigger.runGc()
      val retainedReferenceCount = objectWatcher.retainedObjectCount
      if (!forceDump && retainedReferenceCount == 0) {
        SharkLog.d { "Ignoring user request to dump heap: no retained objects remaining after GC" }
        @Suppress("DEPRECATION")
        val builder = Notification.Builder(application)
          .setContentTitle(
            application.getString(R.string.leak_canary_notification_no_retained_object_title)
          )
          .setContentText(
            application.getString(
              R.string.leak_canary_notification_no_retained_object_content
            )
          )
          .setAutoCancel(true)
          .setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION))
        val notification =
          Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
        notificationManager.notify(
          R.id.leak_canary_notification_no_retained_object_on_tap, notification
        )
        backgroundHandler.postDelayed(
          scheduleDismissNoRetainedOnTapNotification,
          DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS
        )
        lastDisplayedRetainedObjectCount = 0
        return@post
      }

      SharkLog.d { "Dumping the heap because user requested it" }
      dumpHeap(retainedReferenceCount, retry = false, "user request")
    }
  }

  private fun checkRetainedCount(
    retainedKeysCount: Int,
    retainedVisibleThreshold: Int,
    nopeReason: String? = null
  ): Boolean {
    val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
    lastDisplayedRetainedObjectCount = retainedKeysCount
    //  没有泄漏的对象
    if (retainedKeysCount == 0) {
      if (countChanged) {
        SharkLog.d { "All retained objects have been garbage collected" }
        onRetainInstanceListener.onEvent(NoMoreObjects)
        showNoMoreRetainedObjectNotification()
      }
      return true
    }

    val applicationVisible = applicationVisible
    val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod

    if (countChanged) {
      val whatsNext = if (applicationVisible) {
        if (retainedKeysCount < retainedVisibleThreshold) {
          "not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)"
        } else {
          if (nopeReason != null) {
            "would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason"
          } else {
            "dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)"
          }
        }
      } else if (applicationInvisibleLessThanWatchPeriod) {
        val wait =
          AppWatcher.config.watchDurationMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt)
        if (nopeReason != null) {
          "would dump heap in $wait ms (app just became invisible) but $nopeReason"
        } else {
          "dumping heap in $wait ms (app just became invisible)"
        }
      } else {
        if (nopeReason != null) {
          "would dump heap now (app is invisible) but $nopeReason"
        } else {
          "dumping heap now (app is invisible)"
        }
      }

      SharkLog.d {
        val s = if (retainedKeysCount > 1) "s" else ""
        "Found $retainedKeysCount object$s retained, $whatsNext"
      }
    }
      // 内存显露的watchObject 少于5个,会先显示一个通知暂不走堆栈分析,等待2秒再走一次判断
    if (retainedKeysCount < retainedVisibleThreshold) {
      if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
        if (countChanged) {
          onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
        }
        showRetainedCountNotification(
          objectCount = retainedKeysCount,
          contentText = application.getString(
            R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
          )
        )
        scheduleRetainedObjectCheck(
          delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
        )
        return true
      }
    }
    return false
  }

  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      //  前一次 checkRetainedObjects 还没执行
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
  }
···
}

scheduleRetainedObjectCheck 会走到 checkRetainedCount 方法,该方法中会通过objectWatcher.retainedObjectCount 去判断是否有可能泄露的对象。count > 0 则会通过 gcTrigger.runGc() 进行一次 gc操作,然后再获取count进行判断,如果count仍然>0则调用dumpHeap 方法进行堆栈信息分析,调用到 HeapAnalyzerService.runAnalysis()方法。到这里就是判定已经有内存泄漏,往下就是对堆栈进行分析了。关于触发内存检查的时机,上面我们已经分析完了观测对象可能要销毁的时机中的activity的onActivityDestroyed,因为我们是选取ActivityWatcher 进行了分析,其他三种就不在这里分析赘述了。
那再看看第二种时机,未锁屏的前提下,整个应用app对用户不可见的时候。上面其实已经分析到了,在InternalLeakCanary的invoke方法添加了applicationVisible的回调。

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

看下registerVisibilityListener这个拓展方法

internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
  val visibilityTracker = VisibilityTracker(listener)
  registerActivityLifecycleCallbacks(visibilityTracker)
  registerReceiver(visibilityTracker, IntentFilter().apply {
    addAction(ACTION_SCREEN_ON)
    addAction(ACTION_SCREEN_OFF)
  })
}

可见性这块逻辑就是通过注册广播接收者接收系统屏幕息屏与否的广播,注册ActivityLifecycle 来判断当前是否有用户可见的activity。这块逻辑是在VisibilityTracker 中处理的,可见性变化就会调用InternalLeakCanary的invoke中的可见性回调,接着就会调用 heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible),进入这个方法

 /**
   ** 未锁屏的前提下,整个应用app对用户不可见的时候引发内存检查
   **/
  fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
      applicationInvisibleAt = -1L
    } else {
      applicationInvisibleAt = SystemClock.uptimeMillis()
      // Scheduling for after watchDuration so that any destroyed activity has time to become
      // watch and be part of this analysis.
      scheduleRetainedObjectCheck(
        delayMillis = AppWatcher.config.watchDurationMillis
      )
    }
  }

分析可知当 applicationVisible 为false 的时候,也就是不可见的时候,回调用到scheduleRetainedObjectCheck -> checkRetainedObjects -> dumpHeap ->HeapAnalyzerService.runAnalysis() 。应用可见性这个时机也就走到堆栈分析这一步了。

2.1.3 分析内存堆栈

上一小节分析到 HeapAnalyzerService.runAnalysis()

fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) 

该方法有个 heapDumpFile 参数,这个参数就是进行堆栈分析的文件。这个文件怎么来的,追溯参数对象创建的地方,是 AndroidHeapDumper.dumpHeap()方法。文件的创建以及数据的写入就是通过下面一段代码

override fun dumpHeap(): DumpHeapResult {
    // 创建空文件
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
    ···
    return try {
      val durationMillis = measureDurationMillis {
        // 将 dump hprof 数据写入到空文件中
        Debug.dumpHprofData(heapDumpFile.absolutePath)
      }
      if (heapDumpFile.length() == 0L) {
        SharkLog.d { "Dumped heap file is 0 byte length" }
        NoHeapDump
      } else {
        HeapDump(file = heapDumpFile, durationMillis = durationMillis)
      }
    } catch (e: Exception) {
      SharkLog.d(e) { "Could not dump heap" }
      // Abort heap dump
      NoHeapDump
    } finally {
      cancelToast(toast)
      notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
    }
  }

看源码可知,要分析的堆栈信息是通过android sdk 中的 Debug.dumpHprofData()方法来获取堆栈信息的。有文件之后,接着看HeapAnalyzerService.runAnalysis() 的实现

internal class HeapAnalyzerService : ForegroundService(
  HeapAnalyzerService::class.java.simpleName,
  R.string.leak_canary_notification_analysing,
  R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {

  override fun onHandleIntentInForeground(intent: Intent?) {
    if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
      SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
      return
    }

    // Since we're running in the main process we should be careful not to impact it.
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    val heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
    val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)

    val config = LeakCanary.config
    // 分析堆栈,拿到分析结果
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    val fullHeapAnalysis = when (heapAnalysis) {
      is HeapAnalysisSuccess -> heapAnalysis.copy(
        dumpDurationMillis = heapDumpDurationMillis,
        metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
      )
      is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    // 将分析结果回调
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }
···
  companion object {
    private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
    private const val HEAPDUMP_DURATION_MILLIS_EXTRA = "HEAPDUMP_DURATION_MILLIS_EXTRA"
    private const val HEAPDUMP_REASON_EXTRA = "HEAPDUMP_REASON_EXTRA"
    private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"

    fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
      heapDumpDurationMillis?.let {
        intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
      }
      startForegroundService(context, intent)
    }

    private fun startForegroundService(
      context: Context,
      intent: Intent
    ) {
      if (SDK_INT >= 26) {
        context.startForegroundService(intent)
      } else {
        // Pre-O behavior.
        context.startService(intent)
      }
    }
  }
}

代码中开启了HeapAnalyzerService这个前台服务,服务开启会调用 onHandleIntentInForeground 中的逻辑。onHandleIntentInForeground会调用 analyzeHeap 拿到分析结果,然后将分析结果通过config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)进行回调给DefaultOnHeapAnalyzedListener的onHeapAnalyzed方法

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
    // 将分析结果存入数据库,用于在activity中查看
    val db = LeaksDbHelper(application).writableDatabase
    val id = HeapAnalysisTable.insert(db, heapAnalysis)
    db.releaseReference()

    val (contentTitle, screenToShow) = when (heapAnalysis) {
      is HeapAnalysisFailure -> application.getString(
        R.string.leak_canary_analysis_failed
      ) to HeapAnalysisFailureScreen(id)
      is HeapAnalysisSuccess -> {
        val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
        val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
        application.getString(
          R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
        ) to HeapDumpScreen(id)
      }
    }

    if (InternalLeakCanary.formFactor == TV) {
      showToast(heapAnalysis)
      printIntentInfo()
    } else {
      // 显示通知信息
      showNotification(screenToShow, contentTitle)
    }
  }
  private fun showNotification(
    screenToShow: Screen,
    contentTitle: String
  ) {
    val pendingIntent = LeakActivity.createPendingIntent(
      application, arrayListOf(HeapDumpsScreen(), screenToShow)
    )

    val contentText = application.getString(R.string.leak_canary_notification_message)

    Notifications.showNotification(
      application, contentTitle, contentText, pendingIntent,
      R.id.leak_canary_notification_analysis_result,
      LEAKCANARY_MAX
    )
  }

该回调中将分析结果存入数据库,展示一条通知,点击通知回调转到pendingIntent 的目标页面 LeakActivity 展示数据库中的信息。关于如何得到分析hprof文件结果的,其实就是 HeapAnalyzerService 的 analyzeHeap() 方法返回值,一直查看调用,实质调用的是 FindLeakInput 的 analyzeGraph 拓展方法

private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
  ): HeapAnalysisSuccess {
    listener.onAnalysisProgress(EXTRACTING_METADATA)
    val metadata = metadataExtractor.extractMetadata(graph)

    val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
      .filter { it.isRetained && !it.hasReferent }.count()

    // This should rarely happens, as we generally remove all cleared weak refs right before a heap
    // dump.
    val metadataWithCount = if (retainedClearedWeakRefCount > 0) {
      metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances")
    } else {
      metadata
    }

    listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS)
    // 查找到泄露对象id
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    // 查找泄露对象并分成应用泄露、库泄露和其他泄露
    val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)

    return HeapAnalysisSuccess(
      heapDumpFile = heapDumpFile,
      createdAtTimeMillis = System.currentTimeMillis(),
      analysisDurationMillis = since(analysisStartNanoTime),
      metadata = metadataWithCount,
      applicationLeaks = applicationLeaks,
      libraryLeaks = libraryLeaks,
      unreachableObjects = unreachableObjects
    )
  }

到这里就把LeakCanary核心流程梳理结束了,了解了LeakCanary 是如何检测内存泄漏、提示以及展示的。

LeakCanary 主页
LeakCanary GitHub
关于Java中的WeakReference

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

推荐阅读更多精彩内容