Android 主流开源框架(九)LeakCanary 源码解析

前言

最近有个想法——就是把 Android 主流开源框架进行深入分析,然后写成一系列文章,包括该框架的详细使用与源码解析。目的是通过鉴赏大神的源码来了解框架底层的原理,也就是做到不仅要知其然,还要知其所以然。

这里我说下自己阅读源码的经验,我一般都是按照平时使用某个框架或者某个系统源码的使用流程入手的,首先要知道怎么使用,然后再去深究每一步底层做了什么,用了哪些好的设计模式,为什么要这么设计。

系列文章:

更多干货请关注 AndroidNotes

一、使用示例

LeakCanary 是一个 Android 内存泄漏检测框架,2.0 之前与 2.0 之后有较大的改变,例如 2.0 之后使用 Kotlin 重写,分析 hprof 文件的工具由原来的 HAHA 替换成 Shark 等。当然他们的用法也发生了改变,下面对比一下。

2.0 之前
(1)在 app 的 build.gradle 中添加如下依赖:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

(2)在你的 Application 中进行初始化:

public class MyApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      return;
    }
    LeakCanary.install(this);
  }
}

2.0 之后
(1)在 app 的 build.gradle 中添加如下依赖:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
}

(2)没了...

可以看到,2.0 之后只需要添加依赖即可,那它是怎么初始化的呢?
实际它是使用 ContentProvider 实现的,ContentProvider 的 onCreate 方法会在 Application 的 attachBaseContext 方法执行完之后执行。所以 LeakCanary 是利用了这个机制在 onCreate 方法中初始化的,看下源码是不是这样的:

/*AppWatcherInstaller 位于 leakcanary-object-watcher-android 模块*/
internal sealed class AppWatcherInstaller : ContentProvider() {
  ...
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // 这里初始化
    AppWatcher.manualInstall(application)
    return true
  }

  override fun query(...): Cursor? {
    return null
  }

  override fun getType(...): String? {
    return null
  }

  override fun insert(...): Uri? {
    return null
  }

  override fun delete(...): Int {
    return 0
  }

  override fun update(...): Int {
    return 0
  }
}

可以看到,源码中确实是这样的,定义一个类实现 ContentProvider,仅仅只是进行了初始化,其他方法都没有做任何事。

这种方式免去了写一个 Application,然后在这里初始化的步骤,确实提高了用户体验,但是如果所有第三方库都这样做,势必会影响应用的启动速度。LeakCanary 除外,因为它只用在 debug 版本。所以,如果公司内部封装的一些只在 debug 版本使用的库,也可以参考这种做法。

二、源码分析

2.1 InternalAppWatcher#install()

前面初始化的时候调用完 AppWatcher#manualInstall() 后,接着会调用 InternalAppWatcher#install()。看一下这个方法:

  /*InternalAppWatcher*/
  fun install(application: Application) {
    // 检查是否在主线程调用
    checkMainThread()
    // 是否已初始化
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application
    if (isDebuggableBuild) {
      SharkLog.logger = DefaultCanaryLog()
    }
    // 获取配置,用在后面判断是否检测某种类型的泄漏
    val configProvider = { AppWatcher.config }
    // (1)
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    // (2)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    // (3)
    onAppWatcherInstalled(application)
  }

源码中我标记了 3 个关注点,分别如下:

(1)ActivityDestroyWatcher#install()

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

首先创建 ActivityDestroyWatcher 的实例,然后调用 registerActivityLifecycleCallbacks 方法注册 Activity 生命周期的监听,当 Activity 销毁的时候就会回调 onActivityDestroyed 方法。

看下 onActivityDestroyed 方法:

  /*ActivityDestroyWatcher*/
  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"
          )
        }
      }
    }

判断是否检测 Activity,是则调用 ObjectWatcher 的 watch 方法将该 Activity 添加到观察集合中,这里先不分析 watch 方法。

(2)FragmentDestroyWatcher#install()

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

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

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

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

    if (fragmentDestroyWatchers.size == 0) {
      return
    }
    // (4)
    application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })
  }

源码中我标记了 4 个关注点,分别如下:

  • (1):系统版本大于等于 8.0 时,Fragment 用的是 android.app.Fragment,所以使用 AndroidOFragmentDestroyWatcher 检测,然后将它添加到集合中。
  • (2):看下 getWatcherIfAvailable 方法:
  /*FragmentDestroyWatcher*/
  private fun getWatcherIfAvailable(
    fragmentClassName: String,
    watcherClassName: String,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ): ((Activity) -> Unit)? {

    // androidx.fragment.app.Fragment 与 AndroidXFragmentDestroyWatcher 可以找到
    return if (classAvailable(fragmentClassName) &&
        classAvailable(watcherClassName)
    ) {
      // 反射获取 AndroidXFragmentDestroyWatcher 的构造方法
      val watcherConstructor = Class.forName(watcherClassName)
          .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
      // 反射实例化 AndroidXFragmentDestroyWatcher 对象
      @Suppress("UNCHECKED_CAST")
      watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit

    } else {
      null
    }
  }

这里判断引入的是否是 AndroidX 库,如果是则 Fragment 用的是 androidx.fragment.app.Fragment,需要通过反射实例化 AndroidXFragmentDestroyWatcher 对象。

接着使用 fragmentDestroyWatchers.add(it) 将 AndroidXFragmentDestroyWatcher 添加到集合中。

  • (3):与关注点(2)类似,判断引入的是否是 Support 库,如果是则 Fragment 用的是 android.support.v4.app.Fragment,需要通过反射实例化 AndroidSupportFragmentDestroyWatcher 对象,然后添加到集合中。
  • (4):注册 Activity 生命周期的监听,当 Activity 销毁的时候就会回调 onActivityDestroyed 方法,然后遍历 fragmentDestroyWatchers 集合拿到刚刚保存的 xxFragmentDestroyWatcher,接着注册 Fragment 生命周期的监听。

这里的 watcher(activity) 对于不熟悉 kotlin 的人来说可能不太好理解,它其实是使用了 Kotlin 中的高阶函数。我们可以发现 xxFragmentDestroyWatcher 都继承了 (Activity) -> Unit,转成 java 后其实是实现了 Function1 接口,然后重写了它的 invoke 函数。所以这里调用 watcher(activity),实际是调用了 xxFragmentDestroyWatcher 的 invoke 方法。

AndroidOFragmentDestroyWatcher、AndroidSupportFragmentDestroyWatcher 与 AndroidXFragmentDestroyWatcher(多了个 ViewModelClearedWatcher 检测) 逻辑类似,所以这里只分析 AndroidXFragmentDestroyWatcher。看一下 AndroidXFragmentDestroyWatcher:

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?
    ) {
      // (3)
      ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        // (4)
        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) {
        // (5)
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

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

源码中我标记了 5 个关注点,分别如下:

  • (1):注册 Fragment 生命周期的监听。
  • (2):使用 ViewModelClearedWatcher 检测 Activity 中的 ViewModel。
  • (3):Fragment 创建的时候,使用 ViewModelClearedWatcher 检测 Fragment 中的 ViewModel。
  • (4):onFragmentViewDestroyed 生命周期回调的时候将 Fragment 中的 View 添加到观察集合中。
  • (5):onFragmentDestroyed 生命周期回调的时候将 Fragment 添加到观察集合中。

下面再具体看下 ViewModelClearedWatcher:

internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    // (3)
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      @Suppress("UNCHECKED_CAST")
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }
  }

  // (4)
  override fun onCleared() {
    if (viewModelMap != null && configProvider().watchViewModels) {
      viewModelMap.values.forEach { viewModel ->
        objectWatcher.watch(
            viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
        )
      }
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      // (1)
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, objectWatcher, configProvider) as T
      })
      // (2)
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

源码中我标记了 4 个关注点,分别如下:

  • (1):创建 ViewModelClearedWatcher 的实例。
  • (2):将 ViewModel 保存到 ViewModelStore 中的 mMap 集合。
  • (3):反射拿到 ViewModelStore 中的 mMap 集合赋值给 viewModelMap 集合。
  • (4):当不再使用某个 ViewModel 时,会回调 ViewModel 的 onCleared 方法,这个时候检测 ViewModel。

(3)onAppWatcherInstalled()
继续回去看 InternalAppWatcher#install() 中的关注点(3),这里的 onAppWatcherInstalled(application) 也是利用了 Kotlin 中的高阶函数,所以这里实际是调用了 InternalLeakCanary 的 invoke 方法。

那么这个 onAppWatcherInstalled 是哪里赋值的呢?其实是在 init 代码块中:

  /*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
  }

可以看到,这里是通过反射获取 InternalLeakCanary 的实例,然后赋值给 onAppWatcherInstalled。

继续看下 InternalLeakCanary 的 invoke 方法:

  /*InternalLeakCanary */
  override fun invoke(application: Application) {
    _application = application

    checkRunningInDebuggableBuild()

    // (1)
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

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

    // (3)
    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

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

    // (4)
    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    // (5)
    addDynamicShortcut(application)

    Handler().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()
          )
        }
      }
    }
  }

源码中我标记了 5 个关注点,分别如下:

  • (1):添加未回收对象的监听,用于后续回调的时候用。
  • (2):创建 AndroidHeapDumper 的实例,用于生成 hprof 文件。
  • (3):创建 GcTrigger 的实例,用于触发 GC 操作。
  • (4):创建 HeapDumpTrigger 的实例,用于检测。
  • (5):添加进入内存泄漏界面的快捷方式。

到这里 InternalAppWatcher 的 install 方法就分析完了,总结一下。

小结
install 方法主要是将所有需要检测的对象对应的观察者准备好,即 ActivityDestroyWatcher、AndroidOFragmentDestroyWatcher、AndroidXFragmentDestroyWatcher、AndroidSupportFragmentDestroyWatcher 与 ViewModelClearedWatcher。还有准备好后面检测内存泄漏需要用到的 AndroidHeapDumper、GcTrigger、HeapDumpTrigger 等。

2.2 ObjectWatcher#watch()

InternalAppWatcher 的 install 方法已经将所有需要检测的对象对应的观察者,以及后面检测内存泄漏需要用到的类都准备好了。然后每个对象销毁的时候就会调用 ObjectWatcher 的 watch 方法,我们看下这个方法:

  /*ObjectWatcher*/
  // 观察集合
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
  // 引用队列
  private val queue = ReferenceQueue<Any>()

  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    // (1)
    removeWeaklyReachableObjects()
    // 获取唯一标识符
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // (2)
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    ...
    // (3)
    watchedObjects[key] = reference
    // (4)
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

源码中我标记了 4 个关注点,分别如下:

  • (1):removeWeaklyReachableObjects 方法点进去看看:
  /*ObjectWatcher*/
  private fun removeWeaklyReachableObjects() {
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

先看下 KeyedWeakReference:

class KeyedWeakReference(
  referent: Any,
  val key: String,
  val description: String,
  val watchUptimeMillis: Long,
  referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
    referent, referenceQueue
) {}

可以看到,KeyedWeakReference 继承了 WeakReference,并且传入了观察对象和引用队列,所以 KeyedWeakReference 是一个弱引用类型,作用是将需要观察的对象与弱引用关联。

每当发生 GC 时,弱引用所持有的对象就会被回收,并且 JVM 会把该对象放入关联的引用队列中。所以上面 removeWeaklyReachableObjects 方法的意思就是从引用队列中获取 KeyedWeakReference,如果可以获取到说明该对象是已经被回收的对象,则将它从观察集合(watchedObjects)中移除。

  • (2):将需要观察的对象封装到 KeyedWeakReference 中。
  • (3):将 reference 保存到观察集合中。
  • (4):通过执行器执行一个任务,它是在 InternalAppWatcher 中初始化的:
  /*InternalAppWatcher*/
  private val mainHandler by lazy {
    Handler(Looper.getMainLooper())
  }

  private val checkRetainedExecutor = Executor {
    // 其中 val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }

  val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true }
  )

可以看到,内部是使用一个 Handler 发送了一个 5 秒的延迟消息。

继续看 moveToRetained 方法:

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

源码中我标记了 3 个关注点,分别如下:

  • (1):与上面一样,将被回收的对象从观察集合(watchedObjects)中移除。
  • (2):从观察集合中取出 key 对应的观察对象。
  • (3):onObjectRetainedListeners 是前面 InternalLeakCanary 的 invoke 方法中添加的,这里遍历回调 onObjectRetained 方法:
  /*InternalLeakCanary*/
  override fun onObjectRetained() = scheduleRetainedObjectCheck()

  /*InternalLeakCanary*/
  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }

最终走到 HeapDumpTrigger 的 scheduleRetainedObjectCheck 方法:

  /*HeapDumpTrigger*/
  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
  }

如果已经在检测则直接 return,否则使用 Handler 发送一个延迟消息检测未回收的对象。

看下 checkRetainedObjects 方法:

  /*HeapDumpTrigger*/
  private fun checkRetainedObjects() {
    ...
    val config = configProvider()
    ...
    // (1)
    var retainedReferenceCount = objectWatcher.retainedObjectCount

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

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

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    // (4)
    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()
    // (5)
    dumpHeap(retainedReferenceCount, retry = true)
  }

源码中我标记了 5 个关注点,分别如下:

  • (1):获取未回收对象的数量。
  • (2):未回收对象的数量大于 0,则手动执行 GC 操作,然后再获取一次。
  • (3):检查未回收对象的数量,如果小于触发 dump heap 的阈值(5 个),则不继续执行下面的 dump heap。
  • (4):当前时间距离上次 dump head 的时间小于 1 分钟,那么使用 Handler 发送一个延迟消息,等满足一分钟的时候再次调用 checkRetainedObjects 方法检测。
  • (5):执行 dump heap。看一下 dumpHeap 方法:
  /*HeapDumpTrigger*/
  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // (1)
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> {
        if (retry) {
          SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
          // (2)
          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()
        // (3)
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
        // (4)
        HeapAnalyzerService.runAnalysis(
            context = application,
            heapDumpFile = heapDumpResult.file,
            heapDumpDurationMillis = heapDumpResult.durationMillis
        )
      }
    }
  }

源码中我标记了 5 个关注点,分别如下:

  • (1):调用 HeapDumper#dumpHeap() 生成 hprof 文件,内部实际是调用了 AndroidHeapDumper#dumpHeap()。
    看一下这个方法:
  /*AndroidHeapDumper*/
  override fun dumpHeap(): DumpHeapResult {
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
    ...
    return try {
      val durationMillis = measureDurationMillis {
        // 关注点
        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) {
      ...
    } finally {
      ...
    }
  }

可以看到,这里是调用了系统的 Debug#dumpHprofData() 生成 hprof 文件。

  • (2):没有生成 hprof 文件则发送延迟消息重新再检测。
  • (3):有生成 hprof 文件则清除 watchedObjects 中保存的 KeyedWeakReference。
  • (4):最后调用 HeapAnalyzerService#runAnalysis() 进行 hprof 文件的分析。

小结
ObjectWatcher 的 watch 方法主要是检测发生内存泄漏的对象,核心原理是基于 WeakReference 和 ReferenceQueue 实现的。也就是每当发生 GC 时,弱引用所持有的对象就会被回收,并且 JVM 会把该对象放入关联的引用队列中。

具体步骤为:

  1. 将需要观察的对象封装到 KeyedWeakReference 中,这样观察对象和 WeakReference(弱引用)、ReferenceQueue(引用队列) 就进行了关联。
  2. 接着将封装好的 KeyedWeakReference 保存到 watchedObjects(观察集合)。
  3. 每当发生 GC 时,KeyedWeakReference 所持有的对象就会被回收,并加入到引用队列。
  4. 然后遍历引用队列中保存的观察对象,从观察集合中删除这些对象。因为保存在引用队列中的都是已经回收的对象,所以最后观察集合中剩下的就是发生内存泄漏的对象了。
  5. 最后如果未回收的对象大于等于 5 个,就进行 dump head 操作生成 hprof 文件。

2.3 HeapAnalyzerService#runAnalysis()

上面已经找到内存泄漏的对象并且生成了 hprof 文件,那么 runAnalysis 方法就是用来分析该文件的。该方法就不详细分析了,只分析重点流程。先看一下这个方法:

    /*HeapAnalyzerService*/
    fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      heapDumpDurationMillis?.let {
        intent.putExtra(HEAPDUMP_DURATION_MILLIS, heapDumpDurationMillis)
      }
      // 调用下面的 startForegroundService 方法
      startForegroundService(context, intent)
    }

    /*HeapAnalyzerService*/
    private fun startForegroundService(
      context: Context,
      intent: Intent
    ) {
      if (SDK_INT >= 26) {
        context.startForegroundService(intent)
      } else {
        context.startService(intent)
      }
    }

可以看到,这里开启了一个服务来分析 hprof 文件。HeapAnalyzerService 继承了 ForegroundService,ForegroundService 又继承了 IntentService,所以 HeapAnalyzerService 是一个任务执行完成后会自动停止的服务。启动服务后最终会回调到 HeapAnalyzerService 的 onHandleIntentInForeground 方法:

  /*HeapAnalyzerService*/
  override fun onHandleIntentInForeground(intent: Intent?) {
    ...
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS, -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)
      is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }

这里拿到 hprof 文件,然后调用 analyzeHeap 进行分析。点进去看看:

  /*HeapAnalyzerService*/
  private fun analyzeHeap(
    heapDumpFile: File,
    config: Config
  ): HeapAnalysis {
    val heapAnalyzer = HeapAnalyzer(this)
    ...
    return heapAnalyzer.analyze(
        heapDumpFile = heapDumpFile,
        leakingObjectFinder = config.leakingObjectFinder,
        referenceMatchers = config.referenceMatchers,
        computeRetainedHeapSize = config.computeRetainedHeapSize,
        objectInspectors = config.objectInspectors,
        metadataExtractor = config.metadataExtractor,
        proguardMapping = proguardMappingReader?.readProguardMapping()
    )
  }

到这里就开始使用 Shark 库进行 hprof 文件的分析了。看一下 HeapAnalyzer 的 analyze 方法:

  /*HeapAnalyzer*/
  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)
      val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
      // (1)
      sourceProvider.openHeapGraph(proguardMapping).use { graph ->
        // (2)
        val helpers =
          FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
        // (3)
        val result = helpers.analyzeGraph(
            metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
        )
        ...
      }
    } catch (exception: Throwable) {
      ...
    }
  }

可以看到,这里首先生成 heap graph,也就是 heap 的对象关系图。然后将它代入构造一个 FindLeakInput 对象,最后调用 analyzeGraph 方法分析 graph。

看一下 analyzeGraph 方法:

  /*HeapAnalyzer--FindLeakInput*/
  private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
  ): HeapAnalysisSuccess {
    ...
    // (1)
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    // (2)
    val (applicationLeaks, libraryLeaks) = findLeaks(leakingObjectIds)
    // (3)
    return HeapAnalysisSuccess(
        heapDumpFile = heapDumpFile,
        createdAtTimeMillis = System.currentTimeMillis(),
        analysisDurationMillis = since(analysisStartNanoTime),
        metadata = metadata,
        applicationLeaks = applicationLeaks,
        libraryLeaks = libraryLeaks
    )
  }

首先根据 heap graph 找到泄露对象的一组 id,然后根据这些泄漏对象的 id 找到漏对象到 GC roots 的路径,因为一个对象有可能被多个对象引用,为了方便分析这里只保留每个泄漏对象到 GC roots 的最短路劲,最后将 hprof 分析结果返回。

上面的 hprof 分析结果最终会返回到 HeapAnalyzerService#onHandleIntentInForeground() 中:

  /*HeapAnalyzerService*/
  override fun onHandleIntentInForeground(intent: Intent?) {
    ...
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }

接着回调到 DefaultOnHeapAnalyzedListener#onHeapAnalyzed()。如下:

  /*DefaultOnHeapAnalyzedListener*/
  override fun onHeapAnalyzed(heapAnalysis: 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 方法
      showNotification(screenToShow, contentTitle)
    }
  }

  /*DefaultOnHeapAnalyzedListener*/
  private fun showNotification(
    screenToShow: Screen,
    contentTitle: String
  ) {
    // (1)
    val pendingIntent = LeakActivity.createPendingIntent(
        application, arrayListOf(HeapDumpsScreen(), screenToShow)
    )

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

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

可以看到这里构建了一个 PendingIntent,并显示通知栏消息,当点击通知栏消息后就会跳转到 LeakActivity 界面,也就是展示内存泄漏的界面。

到这里,LeakCanary 的源码就分析完了,先总结一下 runAnalysis 方法。

小结
runAnalysis 方法中开启一个 IntentService 服务来分析 hprof 文件,分析库使用的是 Shark。具体步骤是首选生成 heap graph(对象关系图),接着根据 heap graph 找到泄漏对象到 GC roots 的最短路径,最后将 hprof 分析结果返回并显示通知栏消息,当点击通知栏消息后就会跳转到展示内存泄漏的界面。

三、总结

LeakCanary 可以说是众多开源库中使用方法最简单的一个库了,但是里面的逻辑还是很复杂的。而且 2.0 以后用了 kotlin 来重新,对于深入学习 kotlin 也是一个不错的项目,因为很多高级语法糖平时还是很少用到的。

最后再总结一下整个流程:

  1. 首先注册 Activity/Fragment(ViewModel 不需要注册) 生命周期的监听。
  2. 然后每个对象销毁的时候将它们添加到观察集合中(watchedObjects),并且将该对象与弱引用(WeakReference)和引用队列(ReferenceQueque)进行关联。这样每当发生 GC 时,弱引用所持有的对象就会被回收,并加入到引用队列。
  3. 然后遍历引用队列中保存的观察对象,从观察集合中删除这些对象,最后观察集合中剩下的就是发生内存泄漏的对象了。
  4. 最后生成 hprof 文件,并用 Shark 开源库去分析该文件。

关于我

我是 wildmaCSDN 认证博客专家简书程序员优秀作者,擅长屏幕适配
如果文章对你有帮助,点个赞就是对我最大的认可!

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