【9】LeakCanary 2.x内存泄漏检测分析

前述:LeakCanary 2.x已经完全基于Kotlin重构升级了,在使用方面相比之前也出现了不同,这次针对LeakCanary 2.x对内存检测流程进行分析,了解LeakCanary内部是如何实现内存泄漏检测的。

1. 使用

在2.7版本当中,我们直接引入依赖即可,无需通过代码调用LeakCanary.install的方式进行初始话,LeakCanary会在app初始时自动初始化自身。

implementation "com.squareup.leakcanary:leakcanary-android:2.7"
	Application->attachBaseContext 
=====>
	ContentProvider->onCreate 
=====>
	Application->onCreate 
=====>
	Activity->onCreate 

2. 分析

2.1 入口

在AppWatcherInstaller.class 中注释解释是

Content providers are loaded before the application class is created. [AppWatcherInstaller] is 
used to install [leakcanary.AppWatcher] on application start.

内容提供者被加载在应用创建完成之前。AppWatcherInstaller 用于安装leakcanary.AppWatcher在应用启动。

通过AppWatcherInstaller.class可以了解leakCanary的入口就是在AppWatcher调用安装的时候。

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // 初始化观察者的入口处
    AppWatcher.manualInstall(application)
    return true
  }

2.2 检测对象

在fun manualInstall(…) 中,有一个参数watchersToInstall,官方给出的解释是

[watchersToInstall] can be customized to a subset of the default app watchers:

可以自定义为默认应用程序观察者的子集
 @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    // 默认观察者
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    checkMainThread()
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }

通过点进appDefaultWatchers(application)这个方法中可以看到leakCanary检测的对象。

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
    // activity,fragment,rootview,service被观察
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }

在这里插入图片描述
同样在manualInstall下面,调用LeakCanaryDelegate.loadLeakCanary(application)利用反射创建一个InternalLeakCanary的实例,并传入application参数,执行它的invoke方法。

2.3 做了什么

跟进ActivityWatcher.class可以看到这个类的内容并不多,实现InstallableWatcher接口,主要做了两件事情,就是install & uninstall 注册监听回调和反注册(注销)。
tip:无论是list集合中的哪个wather,最终都是通过监听生命周期来调用reachabilityWatcher.expectWeaklyReachable()方法。
activity是通过监听onActivityDestroyed,
FragmentAndViewModelWatcher则是通过反射拿到AndroidSupportFragmentDestroyWatcher来监听onFragmentDestroyed,
RootView是通过addAttachStateChangeListener监听,
service是通过onServiceDestroyed来监听。

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    // 在activity被销毁的时候监听回调,通过expectWeaklyReachable进行后续处理
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

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

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

上述代码可以看出,ActvityWatcher创建了一个lifecycleCallback,并实现了application的注册和卸载。当收到了activity被销毁的回调通知,则会通过reachabilityWatcher.expectWeaklyReachable进行接下来的操作。ReachabilityWatcher是一个接口,所以需要找到这个接口的具体实现类。

ReachabilityWatcher的实现类ObjectWatcher.class

@Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
        (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
        (if (description.isNotEmpty()) " ($description)" else "") +
        " with key $key"
    }
	// 对所有watch的对象创建弱引用并添加到集合中
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
    // 检测不可被回收的对象,进行泄漏检测
      moveToRetained(key)
    }
  }

  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 {
      // 将可以被回收的对象从watchedObjects中移除
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

这个方法进行了线程同步操作。1.首先进来会判断已经启用,否则直接return。2. 接着调用removeWeaklyReachableObjects()方法,这个方法内部是一个do…while的条件体,不断的从队列poll出KeyedWeakReference对象,判断对象不为空,然后从watchedObjects map集合中移除它【移除弱可达的对象】。3.随机生成一个不重复的uuid作为key,来创建一个KeyedWeakReference对象,且将被观察的对象和队列queue、key进行绑定。4.最后通过key-value方式将reference添加进watchedObjects map集合当中。然后moveToRetained(key)执行检测工作。

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

还记得在调用munualInstall方法通过反射获取的InternalLeakCanary实例,调用invoke方法吗,会将自己保存进OnObjectRetainedListeners集合中,然后取出来调用它们的onObjectRetained方法。
看一下OnObjectRetainedListener.class

fun interface OnObjectRetainedListener {

  /**
   * A watched object became retained.
   */
  fun onObjectRetained()

  companion object {
    /**
     * Utility function to create a [OnObjectRetainedListener] from the passed in [block] lambda
     * instead of using the anonymous `object : OnObjectRetainedListener` syntax.
     * Usage:
     * val listener = OnObjectRetainedListener {
     * }
     */
    inline operator fun invoke(crossinline block: () -> Unit): OnObjectRetainedListener =
      object : OnObjectRetainedListener {
        override fun onObjectRetained() {
          block()
        }
      }
  }
}

查看OnObjectRetainedListener的具体实现InternalLeakCanary.class invoke中会初始化一些检测内存泄漏过程中需要的对象

 override fun invoke(application: Application) {
    _application = application

    checkRunningInDebuggableBuild()

    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

	heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
    )
    ...
    

其中重要的两个对象就是heapDumper 和 gcTrigger,并且创建一个HeapDumpTrigger对象把它们传入进去。

  override fun onObjectRetained() = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }

2.4 检测泄漏的具体对象


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() {
	...
    var retainedReferenceCount = objectWatcher.retainedObjectCount
	
	//存在泄漏对象的话手动GC一次 然后再次检测
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      //GC 后重新计算 retainedReferenceCount 
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

	//如果泄漏数量不超出阈值(默认5) 结束
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

	//泄漏数量 > 阈值(默认5),存在内存泄漏,生成hprof文件
    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    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()
    val visibility = if (applicationVisible) "visible" else "not visible"
    //dump文件 通过原生Debug.dumpHprofData(heapDumpFile.absolutePath);
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }

最后通过dumpHeap进行真正的dump操作,然后对结果分析,进行通知展示。
在这里插入图片描述

2.5 流程总结

  1. 当一个对象被销毁时,会调用expectWeaklyReachable方法;
  2. 先是执行removeWeaklyReachableObjects移除弱可达的对象,这些对象是能够正常被回收的对象;
  3. 接着随机生成key,并为监听的对象创建一个弱引用对象,和queue进行关联【queue中保存的是可以回收的对象,watcherobjects保存的是未确定的可回收对象】;
  4. 然后将key和弱引用对象保存在watcherobjects中,是一个hashmap;
  5. 最后调用moveToRetained进行heapdump和泄漏分析。
  6. moveToRetained()方法中,先同样执行removeWeaklyReachableObjects再次进行判断【保证watcherobjects保存的对象暂时无法正常回收】;
  7. 调用munualInstall方法中,通过反射保存的InternalLeakCanary对象调用它的invoke()方法,创建gcTrigger 和 heapDump对象,并将InternalLeakCanary加入onObjectRetainedListeners;
  8. 遍历集合onObjectRetainedListeners执行它的onObjectRetained()方法,最终执行到HeapDumpTrigger中的checkRetainedObjects()方法;
  9. 手动调用gc,重新计数保留的引用对象数,判断泄漏数量是否超过阈值,进行hprof文件导出,对文件进行分析,以通知的形式展示出来。

3. 不能用于release环境的原因

  1. 每次内存泄漏以后,都会生成一个.hprof文件,然后解析,并将结果写入.hprof.result。增加手机负担,引起手机卡顿等问题。
  2. 多次调用GC,可能会对线上性能产生影响。
  3. 同样的泄漏问题,会重复生成 .hprof 文件,重复分析并写入磁盘。
  4. hprof文件较大,信息回捞成问题。

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值