leakcanary使用_LeakCanary浅析内存泄漏对象检测

本文基于LeakCanary2.5深入解析其内存泄漏检测机制。从添加依赖开始,逐步剖析LeakCanary如何通过Provider初始化,利用ObjectWatcher、ActivityDestroyWatcher和FragmentDestroyWatcher监听Activity和Fragment。讲解了WeakReference和ReferenceQueue在内存泄漏检测中的作用,以及如何通过触发堆转储进行内存泄漏分析。最后总结了LeakCanary检测内存泄漏的关键步骤。
摘要由CSDN通过智能技术生成

下面内容都是基于LeakCanary2.5

使用

根据官方文档的说明,只需在build.gradle中导入依赖com.squareup.leakcanary:leakcanary-android:xx即可

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

疑问

  1. 只导入了一个依赖是如何实现内存泄漏监听的?

  2. 内存泄漏信息是如何拿到的?

概貌

https://github.com/square/leakcanary
从源码目录中可以看到leakcanary工程中包含了多个module,并存在依赖关系3fb3e20b286838069f94c7f5548ff9ad.png

依赖分析

在导入leakcanary的工程中执行如下命令打印依赖树

./gradlew :app:dependencie
debugCompileClasspath - Compile classpath for compilation 'debug' (target  (androidJvm)).
+--- com.squareup.leakcanary:leakcanary-android:2.5
| +--- com.squareup.leakcanary:leakcanary-android-core:2.5
| | +--- com.squareup.leakcanary:shark-android:2.5
| | | \--- com.squareup.leakcanary:shark:2.5
| | | \--- com.squareup.leakcanary:shark-graph:2.5
| | | \--- com.squareup.leakcanary:shark-hprof:2.5
| | | \--- com.squareup.leakcanary:shark-log:2.5
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher:2.5
| | | | \--- com.squareup.leakcanary:shark-log:2.5
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72
| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
| | | \--- org.jetbrains:annotations:13.0
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android-androidx:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android-support-fragments:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | +--- com.squareup.leakcanary:plumber-android:2.5
| | | +--- com.squareup.leakcanary:shark-log:2.5
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
...

解惑

根据依赖引用关系进行查找,在leakcanary-object-watcher-android的AndroidManifest.xml中注册了一个provider

<manifest    xmlns:android="http://schemas.android.com/apk/res/android"    package="com.squareup.leakcanary.objectwatcher"    >  <application>    <provider        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"        android:authorities="${applicationId}.leakcanary-installer"        android:enabled="@bool/leak_canary_watcher_auto_install"        android:exported="false"/>  application>manifest>

Provider的onCreate方法中调用了AppWatcher.manualInstall方法

internal sealed class AppWatcherInstaller : ContentProvider() {  /** 1. [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.   */  internal class MainProcess : AppWatcherInstaller()  ...  override fun onCreate(): Boolean {    val application = context!!.applicationContext as Application    AppWatcher.manualInstall(application)    return true  }}

从AppWatcher源码中可以获得几点信息

  1. AppWatcher是使用ObjectWatcher的入口点,AppWatcher.objectWatcher负责检测保留的对象

  2. manualInstall方法用来初始化监听,会被provider自动初始化,也可以通过复写“leak_canary_watcher_auto_install”关闭自动初始化

/** * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to * watch any other object that you expect to be unreachable. */object AppWatcher {  ...  /**   * [AppWatcher] is automatically installed in the main process on startup. You can   * disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:   *   * ```   * <?xml version="1.0" encoding="utf-8"?>   * <resources>   *   <bool name="leak_canary_watcher_auto_install">falsebool>   * resources>   * ```   *   * If you disabled automatic install then you can call this method to install [AppWatcher].   */  fun manualInstall(application: Application) {    InternalAppWatcher.install(application)  }}

初始化方法中会设置对Activity、Fragment的监听,分别是ActivityDestroyWatcher、FragmentDestroyWatcher

internal object InternalAppWatcher {  ...  fun install(application: Application) {    checkMainThread()    if (this::application.isInitialized) {      return    }    InternalAppWatcher.application = application    if (isDebuggableBuild) {      SharkLog.logger = DefaultCanaryLog()    }    val configProvider = { AppWatcher.config }    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)    onAppWatcherInstalled(application)  }}

先看对Activity的监听,利用application的lifecycleCallback注册了destroy时机的回调,回调中使用objectWatcher.watch建立对activity的监听, 对Fragment的监听思想是一致的

internal class ActivityDestroyWatcher private constructor(  private val objectWatcher: ObjectWatcher,  private val configProvider: () -> Config) {  private val lifecycleCallbacks =    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {      override fun onActivityDestroyed(activity: Activity) {        if (configProvider().watchActivities) {          objectWatcher.watch(              activity, "${activity::class.java.name} received Activity#onDestroy() callback"          )        }      }    }  companion object {    fun install(      application: Application,      objectWatcher: ObjectWatcher,      configProvider: () -> Config    ) {      val activityDestroyWatcher =        ActivityDestroyWatcher(objectWatcher, configProvider)      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)    }  }}

ObjectWatcher的watch方法内容也比较简单

class ObjectWatcher constructor(  ...  // 观察者集合  private val onObjectRetainedListeners = mutableSetOf()  // 实例化引用队列  private val queue = ReferenceQueue()  ...  @Synchronized fun watch(    watchedObject: Any,    description: String  ) {    if (!isEnabled()) {      return    }    removeWeaklyReachableObjects()    // 1、生成uuid作为key    val key = UUID.randomUUID()        .toString()    val watchUptimeMillis = clock.uptimeMillis()    // 2、使用弱引用持有被观测对象    val reference =      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)    ...    // 3、放入被观测集合    watchedObjects[key] = reference    // 4、启动线程进行观测    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() }    }  }  // 如果被观测者没有强引用,将被观测者从被观测集合中移除  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 {      ref = queue.poll() as KeyedWeakReference?      if (ref != null) {        watchedObjects.remove(ref.key)      }    } while (ref != null)  }  ...}

其中有如下几步操作:

  1. 生成一个uuid

  2. 实例化一个KeyedWeakReference(继承自WeakReference),以弱引用的方式持有观测对象实例(这里是Activity实例)

  3. uuid作为key,reference作为value放入被观测集合watchedObjects(一个MutableMap)

  4. 在线程池checkRetainedExecutor中启动线程进行观测, 如果被观测对象还存在强引用,则有发生内存泄漏的嫌疑,将会通知监听者采取一定动作,具体是什么后面再说

其中涉及到一个知识点:
第2步使用watchedObject构造弱引用对象时,传入了一个引用队列queue,当watchedObject的变成弱可达时(这里可以理解为没有强引用时),会在GC、ReferenceHandler处理之后入队到queue,这里暂且称之为入队过程。
所以在queue中的对象可以认为不会发生内存泄漏,可以从观测集合中移除

如果观测对象依然是被强引用持有,那么监听者会有什么动作??

上面代码中可以看到,对仍然被强引用持有的对象会逐一调用观察者的onObjectRetained方法,那观察者又是什么?
OnObjectRetainedListener接口是被InternalLeakCanary类实现的

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {  ...  override fun onObjectRetained() = scheduleRetainedObjectCheck()  fun scheduleRetainedObjectCheck() {    if (this::heapDumpTrigger.isInitialized) {      heapDumpTrigger.scheduleRetainedObjectCheck()    }  }  ...}

heapDumpTrigger的scheduleRetainedObjectCheck方法会去post一个Runnable,在LeakCanary的后台线程去执行,实际执行的内容为checkRetainedObjects方法

internal class HeapDumpTrigger(  ...  // 发送一个延迟消息去后台执行,用于检查被观测对象  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() {    ...    val config = configProvider()  ...    // 获取还存在的被观测对象数量    var retainedReferenceCount = objectWatcher.retainedObjectCount    // 如果还存在被观测对象,触发一次GC,并再次统计被观测对象数量    if (retainedReferenceCount > 0) {      // 触发GC,将没有强引用的被观测对象移出被观测集合      gcTrigger.runGc()      // 重新计数      retainedReferenceCount = objectWatcher.retainedObjectCount    }  // 未达到设置条件,暂时不进行转储    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return    val now = SystemClock.uptimeMillis()    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis    // 如果距离上次堆转储的时间小于60秒,则等间隔达到60秒后再次触发对被观测对象的检查    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {      onRetainInstanceListener.onEvent(DumpHappenedRecently)      showRetainedCountNotification(          objectCount = retainedReferenceCount,          contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)      )      // 设置延时触发,保证两次堆转储时间间隔不小于60秒      scheduleRetainedObjectCheck(          delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis      )      return    }    dismissRetainedCountNotification()    // 执行堆转储    dumpHeap(retainedReferenceCount, retry = true)  }  ...}

过程分为如下几步:

  1. 判断被观测对象的数量大于0

  2. 触发GC,将没有强引用的被观测对象移出被观测集合

  3. 重新计数被观测对象,剩下的为有内存泄漏嫌疑的对象

  4. 判断是否达到堆转储条件

  5. 达到条件则进行堆转储

其中堆转储的条件受到内存泄漏对象个数、应用是否显示、观测期三个条件限制,其中符合转储的条件:

  1. 泄漏对象个数>=阈值(默认是5个)

  2. 0

到这里对LeakCanary的内存泄漏对象检测过程已经有个了整体的了解,后面则是拿到堆文件之后的分析工作

总结一下整个过程主要有如下几个关键点:

  1. AndroidManifest.xml中通过Provider进行初始化

  2. 利用lifecycleCallback回调的destroy时机建立观测关系
  3. 利用WeakReference和ReferenceQueue的特点确定泄漏对象
  4. 通过堆转储进行进一步分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值