“A small leak will sink a great ship.” - Benjamin Franklin

这篇文章来讲讲著名的第三方内存泄漏检测工具 - LeakCanary。

注:本文源代码基于 leakcanary-android:2.3

LeakCanary 的使用非常简单,我们一笔带过:

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
}

配置完 gradle 依赖后,任何代码都不用写,就能直接生效。

神奇吗?神奇就对了。

官方文档上提到,LeakCanary 能够自动检测下面几种对象:

  • 销毁之后的 Activity 实例
  • 销毁之后的 Fragment 实例
  • 销毁之后的 Fragment View 实例
  • 清除之后的 ViewModel 实例

在这几种行为发生后,LeanCanary 会做四个步骤来帮助我们分析是否有内存泄漏:

  1. 检查是否有被保留下的对象
  2. Dump Heap
  3. 分析 HPROF 文件
  4. 对泄漏的部分进行分类

我们来按步骤分析分析它的源码,看它究竟是如何做到的。

检查要被保留的对象

LeanCanary 会监测 Android 组件(并不是全部)的生命周期,在它们销毁的时候,会检测它们是否出现了内存泄漏。

这些对象会被传递到一个 ObjectWatcher 类,它使用 WeakReference 来持有这些实例。当然,你也可以使用这个类来自己观察某个对象是否出现了泄漏。

它的代码如下所示:

/**
 * [ObjectWatcher] can be passed objects to [watch]. It will create [KeyedWeakReference] instances
 * that reference watches objects, and check if those references have been cleared as expected on
 * the [checkRetainedExecutor] executor. If not, these objects are considered retained and
 * [ObjectWatcher] will then notify the [onObjectRetainedListener] on that executor thread.
 *
 * [checkRetainedExecutor] is expected to run its tasks on a background thread, with a significant
 * to give the GC the opportunity to identify weakly reachable objects.
 *
 * [ObjectWatcher] is thread safe.
 */
// Thread safe by locking on all methods, which is reasonably efficient given how often
// these methods are accessed.
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 }
) {

  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

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

  private val queue = ReferenceQueue<Any>()

  ...

  /**
   * Returns the objects that are currently considered retained. Useful for logging purposes.
   * Be careful with those objects and release them ASAP as you may creating longer lived leaks
   * then the one that are already there.
   */
  val retainedObjects: List<Any>
    @Synchronized get() {
      removeWeaklyReachableObjects()
      val instances = mutableListOf<Any>()
      for (weakReference in watchedObjects.values) {
        if (weakReference.retainedUptimeMillis != -1L) {
          val instance = weakReference.get()
          if (instance != null) {
            instances.add(instance)
          }
        }
      }
      return instances
    }

  ...

  /**
   * Watches the provided [watchedObject].
   *
   * @param description Describes why the object is watched.
   */
  @Synchronized fun watch(
    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)
    }
  }

  ...
}

可以看到,ObjectWatcher 内部有这么几个重要的东西:

  • onObjectRetainedListeners,使用者可以手动添加 OnObjectRetainedListener,当有对象被保留时,可以接收到回调;
  • watchedObjects,持有对要观察的对象的 WeakReference。可以看到 LeakCanary 使用了 KeyedWeakReference,它是 WeakReference 的子类,添加了一个字段用于保存对象被观测的时长;
  • queueReferenceQueue,这是 Java 中的特性,当内存中对象的可达性发生变化时,GC 会将变化的对象添加到这个队列中;
  • retainedObjects,可能会发生内存泄漏的对象就保存在这个 List 中,供后续分析使用。

默认情况下,LeakCanary 会将应用中的 Activity、Fragment 等组件注册到 ObjectWatcher 中,那么问题来了,是在何时注册的?

通过扒拉代码,我发现了位于 leakcanary/internal/ 中的这个类: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() {
  ...
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    InternalAppWatcher.install(application)
    return true
  }
  ...
}

厉害了,利用了 Content Provider 会早于 Application 类加载的这个特性,在 onCreate() 方法中调用了 InternalAppWatcher.install() 方法进行初始化的工作:

internal object InternalAppWatcher {

  private val onAppWatcherInstalled: (Application) -> Unit

  ...

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

  ...

  val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { AppWatcher.config.enabled }
  )

  fun install(application: Application) {
    SharkLog.logger = DefaultCanaryLog()
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application

    val configProvider = { AppWatcher.config }
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
  }

  ...

  private fun checkMainThread() {
    if (Looper.getMainLooper().thread !== Thread.currentThread()) {
      throw UnsupportedOperationException(
          "Should be called from the main thread, not ${Thread.currentThread()}"
      )
    }
  }

  ...
}

InternalAppWatcher.install() 方法中,首先检查了当前代码是否运行在主线程上,如果不是,则抛出异常;然后分别调用了 ActivityDestroyWatcher 和 FragmentDestroyWatcher 的 install() 方法,当这些事情都做完后,用了一个秀儿的方法,给 internalLeakCanary 这个成员变量赋值。

来看 ActivityDestroyWatcher:

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {

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

  companion object {
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

它的功能非常简单,直接创建 ActivityDestroyWatcher 的实例,并利用 Application 中的 ActivityLifecycleCallbacks 接口,接收 Activity 的 onActivityDestroyed() 回调,然后将 Activity 交给 ObjectWatcher。

kotlin 中的 by 一般用于实现委托机制,在这里的功能相当于可以省略掉其他的回调的代码。
noOpDelegate 的代码如下:

inline fun <reified T : Any> noOpDelegate(): T {
    val javaClass = T::class.java
    val noOpHandler = InvocationHandler { _, _, _ ->
      // no op
    }
    return Proxy.newProxyInstance(
        javaClass.classLoader, arrayOf(javaClass), noOpHandler
    ) as T
  }

FragmentDestroyWatcher 稍微复杂一些:

internal object FragmentDestroyWatcher {

  private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
  private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME = "leakcanary.internal.AndroidXFragmentDestroyWatcher"

  // Using a string builder to prevent Jetifier from changing this string to Android X Fragment
  @Suppress("VariableNaming", "PropertyName")
  private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME = StringBuilder("android.").append("support.v4.app.Fragment").toString()
  private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME = "leakcanary.internal.AndroidSupportFragmentDestroyWatcher"

  fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ) {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
          AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
      )
    }

    getWatcherIfAvailable(
        ANDROIDX_FRAGMENT_CLASS_NAME,
        ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

    getWatcherIfAvailable(
        ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
        ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

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

    application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })
  }

  private fun getWatcherIfAvailable(
    fragmentClassName: String,
    watcherClassName: String,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ): ((Activity) -> Unit)? {

    return if (classAvailable(fragmentClassName) &&
        classAvailable(watcherClassName)
    ) {
      val watcherConstructor = Class.forName(watcherClassName)
          .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
      @Suppress("UNCHECKED_CAST")
      watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit

    } else {
      null
    }
  }

  private fun classAvailable(className: String): Boolean {
    return try {
      Class.forName(className)
      true
    } catch (e: Throwable) {
      // e is typically expected to be a ClassNotFoundException
      // Unfortunately, prior to version 25.0.2 of the support library the
      // FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
      // Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
      // the FragmentManager.FragmentLifecycleCallbacks class, leading to the
      // AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
      // thrown here. So we're just covering our butts here and catching everything, and assuming
      // any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
      false
    }
  }
}

对 Fragment 的监控是主要采用了两个类:leakcanary.internal.AndroidXFragmentDestroyWatcherleakcanary.internal.AndroidSupportFragmentDestroyWatcher。这两个类的实现方式相同,都是 lambda 表达式,我们只看其中一个:

internal class AndroidXFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      ViewModelClearedWatcher.install(activity, objectWatcher, configProvider)
    }
  }
}

利用了 FragmentManager 中的 FragmentLifecycleCallbacks 接口实现了对 Fragment 生命周期的监听,同时还对 ViewModel 的销毁事件做了监听。

当 Activity 走完自己的 onDestroy() 方法后,Watcher 中的 watch() 方法会被执行(上方代码),然后,利用了 Executor (注意,该 Executor 是在主线程上跑的)开始执行moveToRetained(key)代码:

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

首先移除了所有在 GC 后依旧有可达性的对象,如果全部都移除了,retainedRef 就是 null,则不会触发回调,证明没有内存泄漏;如果移除不了,说明有内存泄漏,开始进行下一步:Dump Heap。

Dump Heap

书接上文,Watcher 开始回调所有的 onObjectRetainedListeners。这些 Listeners 是在何时被添加的呢?

我们回到初始化的部分,也即InternalAppWatcher.install(),其中有这样的代码:

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

onAppWatcherInstalled 这个 Lambda 表达式被执行时,leakcanary.internal.InternalLeakCanary 这个类会被初始化,我们看一下这个类:

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
  private lateinit var heapDumpTrigger: HeapDumpTrigger
  lateinit var application: Application

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

    checkRunningInDebuggableBuild()

    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)

    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)

    disableDumpHeapInTests()
  }

  ...

  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }

  fun onDumpHeapReceived(forceDump: Boolean) {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onDumpHeapReceived(forceDump)
    }
  }

  ...

}

好家伙,我 TM 直接好家伙,这居然又是个 Lambda 表达式,话不多说,直接看 invoke() 回调部分中,有 AppWatcher.objectWatcher.addOnObjectRetainedListener(this) 这样一句,将自己做为被回调方添加到了 watcher 中,那么上面的 onObjectRetainedListener 就找到了实现者。

可以看到,调用了 heapDumpTrigger.onObjectRetained() 方法,我们去看看这里做了什么。

fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false
    )
}

可以看到只是很简单地调用了 scheduleRetainedObjectCheck() 方法:

private fun scheduleRetainedObjectCheck(
  reason: String,
  rescheduling: Boolean,
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  if (checkCurrentlyScheduledAt > 0) {
    val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
    SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
    return
  } else {
    val verb = if (rescheduling) "Rescheduling" else "Scheduling"
    val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
    SharkLog.d { "$verb check for retained objects${delay} because $reason" }
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    checkRetainedObjects(reason)
  }, delayMillis)
}

记录了时间戳之后,开始在子线程中执行 checkRetainedObjects(reason) 方法:

private fun checkRetainedObjects(reason: String) {
  ...

  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(
        reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
        rescheduling = true,
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }

  SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
  dismissRetainedCountNotification()
  dumpHeap(retainedReferenceCount, retry = true)

  ...
}

首次运行时,lastHeapDumpUptimeMillis0,所以代码会执行到 dumpHeap(retainedReferenceCount, retry = true),而后续执行时,如果两次执行的间隔过小(60秒),则直接展示通知,不再 dump heap。

private fun dumpHeap(retainedReferenceCount: Int, retry: Boolean) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  val heapDumpFile = heapDumper.dumpHeap()
  if (heapDumpFile == null) {
    if (retry) {
      SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
      scheduleRetainedObjectCheck(
          reason = "failed to dump heap",
          rescheduling = true,
          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
        )
    )
    return
  }
  lastDisplayedRetainedObjectCount = 0
  lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
  objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
  HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}

可以看到,这里关键的一步是 heapDumper.dumpHeap() 方法,这里 dump 出来的内存数据会直接供后面的 HeapAnalyzerService 使用。此处的 heapDumper 是一个 AndroidHeapDumper 的实例,我们看一下它的代码:

internal class AndroidHeapDumper(
  context: Context,
  private val leakDirectoryProvider: LeakDirectoryProvider
) : HeapDumper {

  private val context: Context = context.applicationContext
  private val mainHandler: Handler = Handler(Looper.getMainLooper())

  override fun dumpHeap(): File? {
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null

    val waitingForToast = FutureResult<Toast?>()
    showToast(waitingForToast)

    if (!waitingForToast.wait(5, SECONDS)) {
      SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
      return null
    }

    val notificationManager =
      context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    if (Notifications.canShowNotification) {
      val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
      val builder = Notification.Builder(context)
          .setContentTitle(dumpingHeap)
      val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
      notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
    }

    val toast = waitingForToast.get()

    return try {
      Debug.dumpHPROFData(heapDumpFile.absolutePath)
      if (heapDumpFile.length() == 0L) {
        SharkLog.d { "Dumped heap file is 0 byte length" }
        null
      } else {
        heapDumpFile
      }
    } catch (e: Exception) {
      SharkLog.d(e) { "Could not dump heap" }
      // Abort heap dump
      null
    } finally {
      cancelToast(toast)
      notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
    }
  }

  private fun showToast(waitingForToast: FutureResult<Toast?>) {
    mainHandler.post(Runnable {
      val resumedActivity = InternalLeakCanary.resumedActivity
      if (resumedActivity == null) {
        waitingForToast.set(null)
        return@Runnable
      }
      val toast = Toast(resumedActivity)
      val iconSize = resumedActivity.resources.getDimensionPixelSize(
          R.dimen.leak_canary_toast_icon_size
      )
      toast.setGravity(Gravity.CENTER_VERTICAL, 0, -iconSize)
      toast.duration = Toast.LENGTH_LONG
      // Inflating with application context: https://github.com/square/leakcanary/issues/1385
      val inflater = LayoutInflater.from(context)
      toast.view = inflater.inflate(R.layout.leak_canary_heap_dump_toast, null)
      toast.show()

      val toastIcon = toast.view.findViewById<View>(R.id.leak_canary_toast_icon)
      toastIcon.translationY = -iconSize.toFloat()
      toastIcon
          .animate()
          .translationY(0f)
          .setListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
              waitingForToast.set(toast)
            }
          })
    })
  }

  private fun cancelToast(toast: Toast?) {
    if (toast == null) {
      return
    }
    mainHandler.post { toast.cancel() }
  }
}

dump 的过程比较简单,可以分为四步:

  1. 创建一个 HPROF 文件,用于存储内存信息;
  2. 展示一个通知方便开发者点击察看;
  3. 展示一个 toast 告诉开发者有内存泄漏并请等待;
  4. 调用 Debug.dumpHPROFData() 方法写入文件。

Debug.dumpHPROFData() 方法是 Android 提供的一个方法,会将 HPROF 信息存储到指定的路径中,同时可能会引起一次 GC。

生成 HPROF 文件之后,就来到了重头戏,第三步——分析 HPROF 文件。

分析 HPROF 文件

上面提到了,分析 HPROF 文件使用的是 HeapAnalyzerService 这个服务,我们来看一下它的实现方法。

HeapAnalyzerService 本质上是一个 IntentService,它继承自 leakcanary.internal.ForegoundService,而 ForegroundService 又继承自 IntentService。

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 config = LeakCanary.config
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
  }

  ...

  companion object {
    private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
    private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"

    fun runAnalysis(
      context: Context,
      heapDumpFile: File
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      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.runAnalysis() 方法之后,就启动了这个前台服务,最终会调用回 onHandleIntentInForeground() 方法,紧接着,在调低进程优先级后,调用了 analyzeHeap() 方法;

private fun analyzeHeap(
  heapDumpFile: File,
  config: Config
): HeapAnalysis {
  val heapAnalyzer = HeapAnalyzer(this)

  val proguardMappingReader = try {
    ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
  } catch (e: IOException) {
    null
  }
  return heapAnalyzer.analyze(
      heapDumpFile = heapDumpFile,
      leakingObjectFinder = config.leakingObjectFinder,
      referenceMatchers = config.referenceMatchers,
      computeRetainedHeapSize = config.computeRetainedHeapSize,
      objectInspectors = config.objectInspectors,
      metadataExtractor = config.metadataExtractor,
      proguardMapping = proguardMappingReader?.readProguardMapping()
  )
}

此处新建了 HeapAnalyzer 的实例并进行 HPROF 文件的处理。

Leakcanary 在分析 HPROF 文件方面进行了一次变革,之前使用的是第三方库 HAHA,后来又着手开发了自己的库叫 Shark。接下来就开始使用到自己的 Shark 库了,HeapAnalyzer 就是这个库中的类。

class HeapAnalyzer constructor(
  private val listener: OnAnalysisProgressListener
) {
  ...
  /**
   * Searches the heap dump for leaking instances and then computes the shortest strong reference
   * path from those instances to the GC roots.
   */
  fun analyze(
    heapDumpFile: File,
    leakingObjectFinder: LeakingObjectFinder,
    referenceMatchers: List<ReferenceMatcher> = emptyList(),
    computeRetainedHeapSize: Boolean = false,
    objectInspectors: List<ObjectInspector> = emptyList(),
    metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
    proguardMapping: ProguardMapping? = null
  ): HeapAnalysis {

    ...

    return try {
      listener.onAnalysisProgress(PARSING_HEAP_DUMP)
      Hprof.open(heapDumpFile)
          .use { hprof ->
            val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
            val helpers = FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
            helpers.analyzeGraph(
                metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
            )
          }
    } catch (exception: Throwable) {
      HeapAnalysisFailure(
          heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
          HeapAnalysisException(exception)
      )
    }
  }
}

在这篇文章中就不具体展开 Shark 对于 HPROF 文件的解析了,有空单开一篇文章讲一讲。

总之,在分析完 HRPOF 文件后,返回分析结果,并交给 config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)

这个 Config 有必要提一下,它是个贯穿始终的角色,里面存储了 N 多的全局变量,是一个 data 类,开发者可以使用 copy() 方法修改 Leakcanary 的默认配置,或者自定义解析器、各种 Listener 等等。此处的 onHeapAnalyzedListener 是在 config 中已经被预定义好的:

/**
  * Called on a background thread when the heap analysis is complete.
  * If you want leaks to be added to the activity that lists leaks, make sure to delegate
  * calls to a [DefaultOnHeapAnalyzedListener].
  *
  * Defaults to [DefaultOnHeapAnalyzedListener]
  */
val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),

我们看一下它的 onHeapAnalyzed() 方法:

class DefaultOnHeapAnalyzedListener(private val application: Application) : OnHeapAnalyzedListener {
  private val mainHandler = Handler(Looper.getMainLooper())

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    SharkLog.d { "$heapAnalysis" }

    val id = LeaksDbHelper(application).writableDatabase.use { db ->
      HeapAnalysisTable.insert(db, heapAnalysis)
    }

    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)
    }
  }
}

不考虑我们是 TV 的情况,会展示一个通知:

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
  )
}

点击这个通知,就会进入 LeakActivity。LeakActivity 就会完整展示内存泄漏的 Reference 路径,方便开发者查找问题。