三方库源码学习3-LeakCanary

本文深入解析了LeakCanary内存泄漏检测的实现机制,从初始化、Activity生命周期监听到判断内存泄漏的过程。LeakCanary通过ContentProvider自动初始化,在Activity销毁时利用弱引用监测对象是否被回收,结合引用队列和定时任务检查潜在的内存泄漏,并通过生成Hprof文件进行进一步分析。
摘要由CSDN通过智能技术生成

LeakCanary源码分析

参考资料:
三方库源码笔记(5)-LeakCanary 源码详解
关于Java中的WeakReference

一.在哪里初始化

在老版本的LeakCanary中,在集成LeakCanary时需要我们在Application中调用LeakCanary.install(this);进行初始化工作。但是在新版本中只需要我们添加依赖后,就可以直接监听Activity,Fragment等的内存泄露问题了。那么它的初始化工作是在那执行的呢?

它在 AppWatcherInstaller类的 onCreate()自动完成了初始化工作。

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

AppWatcherInstaller类是ContentProvider的一个子类对象。

这块说一下小知识,那就是ContentProvider的onCreate方法执行顺序是先于Application的onCreate的,晚于Application的attachBaseContext方法。 在这里它会调用到manualInstall()方法,完成初始化的工作。

在manualInstall方法中,他会遍历watchersToInstall集合调用每一个对象的install方法完成生命周期的绑定等初始化工作。

可以看到watchersToInstall集合中默认有四个对象 ActivityWatcherFragmentAndViewModelWatcherRootViewWatcherServiceWatcher,即对应这LeakCanary默认完成Activity,Fragment,RootView,Service的内存泄露检测,如果需要检测其他的对象,需要手动添加并处理。

@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
	//遍历watchersToInstall集合,完成每一个对象的初始化工作。
  watchersToInstall.forEach {
    it.install()
  }
}

//appDefaultWatchers(application)
fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }

二. 生命周期的监听

以Activity为例来说明:

知识补充:Activity声明周期监听器

ActivityLifecycleCallbacks这个是Android给我们提供的一个检测Activity生命周期的一个接口,我们可以实现这个接口,重写他的方法之后,通过Application.registerActivityLifecycleCallbacks()方法注册下这个callback,就可以监听Activity生命周期回调了,他的回调是和Activity生命周期一样的(多了一个onActivitySaveInstanceState)。

值得注意的是:使用ActivityLifecycleCallbacks来监听生命周期会对所有的Activity都做监听。

用一个demo证明一下:在Application中调用如下的LifeCycle()方法。在MainActivity中并跳转到MainActivity2,然后回退到MainActivity观察打印情况。

private fun LifeCycle() {
        val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                Log.d(TAG, "onActivityCreated: ${activity.hashCode()}")
            }
            override fun onActivityStarted(activity: Activity) {
                Log.d(TAG, "onActivityStarted: ${activity.hashCode()}")
            }
            .......
            override fun onActivityDestroyed(activity: Activity) {
                Log.d(TAG, "onActivityDestroyed: ${activity.hashCode()}")
            }
        }
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
    }

打印结果:

2021-07-20 20:18:01.854 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityCreated: 201342997
2021-07-20 20:18:02.035 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityStarted: 201342997
2021-07-20 20:18:07.028 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityCreated: 100570917
2021-07-20 20:18:07.048 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityStarted: 100570917
2021-07-20 20:18:09.224 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityStarted: 201342997
2021-07-20 20:18:09.825 7642-7642/com.example.leakcanary D/LifeCycleCall: onActivityDestroyed: 100570917

可以看到:符合生命周期流程,且所有的Activity都会被监听到。

ActivityWatcher源码分析

可以看到,在它的install()方法中,会调用registerActivityLifecycleCallbacks方法将重写好的ActivityLifecycleCallback对象传入进入,这个回调对象主要重写了onActivityDestroyed方法。从而就接管了Activity的生命周期。然后当当前的Activity回调onDestroy方法后进行后续判断看是否发生内存泄露。

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

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
      //注册,在lifecycleCallbacks中拿到各个生命周期的回调
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

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

三. 如何判断是否发生内存泄露

知识补充:WeakReference(弱引用)

弱引用也是用来描述非必需的对象,但是强度比软引用更弱。不管系统内存是否充足,在下一次垃圾回收发生时,系统都会回收掉只被弱引用关联的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

用一个demo来说明:

Apple类只有一个name字段。

class Apple(var name: String) {

    /**
     * 覆盖finalize,在回收的时候会执行。
     * @throws Throwable
     */
    @Throws(Throwable::class)
    protected fun finalize() {
        println("Apple: $name finalize。")
    }

    override fun toString(): String {
        return "Apple{" +
                "name='" + name + '\'' +
                '}' + ", hashCode:" + this.hashCode()
    }
}
fun main(){
    val appleReferenceQueue = ReferenceQueue<Apple>()
    //appleWeakReference,appleWeakReference2是弱引用对象 ,他们的引用是 Apple("青苹果"),Apple("毒苹果")
    val appleWeakReference = WeakReference(Apple("青苹果"), appleReferenceQueue)
    val appleWeakReference2 = WeakReference(Apple("毒苹果"), appleReferenceQueue)

    println("=====gc调用前=====")

    var reference: Reference<out Apple?>?
    while (appleReferenceQueue.poll().also { reference = it } != null) {
        //不会输出,因为没有回收被弱引用的对象,并不会加入队列中
        System.out.println(reference)
    }

    println(appleWeakReference)			//java.lang.ref.WeakReference@2d6e8792
    println(appleWeakReference2)		//java.lang.ref.WeakReference@2812cbfa
    println(appleWeakReference.get())	//Apple{name='青苹果'}, hashCode:718231523
    println(appleWeakReference2.get())	//Apple{name='毒苹果'}, hashCode:1349414238

    println("=====调用gc=====")
    System.gc()
    try {
        Thread.sleep(5000)
    } catch (e: InterruptedException) {
        e.printStackTrace()
    }

    println("=====gc调用后=====")

    println(appleWeakReference.get())		//null
    println(appleWeakReference2.get())		//null

    //输出结果,并且就是上面的appleWeakReference、appleWeakReference2,再次证明对象被回收了
    var reference2: Reference<out Apple?>? = null
    while (appleReferenceQueue.poll().also { reference2 = it } != null) {
        //如果使用继承的方式就可以包含其他信息了
        println("appleReferenceQueue中:$reference2")
    }
}

说明,上面定义了两个弱引用对象 appleWeakReference,appleWeakReference2 。在gc之前,被弱引用的对象即Apple实例没有被回收,所以引用队列为空。当发生gc后,因为 我们的Apple实例都只被弱引用关联所以会被回收,并执行我们重写的finalize()方法。此时被弱引用的对象变为空。并且弱引用的对象会被加入到与之关联的弱引用队列。

LeakCanary判断是否内存泄露

LeakCanary判断是否内存泄露的机制就是借助上述过程完成的。用Activity来说明一下。因为在Activity中声明了周期监听器,当监听到ActivityDestroyed事件发生时,就会用当前Activity创建一个弱引用对象,并和定义的引用队列保持联系。然后为Activity生成一个唯一的key。以键值对的形式 把key和生成的弱引用对象保存在map集合中。在一段时间后(可以主动触发几次 GC)检测 引用队列是否有值,如果有值,则删除集合中对应的对象。最后集合中剩下的就是可能发生内存泄露的。

具体过程如下:

  1. expectWeaklyReachable方法

    在刚才的onActivityDestroyed方法中,会调用到 reachabilityWatcher.expectWeaklyReachable()方法,这个方法的具体实现在ObjectWatcher。它里面做的工作就是生成当前Activity的弱引用对象,并加入到map集合中,然后在线程池中调用moveToRetained(key)方法。

    class ObjectWatcher constructor{
    
      private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
    //保存弱引用的集合	
      private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
    //引用队列
      private val queue = ReferenceQueue<Any>()
    
      fun watch(
        watchedObject: Any,
        description: String
      ) {
        expectWeaklyReachable(watchedObject, description)
      }
        
      @Synchronized override fun expectWeaklyReachable(
        watchedObject: Any,
        description: String
      ) {
        
        removeWeaklyReachableObjects()
          
        val key = UUID.randomUUID()		//获取当前Activity对应的key
          .toString()
        val watchUptimeMillis = clock.uptimeMillis()
          //生成弱引用对象
        val reference =
          KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
        //把弱引用对象加入到map集合
        watchedObjects[key] = reference
          //通过线程池调用了moveToRetained方法开始判断是否发生了内存泄漏
        checkRetainedExecutor.execute {
          moveToRetained(key)
        }
      }
    

    关于这个线程池:

    val objectWatcher = ObjectWatcher(
      clock = { SystemClock.uptimeMillis() },
      checkRetainedExecutor = {
        mainHandler.postDelayed(it, retainedDelayMillis)
      },
      isEnabled = { true }
    ){}
    fun manualInstall(
        application: Application,
        retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
        watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
      ) {
     ...... 
        this.retainedDelayMillis = retainedDelayMillis
        }
    

    通过主线程的Handler的postDelay延迟5s再去判断是否发生了内存泄露。(5s足够GC了)

  2. moveToRetained方法

    刚才gc过后,如果没有发生内存泄露,那么Activity对象也就是被弱引用的对象会被回收,而对应的弱引用对象会被加入到引用队列中。removeWeaklyReachableObjects()方法就是从引用队列拿到弱引用对象,然后删除map集合中对应的元素,结束。

    如果发生内存泄露,那么正好和上面相反,此时集合中就存在Activity对应的弱引用对象,就会调用到onObjectRetained()方法。

    @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() {
        var ref: KeyedWeakReference?
        do {
          ref = queue.poll() as KeyedWeakReference?
          if (ref != null) {
            watchedObjects.remove(ref.key)
          }
        } while (ref != null)
      }
    
  3. 检测留存的对象

    刚才的一遍判断结果可能还不准确。onObjectRetained()方法调用后,会回调到InternalLeakCanary.onObjectRetained方法。然后进入checkRetainedObjects()方法进行后续检测。

    • 第一次移除不可达对象:移除 ReferenceQueue 中记录的KeyedWeakReference 对象(引用着监听的对象实例);
    • 主动触发GC:回收不可达的对象;
    • 第二次移除不可达对象:经过一次GC后可以进一步导致只有WeakReference持有的对象被回收,因此再一次移除ReferenceQueue 中记录的KeyedWeakReference 对象;
    • 判断是否还有剩余的监听对象存活,且存活的个数是否超过阈值;
    • 若满足上面的条件,则抓取Hprof文件,实际调用的是android原生的Debug.dumpHprofData(heapDumpFile.absolutePath) ;启动异步的HeapAnalyzerService 分析hprof文件,找到泄漏的GcRoot链路。
    // HeapDumpTrigger.checkRetainedObjects
    
    private fun checkRetainedObjects() {
     //第一次移除不可达对象
      var retainedReferenceCount = objectWatcher.retainedObjectCount
    //如果保存弱引用对象的map集合还有元素
      if (retainedReferenceCount > 0) {
      //主动触发GC
        gcTrigger.runGc()
        //第二次移除不可达对象
        retainedReferenceCount = objectWatcher.retainedObjectCount
      }
    	//判断是否还有剩余的监听对象存活,且存活的个数是否超过阈值;	
      if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
    
      ......
    }
    
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值