三方庫源碼筆記(5)-LeakCanary 源碼詳解

對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java

公衆號:字節數組android

系列文章導航:git

LeakCanary 是由 Square 公司開源的用於 Android 的內存泄漏檢測工具,能夠幫助開發者發現內存泄露狀況而且找出泄露源頭,有助於減小 OutOfMemoryError 狀況的發生。在目前的應用開發中也算做是性能優化的一個重要實現途徑,不少面試官在考察性能優化時都會問到 LeakCanary 的實現原理github

本文就基於其當前(2020/10/06)的最新一次提交來進行源碼分析,具體的 Git 版本節點是:9f62126e,來了解 LeakCanary 的總體運行流程和實現原理 😂😂面試

1、支持的內存泄露類型

咱們常常說 LeakCanary 能檢測到應用內發生的內存泄露,那麼它到底具體支持什麼類型的內存泄露狀況呢?LeakCanary 官網有對此進行介紹:數組

LeakCanary automatically detects leaks of the following objects:性能優化

  • destroyed Activity instances
  • destroyed Fragment instances
  • destroyed fragment View instances
  • cleared ViewModel instances

咱們也能夠從 LeakCanary 的 AppWatcher.Config 這個類找到答案。Config 類用於配置是否開啓內存檢測,從其配置項就能夠看出來 leakcanary 支持:Activity、Fragment、FragmentView、ViewModel 等四種類型markdown

data class Config(
    /** * Whether AppWatcher should automatically watch destroyed activity instances. * * Defaults to true. */
    val watchActivities: Boolean = true,

    /** * Whether AppWatcher should automatically watch destroyed fragment instances. * * Defaults to true. */
    val watchFragments: Boolean = true,

    /** * Whether AppWatcher should automatically watch destroyed fragment view instances. * * Defaults to true. */
    val watchFragmentViews: Boolean = true,

    /** * Whether AppWatcher should automatically watch cleared [androidx.lifecycle.ViewModel] * instances. * * Defaults to true. */
    val watchViewModels: Boolean = true,

    /** * How long to wait before reporting a watched object as retained. * * Default to 5 seconds. */
    val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),

    /** * Deprecated, this didn't need to be a part of the API. * Used to indicate whether AppWatcher should watch objects (by keeping weak references to * them). Currently a no-op. * * If you do need to stop watching objects, simply don't pass them to [objectWatcher]. */
    @Deprecated("This didn't need to be a part of LeakCanary's API. No-Op.")
    val enabled: Boolean = true
  )
複製代碼

2、初始化

現在,咱們在項目中引入 LeakCanary 只須要添加以下依賴便可,無須任何的初始化行爲等附加操做,當應用啓動時 LeakCanary 就會自動啓動並開始監測了app

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
複製代碼

通常來講,像這類第三方庫都是須要由外部傳入一個 ApplicationContext 對象進行初始化並啓動的,LeakCanary 的 1.x 版本也是如此,但在 2.x 版本中,LeakCanary 將初始過程交由 AppWatcherInstaller 這個 ContentProvider 來自動完成dom

internal sealed class AppWatcherInstaller : ContentProvider() {

    /** * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
    internal class MainProcess : AppWatcherInstaller()

    /** * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`, * [LeakCanaryProcess] automatically sets up the LeakCanary code */
    internal class LeakCanaryProcess : AppWatcherInstaller()

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

    ···
}
複製代碼

因爲 ContentProvider 會在 Application 被建立以前就由系統調用其 onCreate() 方法來完成初始化,因此 LeakCanary 經過 AppWatcherInstaller 就能夠拿到 Context 來完成初始化並隨應用一塊兒啓動,經過這種方式就簡化了使用者的引入成本。並且因爲咱們的引用方式是 debugImplementation,因此正式版本會自動移除對 LeakCanary 的全部引用,進一步簡化了引入成本

Jetpack 也包含了一個組件來實現經過 ContentProvider 來完成初始化的邏輯AppStartup。在實現思路上二者很相似,可是若是每一個三方庫都經過自定義 ContentProvider 來實現初始化的話,那麼應用的啓動速度就會很感人了吧 :joy:,因此 Jetpack 官方推出的 AppStartup 應該是之後的主流纔對

AppWatcherInstaller 最終會將 Application 對象傳給 InternalAppWatcherinstall(Application) 方法

/** * Note: this object must load fine in a JUnit environment */
internal object InternalAppWatcher {

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

  fun install(application: Application) {
    //檢測是否在 main 線程
    checkMainThread()
    //避免重複初始化
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application
    if (isDebuggableBuild) {
      SharkLog.logger = DefaultCanaryLog()
    }
	//拿到默認配置,默認四種類型都進行檢測
    val configProvider = { AppWatcher.config }
    //檢測 Activity
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    //檢測 Fragment、FragmentView、ViewModel
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
  }

  ···
      
}
複製代碼

LeakCanary 具體進行內存泄露檢測的邏輯能夠分爲三類:

  • ObjectWatcher。檢測 Object 的內存泄露狀況
  • ActivityDestroyWatcher。檢測 Activity 的內存泄露狀況
  • FragmentDestroyWatcher。檢測 Fragment、FragmentView、ViewModel 的內存泄露狀況

當中,ActivityDestroyWatcherFragmentDestroyWatcher 都須要依靠 ObjectWatcher 來完成,由於 Activity、Fragment、FragmentView、ViewModel 本質上都屬於不一樣類型的 Object

3、ObjectWatcher:檢測任意對象

咱們知道,當一個對象再也不被咱們引用時,若是該對象因爲代碼錯誤或者其它緣由致使遲遲沒法被系統回收,此時就是發生了內存泄露。那麼 LeakCanary 是怎麼知道應用是否發生了內存泄露呢?

這個能夠依靠引用隊列 ReferenceQueue 來實現。先來看個小例子

/** * 做者:leavesC * 時間:2020/10/06 14:26 * 描述: * GitHub:https://github.com/leavesC */
fun main() {
    val referenceQueue = ReferenceQueue<Pair<String, Int>?>()
    var pair: Pair<String, Int>? = Pair("name", 24)
    val weakReference = WeakReference(pair, referenceQueue)

    println(referenceQueue.poll()) //null

    pair = null

    System.gc()
    //GC 後休眠一段時間,等待 pair 被回收
    Thread.sleep(4000)

    println(referenceQueue.poll()) //java.lang.ref.WeakReference@d716361
}
複製代碼

能夠看到,在 GC 事後 referenceQueue.poll() 的返回值變成了非 null,這是因爲 WeakReferenceReferenceQueue 的一個組合特性致使的:在聲明一個 WeakReference 對象時若是同時傳入了 ReferenceQueue 做爲構造參數的話,那麼當 WeakReference 持有的對象被 GC 回收時,JVM 就會把這個弱引用存入與之關聯的引用隊列之中。依靠這個特性,咱們就能夠實現內存泄露的檢測了

例如,當用戶按返回鍵退出 Activity 時,正常狀況下該 Activity 對象應該在不久後就被系統回收,咱們能夠監聽 Activity 的 onDestroy 回調,在回調時把 Activity 對象保存到和 ReferenceQueue 關聯的 WeakReference 中,在一段時間後(能夠主動觸發幾回 GC)檢測 ReferenceQueue 中是否有值,若是一直爲 null 的話就說明發生了內存泄露。LeakCanary 就是經過這種方法來實現的

ObjectWatcher 中就封裝了上述邏輯,這裏來看看其實現邏輯

ObjectWatcher 的起始方法是 watch(Any, String),該方法就用於監聽指定對象

/** * References passed to [watch]. * 用於保存要監聽的對象,mapKey 是該對象的惟一標識、mapValue 是該對象的弱引用 */
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

    //KeyedWeakReference 關聯的引用隊列
    private val queue = ReferenceQueue<Any>()

	/** * Watches the provided [watchedObject]. * * @param description Describes why the object is watched. */
    @Synchronized
    fun watch(watchedObject: Any, description: String) {
        if (!isEnabled()) {
            return
        }
        removeWeaklyReachableObjects()
        //爲 watchedObject 生成一個惟一標識
        val key = UUID.randomUUID().toString()
        val watchUptimeMillis = clock.uptimeMillis()
        //建立 watchedObject 關聯的弱引用
        val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
        ···
        watchedObjects[key] = reference
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }
複製代碼

watch() 方法的主要邏輯:

  1. 爲每一個 watchedObject 生成一個惟一標識 key,經過該 key 構建一個 watchedObject 的弱引用 KeyedWeakReference,將該弱引用保存到 watchedObjects 中。ObjectWatcher 能夠前後監測多個對象,每一個對象都會先被存入到 watchedObjects
  2. 外部經過傳入的 checkRetainedExecutor 來指定檢測內存泄露的觸發時機,經過 moveToRetained 方法來判斷是否真的發生了內存泄露

KeyedWeakReference 是一個自定義的 WeakReference 子類,包含一個惟一 key 來標識特定對象,也包含一個 retainedUptimeMillis 字段用來標記是否發生了內存泄露

class KeyedWeakReference(
        referent: Any,
        val key: String,
        val description: String,
        val watchUptimeMillis: Long,
        referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
        referent, referenceQueue
) {
    /** * Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't * been yet. * 用於標記 referent 是否還未被回收,是的話則值不爲 -1 */
    @Volatile
    var retainedUptimeMillis = -1L

    companion object {
        @Volatile
        @JvmStatic
        var heapDumpUptimeMillis = 0L
    }

}
複製代碼

moveToRetained 方法就用於判斷指定 key 關聯的對象是否已經泄露,若是沒有泄露則移除對該對象的弱引用,有泄露的話則更新其 retainedUptimeMillis 值,以此來標記其發生了泄露,並同時經過回調 onObjectRetainedListeners 來分析內存泄露鏈

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

    //若是判斷到一個對象沒有發生內存泄露,那麼就移除對該對象的弱引用
    //此方法會前後調用屢次
    private fun removeWeaklyReachableObjects() {
        // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
        // reachable. This is before finalization or garbage collection has actually happened.
        var ref: KeyedWeakReference?
        do {
            ref = queue.poll() as KeyedWeakReference?
            if (ref != null) {
                //若是 ref 不爲 null,說明 ref 關聯的對象沒有發生內存泄露,那麼就移除對該對象的引用
                watchedObjects.remove(ref.key)
            }
        } while (ref != null)
    }
複製代碼

4、ActivityDestroyWatcher:檢測Activity

理解了 ObjectWatcher 的流程後來看 ActivityDestroyWatcher 就會比較簡單了。ActivityDestroyWatcher 會向 Application 註冊一個 ActivityLifecycleCallbacks 回調,當收到每一個 Activity 執行了 onDestroy 的回調後,就會將將 Activity 對象轉交由 ObjectWatcher 來進行監聽

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

}
複製代碼

5、FragmentDestroyWatcher:檢測Fragment

作 Android 應用開發的應該都知道,如今 Google 提供的基礎依賴包分爲了 SupportAndroidX 兩種,Support 版本已經再也不維護,主流的都是使用 AndroidX 了。而 LeakCanary 爲了照顧老項目,就貼心的爲這兩種版本分別提供了 Fragment 的內存檢測功能

FragmentDestroyWatcher 能夠看作是一個分發器,它會根據外部環境的不一樣來選擇不一樣的檢測手段,其主要邏輯是:

  • 系統版本大於等於 8.0。使用 AndroidOFragmentDestroyWatcher 來檢測 Fragment、FragmentView 的內存泄露
  • 開發者使用的是 Support 包。使用 AndroidSupportFragmentDestroyWatcher 來檢測 Fragment、FragmentView 的內存泄露
  • 開發者使用的是 AndroidX 包。使用 AndroidXFragmentDestroyWatcher 來檢測 Fragment、FragmentView、ViewModel 的內存泄露
  • 經過反射 Class.forName 來判斷開發者使用的是 Support 包仍是 AndroidX 包
  • 因爲 Fragment 都須要被掛載在 Activity 上,全部向 Application 註冊一個 ActivityLifecycleCallback,每當有 Activity 被建立時就監聽該 Activity 內可能存在的 Fragment

這裏令我很疑惑的一個點就是:當系統版本大於等於 8.0 時,AndroidOFragmentDestroyWatcher 不就會和 AndroidSupportFragmentDestroyWatcher 或者 AndroidXFragmentDestroyWatcher 重複了嗎?這算咋回事:joy:

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

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

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

  ···
}
複製代碼

因爲 AndroidXFragmentDestroyWatcherAndroidSupportFragmentDestroyWatcherAndroidOFragmentDestroyWatcher 在邏輯上很相似,且就 AndroidXFragmentDestroyWatcher 同時提供了 ViewModel 內存泄露的檢測功能,因此這裏只看 AndroidXFragmentDestroyWatcher 就行

AndroidXFragmentDestroyWatcher 的主要邏輯是:

  • 在 invoke 方法裏向 Activity 的 FragmentManager 以及 childFragmentManager 註冊一個 FragmentLifecycleCallback,經過該回調拿到 onFragmentViewDestroyed 和 onFragmentDestroyed 的事件通知,收到通知時就經過 ObjectWatcher 啓動檢測
  • 在 onFragmentCreated 回調裏經過 ViewModelClearedWatcher 來啓動和 Fragment 關聯的 ViewModel 的內存泄露檢測邏輯
  • 在 invoke 方法裏經過 ViewModelClearedWatcher 來啓動和 Activity 關聯的 ViewModel 的內存泄露檢測
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)
    }
  }
}
複製代碼

Fragment 和 FragmentView 走向 Destroyed 時,正常狀況下它們都是不會被複用的,應該會很快就被 GC 回收,且它們本質上都只是一種對象,因此直接使用 ObjectWatcher 進行檢測便可

6、ViewModelClearedWatcher:檢測ViewModel

和 Fragment、FragmentView 相比,ViewModel 就比較特殊了,因爲可能存在一個 Activity 和多個 Fragment 同時持有一個 ViewModel 實例的狀況,而 leakcanary 沒法知道 ViewModel 究竟是同時被幾個持有者所持有,因此沒法經過單獨一個 Activity 和 Fragment 的 Destroyed 回調來啓動對 ViewModel 的檢測。幸虧 ViewMode 也提供了 onCleared() 的回調事件,leakcanary 就經過該回調來知道 ViewModel 是何時須要被回收。對 ViewModel 的實現原理不清楚的同窗能夠看個人這篇文章:從源碼看 Jetpack(6)-ViewModel源碼詳解

ViewModelClearedWatcher 的主要邏輯是:

  • ViewModelClearedWatcher 繼承於 ViewModel,當拿到 ViewModelStoreOwner 實例(Activity 或者 Fragment)後,就建立一個和該實例綁定的 ViewModelClearedWatcher 對象
  • ViewModelClearedWatcher 經過反射獲取到 ViewModelStore 中的 mMap 變量,該變量就存儲了全部的 Viewmodel 實例
  • 當 ViewModelClearedWatcher 的 onCleared() 方法被回調了,就說明了全部和 Activity 或者 Fragment 綁定的 ViewModel 實例都再也不被須要了,此時就能夠開始監測全部的 ViewModel 實例了
internal class ViewModelClearedWatcher(
        storeOwner: ViewModelStoreOwner,
        private val objectWatcher: ObjectWatcher,
        private val configProvider: () -> Config
) : ViewModel() {

    private val viewModelMap: Map<String, ViewModel>?

    init {
        // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
        // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
        // does not have ViewModelStore#keys. All versions currently have the mMap field.
        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
        }
    }

    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) {
            val provider = ViewModelProvider(storeOwner, object : Factory {
              @Suppress("UNCHECKED_CAST")
              override fun <T : ViewModel?> create(modelClass: Class<T>): T =
                      ViewModelClearedWatcher(storeOwner, objectWatcher, configProvider) as T
            })
            provider.get(ViewModelClearedWatcher::class.java)
        }
    }
}
複製代碼

7、檢測到內存泄露後的流程

咱們不可能在 Activity 剛被回調了 onDestroy 方法就立刻來判斷 ReferenceQueue 中是否有值,由於 JVM 的 GC 時機是不肯定的,Activity 對象可能不會那麼快就被回收,因此須要延遲一段時間後再來檢測。而即便延遲檢測了,也可能會存在應用沒有發生內存泄露只是系統還未執行 GC 的狀況,因此就須要去主動觸發 GC,通過幾輪檢測後才能夠肯定當前應用是否的確發生了內存泄露

這裏就來看下具體的檢測流程

ObjectWatcher 對象包含了一個 Executor 參數:checkRetainedExecutor。檢測操做的觸發時機就取決於向 checkRetainedExecutor 提交的任務在何時會被執行

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

    ···

    /** * 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)
        }
    }
    
    //判斷 key 關聯的對象是否已經泄露
    //是的話則將更新其 retainedUptimeMillis 值,以此來標記其發生了泄露
    @Synchronized
    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects()
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            //重點,向外發出可能有內存泄露的通知
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    ···
    
}
複製代碼

ObjectWatcher 對象又是在 InternalAppWatcher 裏初始化的,checkRetainedExecutor 在收到任務後會經過 Handler 來延時五秒執行

internal object InternalAppWatcher {

  ···	
    
  private val mainHandler by lazy {
    Handler(Looper.getMainLooper())
  }

  private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }
  
  val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true }
  )

  ···

}
複製代碼

ObjectWatchermoveToRetained 方法又會經過 onObjectRetained 向外發出通知:當前可能發生了內存泄露InternalLeakCanary 會收到這個通知,而後交由 HeapDumpTrigger 來進行檢測

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
   
    private lateinit var heapDumpTrigger: HeapDumpTrigger
    
    override fun onObjectRetained() = scheduleRetainedObjectCheck()

	fun scheduleRetainedObjectCheck() {
    	if (this::heapDumpTrigger.isInitialized) {
      		heapDumpTrigger.scheduleRetainedObjectCheck()
    	}
 	}
    
    ···
    
}
複製代碼

當 LeakCanary 斷定當前真的存在內存泄露時,就會進行 DumpHeap,找到泄露對象的引用鏈,而這個操做是比較費時費內存的,可能會直接致使應用頁面無響應,因此 LeakCanary 進行 DumpHeap 前會有許多前置檢查操做和前置條件,就是爲了儘可能減小 DumpHeap 次數以及在 DumpHeap 時儘可能減小對開發人員的干擾

heapDumpTriggerscheduleRetainedObjectCheck() 方法的主要邏輯是:

  1. 獲取當前還未回收的對象個數 retainedKeysCount。若是個數大於 0,則先主動觸發 GC,儘可能嘗試回收對象,避免誤判,而後執行第二步;若是個數爲 0,那麼流程就結束了
  2. GC 事後再次更新 retainedKeysCount 值,若是對象都被回收了(即 retainedKeysCount 值爲 0),那麼流程就結束了,不然就執行第三步
  3. 若是 retainedKeysCount 小於閾值 5,且當前「應用處於前臺」或者是「應用處於後臺但退到後臺的時間還未超出五秒」,那麼就啓動一個定時任務,在二十秒後從新執行第一步,不然執行第四步
  4. 若是上一次 DumpHeap 離如今不足一分鐘,那麼就啓動一個定時任務,滿一分鐘後從新執行第一步,不然執行第五步
  5. 此時各個條件都知足了,已經能夠肯定發生了內存泄漏,去執行 DumpHeap
internal class HeapDumpTrigger(
        private val application: Application,
        private val backgroundHandler: Handler,
        private val objectWatcher: ObjectWatcher,
        private val gcTrigger: GcTrigger,
        private val heapDumper: HeapDumper,
        private val configProvider: () -> Config
) {

    ···
    
    fun scheduleRetainedObjectCheck( delayMillis: Long = 0L ) {
        val checkCurrentlyScheduledAt = checkScheduledAt
        if (checkCurrentlyScheduledAt > 0) {
            //若是當前已經在進行檢測了,則直接返回
            return
        }
        checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
        backgroundHandler.postDelayed({
          checkScheduledAt = 0
          checkRetainedObjects()
        }, delayMillis)
    }

    private fun checkRetainedObjects() {
        val iCanHasHeap = HeapDumpControl.iCanHasHeap()

        val config = configProvider()

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

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

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

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

        //獲取當前還未回收的對象個數
        var retainedReferenceCount = objectWatcher.retainedObjectCount

        if (retainedReferenceCount > 0) {
            //主動觸發 GC,儘可能嘗試回收對象,避免誤判
            gcTrigger.runGc()
            retainedReferenceCount = objectWatcher.retainedObjectCount
        }

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

        val now = SystemClock.uptimeMillis()
        val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis

        //若是上一次 DumpHeap 離如今不足一分鐘,那麼就啓動一個定時任務,滿一分鐘後再次檢查
        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()
        //各個條件都知足了,已經能夠肯定發生了內存泄漏,去執行 DumpiHeap
        dumpHeap(retainedReferenceCount, retry = true)
    }

    /** * 判斷當前是否符合 DumpHeap 的條件,符合的話返回 false * @param retainedKeysCount 當前還未回收的對象個數 * @param retainedVisibleThreshold 觸發 DumpHeap 的閾值 * 只有當 retainedKeysCount 大於等於 retainedVisibleThreshold 時纔會觸發 DumpHeap,默認值是 5 * @param nopeReason */
    private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int, nopeReason: String? = null ): Boolean {
        //用於標記本次檢測相對上次,未回收的對象個數是否發生了變化
        val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
        lastDisplayedRetainedObjectCount = retainedKeysCount
        if (retainedKeysCount == 0) {
            if (countChanged) {
                //若是 retainedKeysCount 爲 0,且值相對上次檢測減小了,則說明有對象被回收了
                SharkLog.d { "All retained objects have been garbage collected" }
                onRetainInstanceListener.onEvent(NoMoreObjects)
                showNoMoreRetainedObjectNotification()
            }
            return true
        }

        //應用是否還在前臺
        val applicationVisible = applicationVisible
        val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod

        ···

        if (retainedKeysCount < retainedVisibleThreshold) { //還未達到閾值
            if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
                if (countChanged) {
                    onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
                }
                //在通知欄顯示當前未回收的對象個數
                showRetainedCountNotification(
                        objectCount = retainedKeysCount,
                        contentText = application.getString(
                                R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
                        )
                )
                //retainedKeysCount 還未達到閾值,且當前「應用處於前臺」或者是「應用處於後臺但退到後臺的時間還未超出五秒」
                //此時就啓動一個定時任務,在二十秒後從新再檢測一遍
                scheduleRetainedObjectCheck(
                        delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
                )
                return true
            }
        }
        return false
    }

   ···

}
複製代碼

更後面的流程就涉及具體的 DumpHeap 操做了,這裏就再也不展開了,由於我也不太懂,後續有機會再單獨寫一篇文章來介紹了~~

8、小提示

一、檢測任意對象

除了 LeakCanary 默認支持的四種類型外,咱們還能夠主動檢測任意對象。例如,能夠檢測 Service:

class MyService : Service {

  // ...

  override fun onDestroy() {
    super.onDestroy()
    AppWatcher.objectWatcher.watch(
      watchedObject = this,
      description = "MyService received Service#onDestroy() callback"
    )
  }
}
複製代碼

二、更改配置項

LeakCanary 提供的默認配置項大多數狀況已經很適合咱們在項目中直接使用了,而若是咱們想要更改 LeakCanary 的默認配置項(例如不但願檢測 FragmentView),能夠在 Application 中進行更改:

class DebugExampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
  }
}
複製代碼

因爲 LeakCanary 的引用方式是 debugImplementation,在 releas 環境下是引用不到 LeakCanary 的,因此爲了不在生成 release 包時須要主動來刪除這行配置項,須要將 DebugExampleApplication 放到 src/debug/java 文件夾中

9、結尾

能夠看出 Activity、Fragment、FragmentView、ViewModel 等四種類型的內存檢測都是須要依靠 ObjectWatcher 來完成的,由於這四種類型本質上都是屬於不一樣的對象。而 ObjectWatcher 須要依靠引用隊列 ReferenceQueue 來實現,所以 LeakCanary 的基本實現基礎就是來源於 Java 的原生特性

LeakCanary 的總體源碼講得也差很少了,後邊就再來寫一篇關於內存泄露的擴展閱讀 😂😂

相關文章
相關標籤/搜索