浅谈LeakCanary2源码(一)

简介: 浅谈LeakCanary2源码

LeakCanary是一个内存泄漏检测的工具。那么,内存泄漏是如何定义的,通常有哪些情况呢?
一,在基于 Java 的运行时中,内存泄漏是一种编程错误,它导致应用程序保留对不再需要的对象的引用。因此,为该对象分配的内存无法回收,最终导致OutOfMemoryError (OOM)崩溃

二,大多数内存泄漏是由与对象生命周期相关的错误引起的。以下是一些常见的 Android 错误:

  • Fragment实例添加到 backstack 而不清除该 Fragment 的视图字段Fragment.onDestroyView()(在此 StackOverflow 答案中的更多详细信息)。
  • Activity实例作为Context字段存储在由于配置更改而在活动重新创建中幸存下来的对象中。
  • 注册一个侦听器、广播接收器或 RxJava 订阅,这些订阅引用具有生命周期的对象,并且在生命周期结束时忘记注销。

LeakCanary2和LeakCanary的区别

LeakCanary 2.0使用非常简单,只需要在build.gradle中配置一下,无需在项目中添加任何代码。

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

    原理就是利用ContentProvider的特性,其onCreate方法会在Application的onCreate方法之前被系统调用。所以只需要在AndroidManifest.xml中配置一下这个ContentProvider,然后在onCreate方法中进行初始化即可。


    ContentProvider除了在onCreate方法中进行了初始化处理,其他方法都是空实现。在上面的第26行,onCreate方法中初始化代码为InternalAppWatcher.install(application)

    InternalAppWatcher.install(Application)方法完成了LeakCanary的初始化操作,里面涉及到协作的一些类的定义。


    leakcanary-object-watcher-android/src/main/java/leakcanary/internal/InternalAppWatcher.kt

      internal object InternalAppWatcher {
        // 通过判断lateinit修饰的Application是否已经初始化,来判断LeakCanary是否已经安装
        val isInstalled
          get() = ::application.isInitialized
        // LeakCanary安装完成的回调,实际上对应的是leakcanary.internal.InternalLeakCanary这个类
        private val onAppWatcherInstalled: (Application) -> Unit
        val isDebuggableBuild by lazy {
          (application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
        }
        lateinit var application: Application
        // ObjectWatcher参数1
        private val clock = object : Clock {
          override fun uptimeMillis(): Long {
            return SystemClock.uptimeMillis()
          }
        }
        private val mainHandler = Handler(Looper.getMainLooper())
        init {
          // 这里为什么要这么费劲的获取该object的对象呢?
          // 因为InternalLeakCanary类是上层模块的,这里没有办法直接引用
          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
        }
        // 主线程抛出任务,delay 5s
        private val checkRetainedExecutor = Executor {
          mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
        }
        val objectWatcher = ObjectWatcher(
            clock = clock,
            checkRetainedExecutor = checkRetainedExecutor,
            isEnabled = { AppWatcher.config.enabled }
        )
        fun install(application: Application) {
          ...
          checkMainThread()
          // lateinit修饰的对象可以通过::<field>.isInitialized来判断有没有初始化
          // 如果已经初始化了,则不需要再次install
          if (this::application.isInitialized) {
            return
          }
          InternalAppWatcher.application = application
          // 安装ActivityDestroyWatcher、FragmentDestroyWatcher
          val configProvider = { AppWatcher.config }
          ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
          FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
          // 通知上层模块的InternalLeakCanary.invoke方法
          onAppWatcherInstalled(application)
        }
        ...
      }

      在上面的这个install方法中,干了三件事:1. 初始化一个ObjectWatcher对象:其clock就是SystemClock.uptimeMillis()checkRetainedExecutor是利用主线程Handler执行的任务 2. Activity、Fragment销毁的观察者的安装 3. 通知InternalLeakCanary,watcher已经安装完成

      由于Activity、Fragment的销毁不会立刻发生,所以我们先看看第3点中,InternalLeakCanary做了什么工作。
      InternalLeakCanary的部分声明为object InternalLeakCanary : (Application) -> Unit,所以它到底实现了什么呢。这其实对应kotlin中Functions.kt文件的Function1接口,P1为Application,R为Unit即void:

        /** A function that takes 1 argument. */
        public interface Function1<in P1, out R> : Function<R> {
            /** Invokes the function with the specified argument. */
            public operator fun invoke(p1: P1): R
        }
        所以接着看InternalLeakCanary.invoke方法即可,该方法也是完成了一些初始化操作,如下:
        InternalLeakCanary.kt
        override fun invoke(application: Application) {
          this.application = application
          // 内存泄漏时回调该类的方法
          AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
          val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
          // gcTrigger使用的默认的
          val gcTrigger = GcTrigger.Default
          val configProvider = { LeakCanary.config }
          // HandlerThread + Handler 来处理后台任务
          val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
          handlerThread.start()
          val backgroundHandler = Handler(handlerThread.looper)
          heapDumpTrigger = HeapDumpTrigger(
              application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
              configProvider
          )
          // 自定义的扩展方法来检测App是否可见(处于前台)
          application.registerVisibilityListener { applicationVisible ->
            this.applicationVisible = applicationVisible
            heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
          }
          // 动态添加Shortcut
          addDynamicShortcut(application)
          disableDumpHeapInInstrumentationTests()
        }

        其实上面这些初始化的变量,大部分我们都不会在检测内存泄漏时遇到,这些变量大部分都与dump heap有关。
        这里展开说说其中的“自定义的扩展方法来检测App是否可见(处于前台)”这里面的实现原理,这个需求在日常开发中也会用到,也是通过向Application注册Activity生命周期回调,通过计算start-stop的Activity个数来实现的:

        leakcanary-android-core/src/main/java/leakcanary/internal/VisibilityTracker.kt

          internal class VisibilityTracker(
            private val listener: (Boolean) -> Unit
          ) :
              Application.ActivityLifecycleCallbacks by noOpDelegate() {
            private var startedActivityCount = 0
            /**
             * Visible activities are any activity started but not stopped yet. An activity can be paused
             * yet visible: this will happen when another activity shows on top with a transparent background
             * and the activity behind won't get touch inputs but still need to render / animate.
             */
            private var hasVisibleActivities: Boolean = false
            override fun onActivityStarted(activity: Activity) {
              startedActivityCount++
              if (!hasVisibleActivities && startedActivityCount == 1) {
                hasVisibleActivities = true
                listener.invoke(true)
              }
            }
            override fun onActivityStopped(activity: Activity) {
              // This could happen if the callbacks were registered after some activities were already
              // started. In that case we effectively considers those past activities as not visible.
              if (startedActivityCount > 0) {
                startedActivityCount--
              }
              if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
                hasVisibleActivities = false
                listener.invoke(false)
              }
            }
          }
          internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
            registerActivityLifecycleCallbacks(VisibilityTracker(listener))
          }

          上面就是LeakCanary的初始化代码了,接下来说说Activity与Fragment是如何检测内存泄漏的。

          如何观察Activity、Fragment

          在上一节LeakCanary2初始化中,我们还没有讲解下面两行代码

          ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
          FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

          这两行代码都是通过向Application注册registerActivityLifecycleCallbacks从而获取每个启动的Activity,然后

          • 对于Activity而言,直接观察Activity即可
          • 对于Fragment而言,由于Fragment需要依附于Activity,且需要从Activity中获取FragmentManager,然后通过其registerFragmentLifecycleCallbacks方法观察Fragment

          上面两行代码就是这个实现逻辑。先看看Activity的观察逻辑:

          leakcanary-object-watcher-android/src/main/java/leakcanary/internal/ActivityDestroyWatcher.kt


          internal class ActivityDestroyWatcher private constructor(
            private val objectWatcher: ObjectWatcher,
            private val configProvider: () -> Config
          ) {
            private val lifecycleCallbacks =
              object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
                // noOpDelegate()是一个动态代理的实现,不过里面没有写任何逻辑 所以是no op
                // 此处相当于一个适配器方法
                override fun onActivityDestroyed(activity: Activity) {
                  if (configProvider().watchActivities) {
                    objectWatcher.watch(activity)
                  }
                }
              }
            companion object {
              fun install(
                application: Application,
                objectWatcher: ObjectWatcher,
                configProvider: () -> Config
              ) {
                val activityDestroyWatcher =
                  ActivityDestroyWatcher(objectWatcher, configProvider)
                application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
              }
            }
          }

          上面的逻辑很简单,就是对于每个Activity,在其onDestroy方法调用之后,调用objectWatcher.watch观察这个Activity。该方法的逻辑我们下一节再说。

          然后我们看看Fragment里面的观察逻辑,由于Fragment有两种:1. android.app.Fragment 2. support包里面的androidx.fragment.app.Fragment

          前者的FragmentLifecycleCallbacks有API Level限制,限制为O;后者则没有API限制了,但是有androidx限制。所以FragmentDestroyWatcher会判断这两个的是否满足条件,满足条件后才会进行观察。

          相关文章
          |
          Java API Android开发
          浅谈LeakCanary2源码(二)
          浅谈LeakCanary2源码(二)
          44 0
          |
          Java Android开发
          Android体系课之--LeakCanary内存泄露检测原理解析
          #### 内存泄露 不需要的对象实例,无法被垃圾回收,比如被静态片段保留,就说可能发生内存泄露 ##### 常见场景: - 1.不清楚fragment视图的字段的情况下,将fragment添加到backstack中 - 2.Activity以context的形式被添加到一些类中,比如静态类,则gc无法清除,如Activity被非静态内部类Handler引用 - 3.注册一个监听器,广播接收器或者RxJava订阅时,引用了一个生命周期的对象,生命周期结束后,没有取消注册
          |
          Android开发
          在安卓项目中使用 Leakcanary 内存泄露检测工具
          使用 (一)导入 导入 Leakcanary-watcher、Leakcanary-analyzer、Leakcanary-android, 在当前项目的引用 Leakcanary-android 这个 library。
          249 0
          |
          消息中间件 存储 Java
          LeakCanary 源码解析
          LeakCanary 是由 Square 开源的针对 Android 和 Java 的内存泄漏检测工具。
          |
          Java Android开发
          【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 )
          【Android 热修复】热修复原理 ( 类加载分析 | 分析 PathClassLoader 源码 | 分析 BaseDexClassLoader 源码 | 分析 PathDexList 源码 )
          156 0
          |
          Java
          LeakCanary原理解析
          简介 LeakCanary是一款开源的内存泄漏检查工具,在项目中,可以使用它来检测Activity是否能够被GC及时回收。github的地址为https://github.com/square/leakcanary 使用方式解析 将LeakCanary引入AS,在Application中调用如下方法,可以跟踪Activity是否被GC回收。
          1207 0
          |
          存储 Java Android开发
          Java内存问题 及 LeakCanary 原理分析
          在安卓等其他移动平台上,内存问题显得特别重要,想要做到虚拟机内存的高效利用,及内存问题的快速定位,了解下虚拟机内存模块及管理相关知识是很有必要的,这篇文章将从最基础的知识分析,内存问题的产生地方、原因、解决方案等原理。
          4116 0
          |
          编解码 自然语言处理 C语言
          x264源代码分析-转
          相关说明: 1.     使用版本:  x264-cvs-2004-05-11 2.     这次的分析基本上已经将代码中最难理解的部分做了阐释,对代码的主线也做了剖析,如果这个主线理解了,就容易设置几个区间,进行分工阅读,将各个区间击破了.
          980 0