偿还技术债(5)-LeakCanary源码详解

作者: 业志陈 | 来源:发表于2020-10-15 14:24 被阅读0次

    国庆假期想着闲着也是闲着,就想着来深入了解下几个常用的开源库😁😁,看下其实现原理和源码,进行总结并输出成文章。初定的目标是 EventBus、ARouter、LeakCanary、Glide、Coil、Retrofit、OkHttp 等几个。目前已经完成了部分,在之后的几天里会将文章陆续发布出来😁😁
    原文地址:https://juejin.im/user/923245496518439/posts

    LeakCanary 是由 Square 公司开源的用于 Android 的内存泄漏检测工具,可以帮助开发者发现内存泄露情况并且找出泄露源头,有助于减少 OutOfMemoryError 情况的发生。在目前的应用开发中也算作是性能优化的一个重要实现途径,很多面试官在考察性能优化时都会问到 LeakCanary 的实现原理

    本文就基于其当前(2020/10/06)的最新一次提交来进行源码分析,具体的 Git 版本节点是:9f62126e,来了解 LeakCanary 的整体运行流程和实现原理 😎😎

    一、支持的内存泄露类型

    我们经常说 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 等四种类型

    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
      )
    

    二、初始化

    如今,我们在项目中引入 LeakCanary 只需要添加如下依赖即可,无须任何的初始化行为等附加操作,当应用启动时 LeakCanary 就会自动启动并开始监测了

    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 来自动完成

    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

    三、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)检测 WeakReference 中是否有值,如果一直为 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)
        }
    

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

    五、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 进行检测即可

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

    七、检测到内存泄露后的流程

    我们不可能在 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 操作了,这里就不再展开了,因为我也不太懂,后续有机会再单独写一篇文章来介绍了~~

    八、小提示

    1、检测任意对象

    除了 LeakCanary 默认支持的四种类型外,我们还可以主动检测任意对象。例如,可以检测 Service:

    class MyService : Service {
    
      // ...
    
      override fun onDestroy() {
        super.onDestroy()
        AppWatcher.objectWatcher.watch(
          watchedObject = this,
          description = "MyService received Service#onDestroy() callback"
        )
      }
    }
    

    2、更改配置项

    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 文件夹中

    九、结尾

    可以看出 Activity、Fragment、FragmentView、ViewModel 等四种类型的内存检测都是需要依靠 ObjectWatcher 来完成的,因为这四种类型本质上都是属于不同的对象。而 ObjectWatcher 需要依靠引用队列 ReferenceQueue 来实现,因此 LeakCanary 的基本实现基础就是来源于 Java 的原生特性

    LeakCanary 的整体源码讲得也差不多了,后边就再来写一篇关于内存泄露的扩展阅读😎😎

    相关文章

      网友评论

        本文标题:偿还技术债(5)-LeakCanary源码详解

        本文链接:https://www.haomeiwen.com/subject/qavzuktx.html