Android 源码浅析:Leakcanary 内存泄漏检测的好帮手

news2024/9/25 1:24:08

我们一起来分析一下大名鼎鼎的 Leakcanary, 想必作为 Android 开发都多多少少接触过,新版本的 Leakcanary 也用 Kotlin 重写了一遍,最近详细查看了下源码,分享一下。

tips:本来是只想分析下内存泄漏检测部分,但写着写着就跑偏了,因为内存泄漏的检测难点在于对对象生命周期的把控, Leakcanary 对于 Service 生命周期的把控我觉得非常值得我们学习,并且在项目中也会用到。外加 Leakcanary 用 Kotlin 重写,一些语法糖我平时也没用过,就顺便写了下,整体读下来有点啰嗦。

源码版本

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

内存泄漏

本博客着重分析 leakcanary 源码实现原理以及一些优秀设计,对于内存泄漏的解释就简单用我自己的理解来解释下:长生命周期对象持有短生命周期对象,当短生命周期对象需要被回收时因其被长生命周期对象持有导致无法正常回收的情况;

源码浅析

在了解其优秀设计之前先来简单分析下其源码以及实现原理。

如需完整版的学习资料 请点击免费领取

检测原理

Leakcanary 检测内存泄漏的原理很简单,就是利用弱引用 WeakReference 的双参数构造方法

WeakReference(T referent, ReferenceQueue<? super T> q)

来检测被弱引用的对象是否被正常回收、释放,举个例子:

// 定义类
class A
// 检测的目标对象
val obj = A()
val queue = ReferenceQueue<A>()
val weakObj = WeakReference(obj, queue)
// 触发gc回收(注意:这样的操作不一定可以触发gc,具体如何触发gc 在下面的源码分析中有提到 leakcanary 是如何触发gc的)
System.gc()

val tmp = queue.poll()
if (tmp === obj) {
    // 被回收
} else {
    // 未回收
}

Android 开发中的 Activity、Fragment、Service、自定义 View 都是容易发生内存泄漏的对象,Leakcanary 所做的工作就是在合适的时机(一般是在回收时,如 Activity 的 onDestory 后)对这些对象进行弱引用并且关联引用队列,根据其是否被添加到引用队列来判断是否发生泄漏。

关于判断一个对象是否发生泄漏的原理上面的示例代码已经简单演示,下面我们就顺着源码来看看 Leakcanary 的实现细节。

初始化

Leakcanary 仅需引入依赖即可完成初始化,放到现在这也不算多么神奇的技巧了,这是利用了 ContentProvider。

ContentProvider 的初始化时机在 Application 的 onCreate 之前,并且在 ContentProvider 的 onCreate 方法中可以获取到 context、applicationContext。

当项目引入 Leakcanary 后打包出的 apk 的清单文件中可以找到注册了MainProcessAppWatcherInstaller,其关键源码部分如下:

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

可以看出调用了 AppWatcher.manualInstall() 进行了初始化,其源码如下:

AppWatcher.kt

fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 默认 5s
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) // 获取默认Watchers 下面详细分析
) {
  // 检查是否在主线程
  // 原理:Looper.getMainLooper().thread === Thread.currentThread()
  checkMainThread()
  // ...
  this.retainedDelayMillis = retainedDelayMillis
  // 初始化 Shark 库
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // 这行代码下面详细分析
  LeakCanaryDelegate.loadLeakCanary(application)
  // 对 watchers 遍历调用 install 
  watchersToInstall.forEach {
    it.install()
  }
  // 给 installCause 赋值,代表已经初始化
  installCause = RuntimeException("manualInstall() first called here")
}

appDefaultWatchers(application)

上述初始化方法中第三个参数 watchersToInstall 被赋予了默认值,通过 appDefaultWatchers 获取了一个 List<InstallableWatcher>,先看下 InstallableWatcher 源码:

interface InstallableWatcher {
  fun install()
  fun uninstall()
}

是一个接口,定义了两个方法看命名也能明白是安装和卸载,接着看下 appDefaultWatchers 方法返回了什么:

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher // 注意这个 objectWatcher 很重要
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher), // 用于监控activity内存泄漏
    FragmentAndViewModelWatcher(application, reachabilityWatcher),// 用于监控fragment,viewmodel 内存泄漏
    RootViewWatcher(reachabilityWatcher),// 用于监听 rootview 内存泄漏
    ServiceWatcher(reachabilityWatcher) // 用于监听 service 内存泄漏
  )
}

注意上述方法的第二个参数 reachabilityWatcher 默认赋值了 objectWatcher:

val objectWatcher = ObjectWatcher(...)

先记住他是 ObjectWatcher 类的实例,并且将其传递给了用于检测的各个 InstallableWatcher 实现类。

LeakCanaryDelegate.loadLeakCanary(application)

接着再回过头来看一下 LeakCanaryDelegate.loadLeakCanary(application) 这句代码,loadLeakCanary 作为 LeakCanaryDelegate 类中的一个函数类型变量,所以可以直接调用,看一下其源码:

internal object LeakCanaryDelegate {
  // 延迟初始化
  val loadLeakCanary by lazy {
    try {
      // 默认加载 InternalLeakCanary 获取其 INSTANCE 字段
      // InternalLeakCanary 是一个 object class,编译为 java 后会自动生成 INSTANCE
      // 就是一个单例类 这里是获取其单例对象
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      // 出现异常时返回 NoLeakCanary
      NoLeakCanary
    }
  }
  // (Application) -> Unit 函数类型变量,接受一个 application 作为参数
  // 内部都是空实现
  object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {}
    override fun onObjectRetained() {}
  }
}

一般情况下 LeakCanaryDelegate.loadLeakCanary(application) 就相当于调用了 InternalLeakCanary,当然 InternalLeakCanary 也是 (Application) -> Unit 的实现了,对你没看错,函数类型不仅可以声明变量,也可以定义实现类,但需要实现 invoke 方法,看下其源码:

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {
      _application = application
      checkRunningInDebuggableBuild() // 检查是否是 debug 模式
      // 对 AppWatcher 的 objectWatcher 添加 listener
      AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
      // 这个用于触发 gc 回收,下面会分析一下
      val gcTrigger = GcTrigger.Default
      // 获取相关配置
      val configProvider = { LeakCanary.config }
      // 启动了一个 HandlerThread
      val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
      handlerThread.start()
      val backgroundHandler = Handler(handlerThread.looper)
      // 初始化堆转储对象,上面定义的一些变量也都传入了进去
      heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
        configProvider
      )
      // Leakcanary 对 Application 的一个扩展方法
      // 这里的作用是在 App 前后台切换时执行闭包中的逻辑 也就是调用堆转储的 onApplicationVisibilityChanged 方法
      application.registerVisibilityListener { applicationVisible ->
        this.applicationVisible = applicationVisible
        heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
      }
      // 这个方法代码比较简单利用 Application.registerActivityLifecycleCallbacks
      // 获取处于激活状态的 Activity 赋值给 resumedActivity
      registerResumedActivityListener(application)
      // 添加桌面快捷方式,也就是Leakcanary小黄鸟app图标,感兴趣的可以看一下 这里就不详细分析这个方法了
      addDynamicShortcut(application)
      // ...
    }

}

实现细节

从初始化部分源码中可以看出对于内存泄漏监控的核心部分就是 appDefaultWatchers 方法中返回的四个InstallableWatcher 的实现类,下面我们依次来分析。

ObjectWatcher

在上述分析中四个实现类在初始化时都传入了 objectWatcher,所以有必要先来初步了解下:

AppWatcher.kt

val objectWatcher = ObjectWatcher(
  // 返回一个 Clock 对象
  clock = { SystemClock.uptimeMillis() },
  // Executor 对象
  checkRetainedExecutor = {
    // 切换到主线程延迟执行, retainedDelayMillis 默认是 5s
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  // 这个参数是 () -> Boolean 函数类型,默认是直接返回一个 true
  isEnabled = { true }
)

我第一次看到这个clock = { SystemClock.uptimeMillis() } 是有点没看懂,所以先来分析下这个写法,先看看这个 Clock 源码:

// 注意这个 fun interface 是 kotlin 在 1.4 引入的 函数式接口
// 接口中只能有一个未实现的方法
fun interface Clock {
  fun uptimeMillis(): Long
  // ...
}

clock = { SystemClock.uptimeMillis() } 就等价于以下代码:

clock = object : Clock{
    override fun uptimeMillis(): Long {
        SystemClock.uptimeMillis()
    }
}

大概先了解下 objectWatcher 的参数部分,具体方法待下面用到时具体分析。

ActivityWatcher

先来看看我们最熟的 Activity 是如何进行内存泄漏监控的,ActivityWatcher 源码如下:

class ActivityWatcher(
  private val application: Application,
  // 注意这里,传进来的是 AppWatcher 的 objectWatcher
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        // 核心就在这一行代码里 这里也就是调用了 objectWatcher 的 expectWeaklyReachable 方法
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    // 利用 Application 监听 Activity onDestory 生命周期方法
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

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

直接查看 ObjectWatcher 的 expectWeaklyReachable 方法源码:

ObjectWatcher.kt

// 保存监控对象的 map
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// 用于弱引用关联队列
private val queue = ReferenceQueue<Any>()

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any, // 传入的 activity(已经 onDestory)
  description: String // 描述
) {
  if (!isEnabled()) { // 默认 isEnabled 返回 true,不会进入这个 if
    return
  }
  // 这个代码比较简单 我就简单说下作用
  // 当 watchedObject 成功被回收会添加进 queue 中,这个方法就是将添加到 queue 中的对象
  // 从 watchedObjects 中移除,被移除的说明没有发生内存泄漏
  removeWeaklyReachableObjects()
  // 生成id (相当于对象的身份证)
  val key = UUID.randomUUID()
    .toString()
  // 获取时间戳
  val watchUptimeMillis = clock.uptimeMillis()
  // 将对象以及一些信息包装为 KeyedWeakReference
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  // ...
  // 包装好的对象放入 watchedObjects 这个 map 中
  watchedObjects[key] = reference
  // checkRetainedExecutor 别忘了,就是切换到主线程延迟 5s 执行
  checkRetainedExecutor.execute {
    // 主线程调用
    moveToRetained(key)
  }
}

KeyedWeakReference

class KeyedWeakReference(
  referent: Any, // 监控对象,这里是 onDestory 的 Activity
  val key: String, // UUID 身份证
  val description: String, // 描述
  val watchUptimeMillis: Long, // 时间
  referenceQueue: ReferenceQueue<Any> // 当 referent 被回收会添加到这个队列中
) : WeakReference<Any>(referent, referenceQueue)
//最后调用了 WeakReference 的双参数构造器

这个包装相当于对 WeakReference 做了一些扩展,可以通过 key 从前面的 watchedObjects 中移除或者获取。

moveToRetained

再来看看 moveToRetained 方法源码:

@Synchronized private fun moveToRetained(key: String) {
  // 再次尝试从 queue 中获取回收成功的对象,从 watcherObjects 中移除
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) { // 如果还可以获取到代表回收失败 发生了内存泄漏
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    // 遍历 onObjectRetainedListeners 对象调用其 onObjectRetained 方法
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

注意这个 onObjectRetainedListeners,在初始化的第二小节我们分析的 InternalLeakCanary 源码中有一处调用: AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

这里的遍历调用一般情况下就相当于调用了 AppWatcher 的 onObjectRetained 方法,看下其源码部分:

InternalLeakCanary.kt

// 又调用了 scheduleRetainedObjectCheck 方法
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    // 又调用到了 堆转储 对象里的 scheduleRetainedObjectCheck 方法
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}

继续跟踪源码,查看 HeapDumpTrigger 类中的 scheduleRetainedObjectCheck 方法:

HeapDumpTrigger.kt

fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  // backgroundHandler 前面看过了 是一个 HandlerThread 这里是放到子线程执行
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    // 重点是这个方法
    checkRetainedObjects()
  }, delayMillis)
}

接着查看 checkRetainedObjects 方法源码:

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

  val config = configProvider()
  // ...
  // 获取仍然存在于 map 中没有被回收的对象数量
  var retainedReferenceCount = objectWatcher.retainedObjectCount
  // 如果有再执行一次 gc
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc() // gc 操作,注意这里的实现 后面会贴源码
    retainedReferenceCount = objectWatcher.retainedObjectCount // 再次获取数量
  }
  // 里面是对app 是否在前台,存活对象数量的判断,retainedVisibleThreshold 默认是 5
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
  
  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  // 判断上次堆转储时间间隔是否小于 60s
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    onRetainInstanceListener.onEvent(DumpHappenedRecently)
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
    )
    // 小于60s 就延迟对应时间在重新执行该方法
    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"
  )
}

注意上述的 gcTrigger.runGc() 触发 gc 操作,这段代码看一下 Leakcanary 是如何实现的:

object Default : GcTrigger {
  override fun runGc() {
    // 这段执行gc的代码 leakcanary 注释中表示是借鉴于 Android 系统源码片段
    // System.gc() 并不是每次执行都能触发gc,而 Runtime gc() 更容易触发
    // 这里的源码细节值得借鉴
    Runtime.getRuntime().gc()
    enqueueReferences()
    System.runFinalization()
  }
  // ...
}

到这里源码还剩下两个部分:1. 获取堆信息;2. 分析堆信息;先说下第二点 Leakcanary 在 2.x 之前的版本使用 haha 这个库来解析 .hprof 文件,而 2.x 之后另起炉灶用 shark 库来完成这一工作,很遗憾我对 shark 的库不够了解,这部分就暂时不深入分析了。

来看看 Leakcanary 是如何获取堆信息写入文件的,dumpHeap 方法源码:

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  // 注意这个 directoryProvider 并不是 ContentProvider 只是一个普通类
  val directoryProvider =
    InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
  // 新建堆转储文件,内部包含一些权限、目录等等逻辑判断 比较简单就不贴代码了
  // 生成文件名:fileName = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'.hprof'", Locale.US).format(Date())
  val heapDumpFile = directoryProvider.newHeapDumpFile()

  val durationMillis: Long
  if (currentEventUniqueId == null) {
    currentEventUniqueId = UUID.randomUUID().toString()
  }
  try {
    // 通知栏 Event
    InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
    // ...
    durationMillis = measureDurationMillis {
      // 这里是堆转储核心
      configProvider().heapDumper.dumpHeap(heapDumpFile)
    }
    // 触发后面的分析方法
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
  } catch (throwable: Throwable) {
    // 堆转储失败
    InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
    if (retry) { // 重试则延迟再执行一次方法
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
      )
    }
    // 通知栏展示
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(
        R.string.leak_canary_notification_retained_dump_failed
      )
    )
    return
  }
}

configProvider().heapDumper.dumpHeap(heapDumpFile) 这行代码是堆转储的核心,来看一下其源码:

AndroidDebugHeapDumper.kt

object AndroidDebugHeapDumper : HeapDumper {
  override fun dumpHeap(heapDumpFile: File) {
    // 利用 Android 本身的 Debug dumpHprofData 方法实现
    Debug.dumpHprofData(heapDumpFile.absolutePath)
  }
}

堆转储的实现非常简单,系统源码中自带了对应方法,传入文件即可。

FragmentAndViewModelWatcher

接着来看看对于 Fragment 和 ViewModel 是如何进行检测的,直接看源码:

在这里插入图片描述

和 ActivityWatcher 如出一辙,同样利用 Application 的 api 注册对 Activity 的监听,在 Activity onCreate 生命周期时遍历 fragmentDestoryWatchers 进行调用,接着来看看 fragmentDestoryWatchers 的定义:

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  // 注意这个 list 的类型,item 是函数式变量,相当于方法可以直接调用
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
  
  // sdk26 以上添加 AndroidOFragmentDestroyWatcher
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      // reachabilityWatcher 是老朋友 objectWatcher 了
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
  // 通过反射获取 AndroidXFragmentDestroyWatcher
  // 对应 androidX 的 fragment
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME, // "androidx.fragment.app.Fragment"
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, // "AndroidXFragmentDestroyWatcher"
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  
  // 通过反射获取 AndroidSupportFragmentDestroyWatcher
  // 对应之前的 android support 包下的 Fragment
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, // "android.support.v4.app.Fragment"
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, // "AndroidSupportFragmentDestroyWatcher"
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  // 返回
  fragmentDestroyWatchers
}

依次来看看上面定义的三个 item 实现细节。

AndroidOFragmentDestroyWatcher

在这里插入图片描述

reachabilityWatcher.expectWeaklyReachable() 方法在 ActiviyWatcher 小节中已经详细分析了,就不再赘述了。

AndroidXFragmentDestroyWatcher

和 AndroidOFragmentDestroyWatcher 一样是通过 Activity 来监听 Fragment 的生命周期,差别在于注册监听的 api :

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  
  // 和上面的 AndroidOFragmentDestroyWatcher 一样 就不贴代码了
  private val fragmentLifecycleCallbacks = // ...

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      // 和 AndroidOFragmentDestroyWatcher 的原理是一样的
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 注意这里,多了一个针对 ViewModel 的处理
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

这里增加了对 ViewModel 的监听,看一下其是如何实现的。

ViewModelClearedWatcher

直接看源码:

// 注意这是一个 ViewModel 的子类
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  companion object {
    // 外部调用的 install 方法
    fun install(
      storeOwner: ViewModelStoreOwner, // 这里也就是 activity
      reachabilityWatcher: ReachabilityWatcher // objectWatcher
    ) {
      // 相当于再 Activity onCreate 方法中又创建了一个 ViewModel
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
  
  // 反射 activity 中的 ViewModelStore 中的 mMap 字段
  // 这里面存储着 activity 中创建的 ViewModel
  private val viewModelMap: Map<String, ViewModel>? = try {
    val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
    mMapField.isAccessible = true
    mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    null
  }
  
  // ViewModel 销毁时触发的方法
  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      // 进行检测
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }
}

AndroidSupportFragmentDestroyWatcher

最后再来看一下 AndroidSupportFragmentDestroyWatcher,猜也能猜到和上面的逻辑是一样的,仅仅是为了支持 AndroidSupport 包:

internal class AndroidSupportFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  // 还是和上面一样的
  private val fragmentLifecycleCallbacks = // ...

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      // AndroidSupport 包的 api
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
    }
  }
}

RootViewWatcher

这个类的源码呢,就不贴了,因为涉及到了 Square 的另一个库 Curtains,这个库的作用就是可以监听 window 的生命周期。很遗憾我对这个库的了解也不到位,就不瞎说八道了。

不过这里可以稍微总结一下,对于监听对象是否泄漏很关键的一个点就是能够监听到对象的生命周期,也就是能监听到对象的销毁。

下一小节是值得我们学习的一个小节,监听 Service 的销毁,看下 Leakcanary 是如何做到的。

ServiceWatcher (重点 值得学习借鉴)

对于 Service 的生命周期,我们都知道 Service 销毁时会走 onDestory,但是系统并没有提供像监听 Activity 的 Application.registerActivityLifecycleCallbacks 这样的 api,这一小节就重点分析下 Leakcanary 是如何做到的。

直接看源码:

class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
    override fun install() {
      checkMainThread() // 检测主线程
      // ...
      try {
        // 重点 1: 反射 ActivityThread 的 mH 中的 mCallback
        // 也就是 Handler 的 mCallback
        swapActivityThreadHandlerCallback { mCallback ->
          uninstallActivityThreadHandlerCallback = {
            swapActivityThreadHandlerCallback {
              mCallback
            }
          }
          Handler.Callback { msg ->
            if (msg.obj !is IBinder) {
              return@Callback false
            }
            if (msg.what == STOP_SERVICE) { // AMS Service onDestory 的 message 的 what
              val key = msg.obj as IBinder
              // 注意这个 activityThreadServices
              // 这个是反射获取的 ActivityThread 中的 mServices 字段是 Map<IBinder, Service> 类型
              // 这个字段存放所有启动的 Service
              activityThreadServices[key]?.let {
                // 这里是将要销毁的 Service 进行存储
                // key 是 IBinder 对象
                onServicePreDestroy(key, it)
              }
            }
            mCallback?.handleMessage(msg) ?: false
          }
        }
        // 重点 2:反射获取 AMS (ActivityManagerService)
        swapActivityManager { activityManagerInterface, activityManagerInstance ->
          uninstallActivityManager = {
            swapActivityManager { _, _ ->
              activityManagerInstance
            }
          }
          // 对 AMS 进行动态代理 
          Proxy.newProxyInstance(
            activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
          ) { _, method, args ->
            // 判断方法名是否是 serviceDoneExecuting 
            if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
              // 获取 IBinder 对象
              val token = args!![0] as IBinder
              // 从上面存储的 service 容器中查看是否存在对应 service
              if (servicesToBeDestroyed.containsKey(token)) {
                // 进行内存泄漏检测
                onServiceDestroyed(token)
              }
            }
            // ...
          }
        }
      } 
      // ...
    }
    
    private fun onServiceDestroyed(token: IBinder) {
      // 从 servicesToBeDestroyed 中移除
      servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
        serviceWeakReference.get()?.let { service ->
          // 利用 objectWatcher 进行内存泄漏检测
          reachabilityWatcher.expectWeaklyReachable(
            service, "${service::class.java.name} received Service#onDestroy() callback"
          )
        }
      }
    }
}

可以看出对于 Service 进行内存泄漏检测的核心有两步骤:

  1. 反射 Activity 的 mH 的 mCallback 获取即将销毁的 Service 对象;
  2. 对 AMS 进行动态代理监听 Service 的销毁时机。

总结

内存泄漏检测的原理非常简单,利用弱引用的双参数构造传入引用队列,掌握被检测对象的生命周期,在销毁时检查引用队列的情况。我们自己也能很轻松的写出对应 Demo,但 Leakcanary 中很多优秀设计非常值得学习,如:无感初始化(ContentProvider),一些 Kotlin 语法糖,对于 Service 生命周期的监听。

尤其是最后对于 Service 生命周期的监听,通过反射系统类,结合动态代理很轻松就实现了,由此我们也可以延伸出对 Service 其他生命周期的监听。

内存泄漏检测并不难,难点在于分析堆信息以及对于各种对象的生命周期把控。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488136.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c语言Have Fun with Numbers

题目 Have Fun with Numbers Notice that the number 123456789 is a 9-digit number consisting exactly the numbers from 1 to 9, with no duplication. Double it we will obtain 246913578, which happens to be another 9-digit number consisting exactly the numbers …

2016年iOS公开可利用漏洞总结

0x00 序 iOS的安全性远比大家的想象中脆弱&#xff0c;除了没有公开的漏洞以外&#xff0c;还有很多已经公开并且可被利用的漏洞&#xff0c;本报告总结了2016年比较严重的iOS漏洞&#xff08;可用于远程代码执行或越狱&#xff09;&#xff0c;希望能够对大家移动安全方面的工…

【LeetCode】139. 单词拆分

139. 单词拆分&#xff08;中等&#xff09; 思路 首先将大问题分解成小问题&#xff1a; 前 i 个字符的子串&#xff0c;能否分解成单词&#xff1b;剩余子串&#xff0c;是否为单个单词&#xff1b; 动态规划的四个步骤&#xff1a; 确定 dp 数组以及下标的含义 dp[i] 表示 s…

Zero-ETL、大模型和数据工程的未来

编者按&#xff1a;本文探讨了数据工程领域的未来趋势和挑战&#xff0c;以及其不断变化、甚至经常出现“重塑”的特点。在数据工程领域&#xff0c;大数据的性能、容量提升总是有一定的上限&#xff0c;每一次进步都会带来一定的技术提升&#xff0c;从而提高上限。但是很快我…

解锁采购系统数字升级?来看看云时通SRM!

疫情影响下&#xff0c;全球经济一体化使得企业在供应商的管理上面临着巨大挑战&#xff0c;传统采购方法已经不足以支持企业管理和竞争&#xff0c;企业采购管理急需数字化转型。 相较于传统采购方法&#xff0c;目前成功的供应商系统管理&#xff0c;还需要具有更多的市场要…

IT圈最近比较火热的技术都是哪些?

前言 如果现在要问什么行业最火&#xff0c;毋庸置疑&#xff0c;会有很多人第一反应回答是IT行业。众所周知&#xff0c;近些年互联网行业的高速发展&#xff0c;IT技术不断推陈出新&#xff0c;各种技术更新迭代周期越来越快&#xff0c;涌入IT技术开发的人员也是成倍增长&am…

QT基础铺垫

1.qt定位 qt在整个课程体系中起到以下作用 1.c理论的实践课 2.图形用户界面GUI开发 3.一个独立的就业方向 3.qt特性 qt经常被当作是一个基于c语言的gui开发框架&#xff0c;但是这并不是qt的全部&#xff0c;除了开发界面外&#xff0c;qt还包含了很多其他功能&#xff1…

C++最后一个数组元素后加不加逗号,的问题(C++11 及以上的标准中,最后一个元素后面可以加逗号,也可以不加)

这代码看得我有点疑惑&#xff0c;最后一个元素后咋有个逗号呢&#xff1f;也没报错&#xff1f; 原因&#xff1a; 在 C11 及以上的标准中&#xff0c;最后一个元素后面可以加逗号&#xff0c;也可以不加。因此&#xff0c;上述代码可以写成以下两种形式&#xff1a; std::v…

零基础学SQL(十三、事务)

目录 前置建表 ​编辑 一、什么是事务 二、事务特性ACID 1、原子性(Atomicity) 2、一致性(Consistency) 3、隔离性(Isolation) 4、持久性(Durability) 三、事务控制 1、ROLLBACK 2、COMMIT 使用 set autocommit0;命令修改默认提交方式为FALSE 前置建表 CREATE TABLE…

无距离障碍:远程桌面Ubuntu实现全球办公

目录 前言 视频教程 1. ubuntu安装XRDP 2.局域网测试连接 3. Ubuntu安装cpolar内网穿透 4.cpolar公网地址测试访问 5.固定域名公网地址 [TOC] 转载自远程穿透文章&#xff1a;Windows通过RDP异地远程桌面Ubuntu【内网穿透】 前言 XRDP是一种开源工具&#xff0c;它允许…

Android开发中的前五个代码异味:Jetpack Compose UI和MVVM

Android开发中的前五个代码异味&#xff1a;Jetpack Compose UI和MVVM 代码异味是指软件代码中潜在问题的指标&#xff0c;可能并不一定是错误&#xff0c;但可能会导致问题&#xff0c;如增加维护复杂性、降低性能或降低可读性。我们将探讨Android开发中的前五个代码异味&…

【C++】布隆过滤器

文章目录 布隆过滤器提出布隆过滤器概念布隆过滤器应用场景设计思路:布隆过滤器的插入布隆过滤器的查找布隆过滤器删除BloomFilter.h布隆过滤器优点布隆过滤器缺陷 布隆过滤器提出 我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经…

Leetcode力扣秋招刷题路-0902

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 902. 最大为 N 的数字组合 给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如&#xff0c;如果 digits [‘1’,‘3’,‘5’]&#xff0c;我…

一图看懂 requests 模块:用Python编写、供人类使用的HTTP库, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 requests 模块&#xff1a;用Python编写、供人类使用的HTTP库, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【requests】统计常量str 模块3 w…

小红书违禁词有哪些,小红书违禁词汇总分享

大家都知道小红书平台对于违禁词的管控一向非常严格&#xff0c;笔记中一旦出现就可能被限流&#xff0c;今天为大家整理了一份小红书违禁词汇总&#xff0c;希望能够帮助大家避免被限流。 小红书违禁词汇总大致有以下几个分类&#xff0c;大家平时写笔记的时候最好避开这些词或…

HashMap底层实现原理

HashMap HashMap 最早出现在 JDK 1.2中&#xff0c;底层基于散列算法实现&#xff0c;它是一个key-value结构的容器。 是一个key-value的映射容器&#xff0c;key不重复jdk8中的HashMap基于数组链表红黑树实现不保证键值的顺序可以存入null值非线程安全&#xff0c;多线程环境…

log4j2.xml配置解析

log4j2.xml文件的配置大致如下&#xff1a; Configuration&#xff1a;为根节点&#xff0c;有status和monitorInterval等多个属性 status的值有 “trace”, “debug”, “info”, “warn”, “error” and “fatal”&#xff0c;用于控制log4j2日志框架本身的日志级别&#x…

python+vue+nodejs旅游资源信息网站

1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和验证码&#xff0c;然后对登录进来的用户判断身份信息&#xff0c;判断是管理员用户还是普通用户。 2&#xff0e;系统用户管理&#xff1a;不管是…

如何选择正确的数据可视化图表

数据可视化是数据分析的重要组成部分&#xff0c;因为它们能够以图形格式有效地汇总大量数据。有许多可用的图表类型&#xff0c;每种类型都有自己的优势和用例。分析过程中最棘手的部分之一是选择使用这些可视化效果之一的正确方法来表示数据。 在本文中&#xff0c;我们根据需…

基于redis和threadlocal实现登录状态校验和拦截

1.流程图 单机节点下的登录状态校验 分布式节点下的登录状态校验 2.代码实现 实现步骤分为如下几步 实现WebMvcConfigurer接口&#xff0c;添加拦截器定义拦截器&#xff0c;需要配置两个interceptor&#xff0c;第一个用于刷新token&#xff0c;写threadlocal&#xff…