0.前言

Android开发中,内存泄露是引发OOM的一大原因,为了避免内存泄露的发生,通常使用LeakCanary来检测是否发生了内存泄露,接下来就根据LeakCanary源码(版本 2.8.1 ),来看看是怎么实现内存泄露的检测的。

1.初始化

自2.0版本之后,LeakCanary都不需要在代码中主动初始化,只需要在build.gradle中引入项目即可:

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

这是因为项目在自己的ContentProvider中自动初始化了
这里有一个知识点,那就是ContentProvider的onCreate()方法,会在Application的onCreate()方法之前执行。
项目中的相关源码如下:
leakcanary/leakcanary-object-watcher-android/src/main/AndroidManifest.xml

  <application>
    <provider
        android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false"/>
  </application>
/**
 * Content providers are loaded before the application class is created. [MainProcessAppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 *
 * [MainProcessAppWatcherInstaller] automatically sets up the LeakCanary code that runs in the main
 * app process.
 */
internal class MainProcessAppWatcherInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }

  override fun query(
    uri: Uri,
    projectionArg: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
  ): Cursor? = null

  override fun getType(uri: Uri): String? = null

  override fun insert(uri: Uri, contentValues: ContentValues?): Uri? = null

  override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

  override fun update(
    uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
  ): Int = 0
}

可以看到,在MainProcessAppWatcherInstaller这个ContentProvider的子类中,只在onCreate()方法下初始化了LeakCanary库,其他的方法全是空实现。

2.回收监听

首先要知道,LeakCanary检测什么对象的泄露?
官网有如下的描述:

LeakCanary automatically detects leaks of the following objects:
destroyed Activity instances
destroyed Fragment instances
destroyed fragment View instances
cleared ViewModel instances

所以,LeakCanary是在检测Activity,Fragment,Fragment中的View,ViewModel这几种对象是否泄露,接下来看看是怎么监听对象的。

初始化方法为 AppWatcher.manualInstall(application),源码如下:

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

这里有两个关键信息,一个是retained时间默认5秒(后文会详细说retained是什么),另一个是默认观察者是appDefaultWatchers,appDefaultWatchers的源码如下:

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

看到这里,已经豁然开朗,这些观察者就是在对不同的对象进行监听,除了上文说到的4个外,还可以监听Service,查了一下更新日志,这是在Version 2.6 - Christmas Release 🎄 (2020-12-24)版本新增的功能,应该是官网首页没有及时更新显示内容。

再跟踪ActivityWatcher方法,源码如下:

/**
 * 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) {
          //注释1
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

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

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

监听activity的生命周期,当发生destory后,触发注释1处的检测,最后是由ObjectWatcher.expectWeaklyReachable()方法处理,其他几个的监听和activity类似,最终都交给ObjectWatcher.expectWeaklyReachable()方法处理。

3.回收检测

上文说到,当监听到对象的destory后,触发ObjectWatcher.expectWeaklyReachable()方法执行回收检测,源码如下:

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

先根据当前对象,生成一个弱应用对象,然后放到队列中,5秒后检测队列里面的弱引用对象是否已经回收。
使用removeWeaklyReachableObjects和moveToRetained方法实现,
removeWeaklyReachableObjects是移除所有已经回收的引用,
moveToRetained是在此5秒后执行(5秒就是上文提到的retained时间),再次移除所有回收的引用,如果这个时候还有引用存在,说明对象没用被回收,存在泄露的风险。

4.回调处理

当检测到泄露的对象,通过OnObjectRetainedListener.onObjectRetained()回调处理,处理内容包括获取内存快照,生成hprof文件,解析hprof文件,找到内存泄露对象,计算到对象到GC roots的最短路径并合并成一棵树,输出分析结果等。
关于回调处理的过程,过于复杂且不是本文的重点,不过多介绍。