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 認證博客專家簡書程序員優秀作者,擅長屏幕適配
如果文章對你有幫助,點個贊就是對我最大的認可!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章