LeakCanary2.3 核心原理浅析

文章目录

    • 概述
    • 核心流程拆解
    • 源码探究
      • SDK自动初始化
            • 关于ContentProvider初始化
        • AppWatcherInstaller
        • InternalAppWatcher
        • 小结
      • 自动监测Android特定对象
        • Activity的监测
        • Fragment的监测
          • android Fragment的Watcher初始化
          • androidx Fragment的Watcher初始化
          • support Fragment的Watcher初始化
        • 小结
      • 检测对象内存泄漏
        • 检测前准备
        • 初步检测
        • 最终判定
        • 小结
      • dump heap并解析
        • dump heap
        • 解析hprof
        • 小结
      • 通知和显示结果
    • 总结

概述

LeakCanary是Android开发中常用的用来检测内存泄漏的框架,它能够帮助开发者快速发现是否发生内存泄漏,并可视化的给予提示。

这里基于LeakCanary 2.3版本进行分析。

只需要在工程中进行简单的集成配置,就能自动对Activity、Fragment、fragment View进行自动检测。也可以通过ObjectWatcher#watch方法对指定对象进行检测。

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

仅需添加一行依赖,就完成了接入工作,当buildTypes为debug时,运行APP就会自动启动LeakCanary。

核心流程拆解

LeakCanary的核心功能,大致可以分为两块:

  • 检查对象是否被释放,若未被及时回收,则可能发生内存泄漏
  • dump heap并解析hprof文件,查找检测的对象生成引用链信息

检测泄漏功能的主要流程如下:

  1. SDK自初始化
    在1.X是通过手动调用方法初始化,2.X实现自动初始化。

  2. 自动对Android特定对象进行检测
    例如自动监测Activity、Fragment、fragment View生命周期销毁。

  3. 检测判断对象是否发生内存泄漏
    检查一个对象应该被回收,可能产生内存泄漏而没有被回收。

  4. dump heap至hprof文件并解析文件,生成泄漏引用链
    依赖另一个专门分析hprof的库来解析文件和生成分析结果。在1.X是依赖haha库,2.X改成依赖shark库。

  5. 在通知栏和Activity界面显示泄漏信息
    把泄漏分析结果发送到通知栏和Activity界面中显示可能导致泄漏的对象引用链。

源码探究

SDK自动初始化

前面看到LeakCanary只需要添加一行依赖,不需要开发者手动调用它的方法,那么它是如何自己把自己启动起来呢?可以猜测通过注册静态广播监听系统广播事件或者注册ContentProvider。

查找LeakCanary开源库中的AndroidManifest.xml,可以发现没有符合的静态注册广播,而存在ContentProvider:

<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="${applicationId}.leakcanary-installer"
    android:exported="false"/>

位于AndroidManifest.xml

在AppWatcherInstaller中会进行LeakCanary的初始化。

关于ContentProvider初始化

清单中注册的ContentProvider在应用启动的时候就会进行初始化:

[ActivityThread#handleBindApplication]

private void handleBindApplication(AppBindData data) {
    // ···
    Application app;
    // ···
    // 反射实例化Application,并会调用其attachBaseContext方法
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    // ···
    // 初始化注册的ContentProvider
    installContentProviders(app, data.providers);
    // ···
    // 触发Application的onCreate回调
    mInstrumentation.callApplicationOnCreate(app);
    // ···
}

ContentProvider启动后就会回调onCreate方法。

AppWatcherInstaller

AppWatcherInstaller继承ContentProvider,在其onCreate回调中,调用InternalAppWatcher#install方法进行初始化。

InternalAppWatcher

InternalAppWatcher负责SDK的初始化。

// 初始化ObjectWatcher,用于检测传入的对象是否未被及时回收
val objectWatcher = ObjectWatcher(
    clock = clock,
    checkRetainedExecutor = checkRetainedExecutor,
    isEnabled = { AppWatcher.config.enabled }
)

接着看它的init代码块:

init {
  // 通过反射获取InternalLeakCanary实例
  val internalLeakCanary = try {
    val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
    leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null)
  } catch (ignored: Throwable) {
    NoLeakCanary
  }
  @kotlin.Suppress("UNCHECKED_CAST")
  // onAppWatcherInstalled指向InternalLeakCanary#invoke(Application)方法
  onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}

接着看install方法:

fun install(application: Application) {
  SharkLog.logger = DefaultCanaryLog()
  // 检查是否在主线程,不在主线程则抛异常
  checkMainThread()
  if (this::application.isInitialized) {
    return
  }
  InternalAppWatcher.application = application

  val configProvider = { AppWatcher.config }
  // Activity自动监测相关初始化
  ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
  // Fragment自动监测相关初始化
  FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
  // 在init代码块中,onAppWatcherInstalled被赋值为InternalLeakCanary#invoke
  onAppWatcherInstalled(application)
}

该方法中进行了Activity和Fragment自动监测功能的初始化,最后初始化InternalLeakCanary。

接着看InternalLeakCanary的invoke方法:

override fun invoke(application: Application) {
  this.application = application

  // 检查是否在DEBUGGABLE启动,若不是则抛异常(除非开启设置)
  checkRunningInDebuggableBuild()

  // 添加OnObjectRetainedListener监听,单发生对象未及时回收时,会回调onObjectRetained
  AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

  // 用于dump heap
  val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)

  // 用于主动触发gc
  val gcTrigger = GcTrigger.Default

  val configProvider = { LeakCanary.config }

  // 开辟子线程,创建子线程通信handler
  val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
  handlerThread.start()
  val backgroundHandler = Handler(handlerThread.looper)

  // 用于调度dump heap
  heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
  )
  // 监听应用前台后台切换(通过Application#registerActivityLifecycleCallbacks监听)
  application.registerVisibilityListener { applicationVisible ->
    this.applicationVisible = applicationVisible
    heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
  }
  // 监听当前Resumed的Activity(通过Application#registerActivityLifecycleCallbacks监听)
  registerResumedActivityListener(application)
  // 初始化Shortcuts(查看泄漏信息的Activity的快捷方式)
  addDynamicShortcut(application)

  disableDumpHeapInTests()
}

该方法中初始化了核心功能模块的类和各种Activity生命周期监听。

小结

LeakCanary通过注册ContentProvider,实现应用启动时自动初始化,完成核心功能模块的工具类的初始化和Activity、Fragment生命周期的监听注册。

自动监测Android特定对象

Activity的监测

LeakCanary在初始化时调用ActivityDestroyWatcher#install:

fun install(
  application: Application,
  objectWatcher: ObjectWatcher,
  configProvider: () -> Config
) {
  val activityDestroyWatcher =
    ActivityDestroyWatcher(objectWatcher, configProvider)
  // 向Application注册ActivityLifecycleCallbacks
  application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}

该方法中就是注册Activity生命周期监听。
接着看这个ActivityLifecycleCallbacks的实现:

private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
      // watchActivities配置是否支持检测Activity,默认是true
      if (configProvider().watchActivities) {
        // 通过ObjectWatcher#watch进行检测
        objectWatcher.watch(
            activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }
  }

当Activity销毁时,触发onActivityDestroyed回调,在这里调用ObjectWatcher#watch传入Activity实例和一个描述字符串,进行内存泄漏检查检查。

Fragment的监测

在初始化完Activity的自动监测后,接着调用FragmentDestroyWatcher#install进行Fragment的监测初始化:

fun install(
  application: Application,
  objectWatcher: ObjectWatcher,
  configProvider: () -> AppWatcher.Config
) {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

  if (SDK_INT >= O) {
    // 添加对android.app.Fragment的支持
    fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
    )
  }

  // 如果使用了AndroidX库,添加对androidx.fragment.app.Fragment的支持
  getWatcherIfAvailable(
      ANDROIDX_FRAGMENT_CLASS_NAME,
      ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      objectWatcher,
      configProvider
  )?.let {
    fragmentDestroyWatchers.add(it)
  }

  // 如果使用support库,添加对support.v4.app.Fragment的支持
  getWatcherIfAvailable(
      ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
      ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      objectWatcher,
      configProvider
  )?.let {
    fragmentDestroyWatchers.add(it)
  }

  if (fragmentDestroyWatchers.size == 0) {
    return
  }

  // 注册Activity生命周期监听
  application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) {
      // 在onActivityCreated回调中,依次对各Watcher进行初始化
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  })
}

该方法中会将不同库的Fragment的Watcher处理类收集到集合中保存,并注册Activity生命周期监听,当触发onActivityCreated回调后,对各Watcher进行初始化。

android Fragment的Watcher初始化

调用AndroidOFragmentDestroyWatcher#invoke方法传入activity,向该activity的FragmentManager注册FragmentLifecycleCallbacks。

当触发onFragmentViewDestroyed回调时,通过ObjectWatcher#watch检查fragment.mView对象(即Fragment#onCreateView创建的View)。

当触发onFragmentDestroyed回调时,检查fragment对象是否泄漏。

androidx Fragment的Watcher初始化

在getWatcherIfAvailable方法中会通过反射创建AndroidXFragmentDestroyWatcher实例,通过supportFragmentManager注册LifecycleCallbacks监听Fragment生命周期。

同android Fragment的监听一样,在onFragmentViewDestroyed检测View,在onFragmentDestroyed检测Fragment

有一点不同的是,AndroidXFragmentDestroyWatcher在onFragmentCreated回调中,还会初始化ViewModel的监听。当ViewModel执行clear触发onCleared回调时,会检测ViewModel是否泄漏。

support Fragment的Watcher初始化

反射创建AndroidSupportFragmentDestroyWatcher进行监听,同样通过注册LifecycleCallbacks监听Fragment生命周期,在销毁时检测ViewFragment

小结

LeakCanary自动监测Activity和Fragment都是通过注册Lifecycle监听生命周期回调,在销毁时检测对象是否被及时回收。正常情况下,生命周期销毁后,对象仍没有被释放,则可能产生了泄漏。

检测对象内存泄漏

检测一个对象是否泄漏,是LeakCanary的核心功能,是所有流程中的关键流程。LeakCanary中调用ObjectWatcher#watch检测一个对象是否期望应该被释放,但仍然被持有,从而判定其产生泄漏。

在查看watch方法前,先看ObjectWatcher中的两个成员:

private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
  • watchedObjects:缓存待检测对象。以随机UUID作为key,以KeyedWeakReference作为value。
  • queue:引用队列。当我们用Reference包装一个对象且该对象仅被这个Reference持有时,那么该对象可被GC回收。对象回收后,之前包装用的Reference即会被加入引用队列中。

KeyedWeakReference继承自WeakReference,增加了几个成员,其中有一个key,持有UUID,可通过它定位自身在watchedObjects中的位置。

检测前准备

接下来进入watch方法:

@Synchronized fun watch(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  // 首先清除watchedObjects和queue中缓存
  removeWeaklyReachableObjects()
  // 生成UUID作为key
  val key = UUID.randomUUID()
      .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  // 创建KeyedWeakReference,传入key、待检测对象和queue
  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"
  }

  // 缓存至watchedObjects集合中
  watchedObjects[key] = reference
  checkRetainedExecutor.execute {
    // 执行检测
    moveToRetained(key)
  }
}

该方法首先清除watchedObjects和queue中缓存,之后生成key和KeyedWeakReference,缓存至watchedObjects
注意,KeyedWeakReference不仅保存了待检测对象和key,还保存了引用队列。当Reference的引用队列不为空时,当它包装的对象被回收时,会将自身加入引用队列中。

进入removeWeaklyReachableObjects方法看看如何清理缓存:

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 {
    // queue中元素不断移出队列
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      // 通过持有的key,从watchedObjects移除对应的KeyedWeakReference
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

回到ObjectWatcher#watch中,方法最后通过checkRetainedExecutor提交任务执行,通过它提交的任务会延迟5s执行:

private val checkRetainedExecutor = Executor {
  // 通过主线程handler发送延迟5s的消息(watchDurationMillis默认是5s)
  mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}

初步检测

当延迟5s后将会在主线程执行moveToRetained方法,接下来进入这个方法:

@Synchronized private fun moveToRetained(key: String) {
  // 清理queue和watchedObjects缓存
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  // 判断取出的元素不为空,若不为空则可能存在泄漏
  if (retainedRef != null) {
    // 记录当前时间
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    // 回调OnObjectRetainedListener接口,在InternalLeakCanary初始化时注册
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

该方法中首先清理queue和watchedObjects缓存,若对象已经被回收,则对应的KeyedWeakReference会加入queue中,从而也会将watchedObjects中对应元素移除。若在清理后仍能从watchedObjects中查找到元素,说明对象未被回收,可能发生泄漏。

若对象仍未被回收,则回调执行InternalLeakCanary#onObjectRetained方法,在该方法中又会调用HeapDumpTrigger#onObjectRetained方法:

fun onObjectRetained() {
  scheduleRetainedObjectCheck(
      reason = "found new object retained",
      rescheduling = false
  )
}

private fun scheduleRetainedObjectCheck(
  reason: String,
  rescheduling: Boolean,
  delayMillis: Long = 0L
) {
  // 省略避免发送handler消息的判断 ···
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    // 检查对象是否仍被持有
    checkRetainedObjects(reason)
  }, delayMillis)
}

最终判定

5s后对象仍未被回收,则通过handler切换到子线程再次检查,进入HeapDumpTrigger#checkRetainedObjects方法:

private fun checkRetainedObjects(reason: String) {
  val config = configProvider()
  // A tick will be rescheduled when this is turned back on.
  if (!config.dumpHeap) {
    SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
    return
  }

  // 先再清理一次集合缓存,然后获取watchedObjects中经过初步检测后,仍存在的元素个数
  var retainedReferenceCount = objectWatcher.retainedObjectCount

  // 判断是否存在泄漏对象个数
  if (retainedReferenceCount > 0) {
    // 通过Runtime.getRuntime().gc()立即触发GC,然后sleep 100ms
    gcTrigger.runGc()
    // GC后再次查询未被回收的对象个数
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }

  // 判断泄漏对象个数是否达到阈值(阈值默认5个)
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

  // 省略调试模式判断部分 ···

  // 省略两次dump时间间隔判断部分 ···

  SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
  // cancel通知
  dismissRetainedCountNotification()
  // 执行dump heap
  dumpHeap(retainedReferenceCount, retry = true)
}

该方法中会通过Runtime.getRuntime().gc()立即触发一次GC,然后等100ms再次检查对象是否被释放。若仍存在对象未被释放,则判断个数是否达到阈值(5个)、是否不处于调试中、距离前一次dump是否超过60s,都满足的话就会进行dump heap。

小结

LeakCanary检查一个期望不再被引用的对象是否泄漏是通过WeakReferenceReferenceQueue配合使用。

  • 首先创建WeakReference包装对象(需要传入引用队列),然后将WeakReference缓存至集合中。
  • 延迟5s后进行初次判断。若期间发生GC,WeakReference包装的对象不再被引用即会被回收,同时WeakReference自身加入引用队列。此时通过获取引用队列中的WeakReference,去移除WeakReference集合中的对应元素。若WeakReference集合还残留元素,则说明对应WeakReference没有加入引用队列,也意味着WeakReference没有被回收。
  • 切换子线程再最终判定。主动触发一次GC,等待100ms后,再次检查集合。若仍发现对象被引用未被释放,则判定这些缓存集合中的对象存在内存泄漏,将进行dump heap操作。

dump heap并解析

dump heap

当判定存在内存泄漏后,会调用HeapDumpTrigger#dumpHeap方法:

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean
) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  // 生成hprof文件
  val heapDumpFile = heapDumper.dumpHeap()
  if (heapDumpFile == null) {
    // 省略dump失败的重试和提醒部分 ···
  }
  lastDisplayedRetainedObjectCount = 0
  lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
  // 清除watchedObjects集合中早于heapDumpUptimeMillis的元素
  objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
  // 发送文件到HeapAnalyzerService解析
  HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}

该方法中将通过AndroidHeapDumper#dumpHeap生成hprof文件:

override fun dumpHeap(): File? {
  // 创建目标存储文件(有读写权限会存在sdcard,否则存在app目录下,文件命名规则yyyy-MM-dd_HH-mm-ss_SSS.hprof)
  val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null

  // ···

  return try {
    // dump heap
    Debug.dumpHprofData(heapDumpFile.absolutePath)
    if (heapDumpFile.length() == 0L) {
      SharkLog.d { "Dumped heap file is 0 byte length" }
      null
    } else {
      // 写入成功返回hprof文件
      heapDumpFile
    }
  } catch (e: Exception) {
    SharkLog.d(e) { "Could not dump heap" }
    // Abort heap dump
    null
  } finally {
    cancelToast(toast)
    notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
  }
}

该方法中调用系统Debug#dumpHprofData方法进行堆转储,保存在yyyy-MM-dd_HH-mm-ss_SSS.hprof命名规则的文件中,最后成功写入返回文件。

解析hprof

关于hprof协议格式可以参考hprof manual、堆转储HPROF协议。

hprof文件中分为header和records两部分,其中records由多个Record组成。
而Record又由4部分组成:

  • TAG:Record类型
  • TIME:时间戳
  • LENGTH: BODY的字节长度
  • BODY:存储的数据,例如trace、object、class、thread等信息

解析hprof文件主要就是根据TAG,创建对应的集合保存信息在内存中。

当生成hprof文件后,便会启动HeapAnalyzerService进行解析。HeapAnalyzerService继承自IntentService,在它的onHandleIntent方法中又会调用onHandleIntentInForeground方法:

override fun onHandleIntentInForeground(intent: Intent?) {
  if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
    SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
    return
  }

  // Since we're running in the main process we should be careful not to impact it.
  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
  // 取出传递过来的文件
  val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File

  val config = LeakCanary.config
  // 解析并将结果保存在HeapAnalysis中
  val heapAnalysis = if (heapDumpFile.exists()) {
    analyzeHeap(heapDumpFile, config)
  } else {
    missingFileFailure(heapDumpFile)
  }
  onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
  // 将解析结果回调出去
  config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}

该方法中从intent取出hprof文件后,调用analyzeHeap方法进行解析,结果将会保存在HeapAnalysis中,最后通过接口回调出去。

在analyzeHeap方法中,会创建HeapAnalyzer,执行真正的解析操作。(若apk有混淆,还可以应用混淆mapping文件)。
看HeapAnalyzer#analyze方法:

fun analyze(
  heapDumpFile: File,
  leakingObjectFinder: LeakingObjectFinder,
  referenceMatchers: List<ReferenceMatcher> = emptyList(),
  computeRetainedHeapSize: Boolean = false,
  objectInspectors: List<ObjectInspector> = emptyList(),
  metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
  proguardMapping: ProguardMapping? = null
): HeapAnalysis {
  val analysisStartNanoTime = System.nanoTime()

  // ···

  return try {
    listener.onAnalysisProgress(PARSING_HEAP_DUMP)
    // 打开文件、读取文件头
    Hprof.open(heapDumpFile)
        .use { hprof ->
          // 生成hprof文件中Record关系图,用HprofHeapGraph对象保存
          val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
          val helpers =
            FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
          // 构建从GC Roots到检测对象的最短引用路径,并返回结果
          helpers.analyzeGraph(
              metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
          )
        }
  } catch (exception: Throwable) {
    // 异常,返回失败结果
    HeapAnalysisFailure(
        heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
        HeapAnalysisException(exception)
    )
  }
}

该方法中首先打开hprof文件,读取文件头信息。之后解析文件,创建HprofHeapGraph保存指定TAG的Record信息到gcRoots(GC Roots信息)、objects(内存中对象信息)、classes(类信息)、instances(实例信息)集合中。

之后匹配待检测对象和HprofHeapGraph.objects找到对应对象ID。然后从GC Roots开始通过BFS查找到检测对象的调用路径,期间会根据LeakCanaryCore自带的白名单剔除名单中的类。最后从众多路径中找到一条最短引用路径,将结果保存在HeapAnalysisSuccess中返回。

HeapAnalysisSuccess中有applicationLeaks和libraryLeaks两个集合,分别保存应用代码中的泄漏路径和依赖库代码中的泄漏路径。

小结

首先调用系统Debug#dumpHprofData方法生成hprof文件,之后通过shark库进行解析,构建GC Roots到检测未被释放对象的最短引用路径

通知和显示结果

回到HeapAnalyzerService#onHandleIntentInForeground中,当执行完analyzeHeap方法,拿到结果后(成功返回HeapAnalysisSuccess,失败返回HeapAnalysisFailure),传给onHeapAnalyzedListener接口的onHeapAnalyzed回调方法。

onHeapAnalyzedListener的默认实现类是DefaultOnHeapAnalyzedListener,看它的onHeapAnalyzed方法:

override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
  SharkLog.d { "$heapAnalysis" }

  // 将结果写入数据库
  val id = LeaksDbHelper(application).writableDatabase.use { db ->
    HeapAnalysisTable.insert(db, heapAnalysis)
  }

  val (contentTitle, screenToShow) = when (heapAnalysis) {
    // 创建失败结果界面
    is HeapAnalysisFailure -> application.getString(
        R.string.leak_canary_analysis_failed
    ) to HeapAnalysisFailureScreen(id)
    // 创建成功结果界面
    is HeapAnalysisSuccess -> {
      val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
      val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
      application.getString(
          R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
      ) to HeapDumpScreen(id)
    }
  }

  if (InternalLeakCanary.formFactor == TV) {
    showToast(heapAnalysis)
    printIntentInfo()
  } else {
    // 通知栏提示
    showNotification(screenToShow, contentTitle)
  }
}

该方法中根据解析结果创建对应的Screen,Screen包含解析结果和渲染View的逻辑。之后创建跳转LeakActivity的PendingIntent,显示通知栏提示。当点击通知栏时打开LeakActivity,通过Screen来渲染结果视图。

总结

LeakCanaryCore的核心原理就是通过WeakReferenceReferenceQueue的组合,来检测期望被回收的对象。当期望被回收的对象没有被释放,就会dump heap生成hprof文件,借由shark库进行解析,构建泄漏对象最短引用路径。最后将结果在通知栏和Activity进行渲染显示。

你可能感兴趣的:(Android)