IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【LeakCanary】源码分析 -> 正文阅读

[移动开发]【LeakCanary】源码分析

LeakCanary原理分析

LeakCanary的Github地址如下:https://square.github.io/leakcanary/

前言

内存泄漏问题,应该是我们项目开发过程中比较容易遇见的问题了,虽说一两个内存泄漏并不会对应用的产生致命的影响,但是一旦内存泄漏越积越多,而且一直得不到解决,那么恭喜,应用极有可能发生意想不到的OOM问题,应用会变得相当不稳定。

在以前没有LeakCanary这个库的时候,排查内存泄漏问题是个非常耗时且痛苦的过程!

举个例子,如果你认为这个地方可能会出现内存泄漏,那么你需要不断反复的进入这个界面好几次,等感觉差不多了,就把这个时候应用内存快照搞下来,也就是hprof格式的文件,这个文件包含了整个应用的内存使用情况,然后使用MAT工具打开这个文件看看哪些对象存在多个实例,如果它对应的界面已经被销毁,但实例仍存在,那就有可能发生内存泄漏。但是一旦hprof文件没有发现有疑似泄漏的对象,那么你又得去怀疑是不是另一个地方发生了泄漏,或者说是不是操作次数不够等因素导致的,相当痛苦~

回归正题,那么,LeakCanary有什么神奇之处呢?

LeakCanary是一个检测项目内存泄漏的第三方库。

目前已经升级到V2.7版本了,对于2.0以上的版本,LeakCanary的使用可以说是【相当的简单】。LeakCanary对代码的侵入性极低,我们看它的使用就明白了。

想要使用LeakCanary,我们只需要在build.gradle文件中添加入依赖即可:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

由于使用了debugImplementation引入依赖,因此leakcanary只有在debug模式下才会生效。

仅此而已,我们已经可以在项目中检测内存泄漏了,甚至我们都不需要像其他大部分第三方库一样,需要在Application中对库进行初始化。由此可以看出,它对代码几乎是零侵入的。下面我们开始对LeakCanary进行源码分析。

源码分析(基于V2.7)

使用LeakCanary我们只需要引入它的依赖即可,并不需要我们手动对其进行初始化。这是怎么做到的呢?其实,它是利用了ContentProvider在Application被创建之前被加载的原理,在ContentProvider的onCreate完成了初始化任务。

我们在打包完一个debugApk后,在apk文件中会包含一个合并后的AndroidManifest.xml,如下:

在这里插入图片描述

当我们打开这个文件,我们就会看到里面有provider标签的leakcanary相关的代码:

<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:enabled="@ref/0x7f040008"
    android:exported="false"
    android:authorities="com.example.mhandler.leakcanary-installer" />

我们定位到leakcanary.internal.AppWatcherInstaller这个类:

internal sealed class AppWatcherInstaller : ContentProvider() {
  internal class MainProcess : AppWatcherInstaller()
  internal class LeakCanaryProcess : AppWatcherInstaller()

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

首先可以看到,它继承自ContentProvider类,也就是说,AppWatcherInstaller是一个ContentProvider!然后在它的onCreate方法中,通过AppWatcher.manualInstall(application)方法对LeakCanary进行初始化。

我们继续进入manualInstall方法:

@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  //主线程判断
  checkMainThread()
  //防止重复Installe
  if (isInstalled) {
    throw IllegalStateException(
      "AppWatcher already installed, see exception cause for prior install call", installCause
    )
  }
  ......................................................................
  //获取InternalLeakCanary实例加载LeakCanary
  LeakCanaryDelegate.loadLeakCanary(application)
  //加载watcher
  watchersToInstall.forEach {
    it.install()
  }
}

manualInstall方法大致做了一下几件事:

1、通过Looper检查是否在主线程中,如果不在主线程,将抛出异常。

2、防止LeakCanary重复Installe

3、获取InternalLeakCanary实例加载LeakCanary

4、加载watcher

我们重点看它是如何加载watcher的,首先watchersToInstall这个变量是方法参数传进来的,而watchersToInstall的默认值是由appDefaultWatchers(application)方法获得的,我们进入该方法:

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

可以看到,它返回了一个InstallableWatcher的集合,也就是观察者的集合,它们包含:

  • ActivityWatcher
  • FragmentAndViewModelWatcher
  • RootViewWatcher
  • ServiceWatcher

其中,每个Watcher在构建的时候都传递了objectWatcher这个参数,我们先进入ActivityWatcher,看看它是如何构建的:

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

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

  override fun install() {
    //注册了应用activity生命周期的回调
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    //反注册activity生命周期回调
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

这段代码很简单,在ActivityWatcher.install()方法中,注册了应用Activity生命周期的回调,

当Activity执行了Destroy方法的时候,就通过objectWatcher对这个Activity进行监听,

objectWatcher监听的是啥呢?

我们想一下,当Activity回调了Destroy方法时,是不是就意味着下次GC到来时,这个Activity就需要被回收了,而objectWatcher的作用就是开始对Activity进行监听,如果Activity在GC时没有被回收,那么将会认为它发生了内存泄漏

那么,objectWatcher是如何对Activity进行监听的呢?

我们继续进入ObjectWatcher的expectWeaklyReachable方法:

ObjectWatcher.kt:
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  //移除弱引用可达Activity对象,留下不能被GC回收的对象
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  //根据activity键创建弱引用,并绑定到引用队列中
  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"
  }
 //将弱引用reference以键值形式存储在被监视的对象watchedObjects集合中
  watchedObjects[key] = reference
  // 通过线程池来启动任务
  checkRetainedExecutor.execute {
    // 获取不能回收的activity
    moveToRetained(key)
  }
}

首先看两个变量,

  • watchedObject:这个是刚刚外部传进来的activity对象,
  • watchedObjects:activity对象的集合,在这个队列中的activity正在被监视是否发生内存泄漏

下面我们先总结下这个expectWeaklyReachable方法的大致流程,内部的子流程等会细说:

1、执行removeWeaklyReachableObjects(),作用是将已被回收的Activity从watchedObjects中移除,watchedObjects将只留不能被GC回收的Activity对象

2、执行KeyedWeakReference方法:创建出activity的弱引用reference,并将activity和引用队列queue进行绑定,同时用key来标记这个activity。这样,在activity被GC回收时,它就能自动被添加到引用队列queue上了

3、将弱引用reference以键值形式存储到watchedObjects

4、通过线程池执行moveToRetained(key)方法,

好了,expectWeaklyReachable的流程大概就是这样,那么我们现在继续看看内部的一些值得关注的子流程。

首先进入removeWeaklyReachableObjects方法看看:

ObjectWatcher.kt:
private fun removeWeaklyReachableObjects() {
  var ref: KeyedWeakReference?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

可以看到,有一个循环,不断的从queue中取出对象,queue是啥,我看它的定义 :

private val queue = ReferenceQueue<Any>()

很明显,它是一个引用队列ReferenceQueue,要知道它是个什么,就需要对四大引用有一定了解了,这里就不在赘述四大引用的概念。就简单的说明一下吧,我们知道被弱引用(软引用)引用的对象,在GC到来时,就会对对象进行回收,同时,这个对象的引用还会被自动添加到与它关联的引用队列中。

知道这点就行了,也就是说,如果弱引用引用的对象被回收,那么这个弱引用将被添加到引用队列中,如果GC后,引用队列有某个对象的弱引用,就意味着这个对象被回收了,反之亦然。

removeWeaklyReachableObjects方法内部的循环不断的从引用队列queue中取出引用对象ref,如果ref不为null(意味着它关联的对象已经被回收),就将其从watchedObjects中移除,而watchedObjects是正在被监听的可能发生内存泄漏的对象。

我们继续看回到expectWeaklyReachable方法的倒数三行,通过线程池启动任务来执行moveToRetained(key)`方法:

ObjectWatcher.kt:
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  ..............................................................
  // 通过线程池来启动任务
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

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

这里要知道一点,就是这个线程池会默认延迟5秒才启动任务,且这个延迟的时间是可配置的,我们可以在初始化Leaknary的时候通过manualInstall方法的retainedDelayMillis参数进行配置。延迟5秒后,将执行moveToRetained(key)方法该方法的流程如下:

1、执行removeWeaklyReachableObjects()方法,将已被回收的Activity从watchedObjects中移除,watchedObjects将只留不能被GC回收的Activity对象

2、根据key从watchedObjects中取出activity对应的弱引用retainedRef

3、如果retainedRef不为null,保存泄漏对象的时间戳

4、遍历onObjectRetainedListeners集合(这个集合保存的元素,实际上是InternalLeakCanary对象,InternalLeakCanary实际上在manualInstall方法中已经完成了初始化)。然后执行InternalLeakCanary.onObjectRetained()方法通知InternalLeakCanary发生内存泄漏。

下面我们继续进入InternalLeakCanary类的onObjectRetained方法:

InternalLeakCanary.kt:
override fun onObjectRetained() = scheduleRetainedObjectCheck()

可以看到onObjectRetained又执行了scheduleRetainedObjectCheck()方法:

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

判断到如果堆转储触发器heapDumpTrigger已经被安装,则继续执行heapDumpTrigger.scheduleRetainedObjectCheck()方法:

HeapDumpTrigger.kt:
fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
}

这个方法中,在后台线程执行了checkRetainedObjects()方法检查泄漏对象:

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

  val config = configProvider()

  if (iCanHasHeap is Nope) {
    if (iCanHasHeap is NotifyingNope) {
      //获取可能存在内存泄漏的对象的总数
      var retainedReferenceCount = objectWatcher.retainedObjectCount
      if (retainedReferenceCount > 0) {
        //如果总数大于0,则抖动调用一次GC
        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) {
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }

  //检查泄漏的对象总数,内部实现:如果数量小于等于5,会返回true
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

  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"
  // 堆转储
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

方法有点长,大致流程是这样的:

1、获取可能存在内存泄漏的对象的总数,如果总数大于0,则进行手动执行一次GC

2、通过checkRetainedCount方法(如果泄漏数量大于5时返回true),判断是否需要进行堆转储,并把判断结果保存在wouldDump变量中,也就是说,如果泄漏对象大于5个,wouldDump将会置为true,wouldDump为true,将会显示发生内存泄漏的通知

3、再次获取可能存在内存泄漏的对象的总数,如果总数大于0,则进行手动执行一次GC

4、再次调用checkRetainedCount方法,如果泄漏对象小于等于5个,将直接return,如果大于5个,则往下执行代码

5、判断两次堆转储的时间,如果间隔小于60S,也会直接return,避免频繁进行堆转储

6、如果上面的流程方法都没有被return,将执行dumpHeap方法进行堆转储操作

dumpHeap方法就不看了,因为涉及到了square的另一个开源库shark的一些操作,感兴趣的大佬自己去了解吧。另外,dumpHeap方法执行的过程中,还会执行objectWatcher.clearObjectsWatchedBefore方法对已经提醒了内存泄漏的引用从watchedObjects中移除。

至此,LeakCanary如何对Activity对象进行内存泄漏的检测已经分析完毕,

另外,还有对Fragment、RootView、Service的监听我们还没有看,但是它们的大体的流程其实是一样的,只是监听的入口稍有不同,后续等我有时间再继续补全这篇文章,感兴趣的大佬可以跟着本篇幅的分析流程继续深入。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-23 15:51:56  更:2021-12-23 15:52:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:54:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码