组内分享之 LeakCanary 解析(一)

初始化

LeakCanary 当前最新版本的初始化不需要显示调用 install 方法完成,原理是通过 ContentProvider 完成。

ContentProvider onCreate 方法在 handleBindApplication 中调用,并且在 Application 的 onCreate 之前。

接下来具体看 LeakCanary 实现的 ContentProvider

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

在 onCreate 方法中调用了 AppWatcher.manualInstall(application) 进行初始化

fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  checkMainThread()
  // 省略判断代码
  ...
  // Requires AppWatcher.objectWatcher to be set
  LeakCanaryDelegate.loadLeakCanary(application)

  watchersToInstall.forEach {
    it.install()
  }
}
复制代码

可以看到该方法有三个参数 上下文、任务延迟时间、待安装的监听器

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

默认的监听器包括了对 Activity、Fragment、RootView、Service 的监听

Activity

监听通过 LifeCycle 实现,Activity 在 onActivityDestroyed 回调中触发检查,

Fragment

添加了 高版本、androidx、android.support.v4 支持的 fragment

RootView

包括 Toast 等顶层的 View,在 View 的 onDetachFromWindow 方法回调时触发检查

Service

通过添加 ActivityThread 中的 Handler 的 Callback 接受消息,在接受到 STOP_SERVICE 消息时, 通过添加代理的方法,记录将要结束生命周期的 Service,在检测到调用 serviceDoneExecuting (一般是 Service 的每个生命周期调用结束时调用)触发泄漏检查

检查内存泄漏

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue
  watchedObjects[key] = reference
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}
复制代码

为每一个被监听的对象生成一个 key,并生成一个弱引用记录在 watchedObjects 中,并启动一个任务 dump 内存信息

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

可以看到多次调用了 removeWeaklyReachableObjects 方法,当弱引用被回收后会被添加到队列中,则通过遍历队列,删除记录的观察对象,当删除结束后,watchedObjects 中仍存在记录,则发生了内存泄漏

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)
}
复制代码

dump 内存信息

通过提交任务执行到 HeadDumpTrigger 中 checkRetainedObjects

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

  val config = configProvider()

  // 1.不能触发 dump
  if (iCanHasHeap is Nope) {
    if (iCanHasHeap is NotifyingNope) 
      // 触发 gc,获取仍然遗留的对象数量
      // 未能触发 dump,判断是否需要发送通知提示泄漏的数量
    return
  }
  // 2.触发 gc,获取仍然遗留的对象数量,判断是否需要 dump

  // 3.若 dump 发生的太快,则更新泄漏的数量不触发 dump

  // 4.移除内存泄漏数量通知,开始 dump
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}
复制代码

上述过程中,多次调用了 checkRetainedCount 方法

private fun checkRetainedCount(
  retainedKeysCount: Int,
  retainedVisibleThreshold: Int,
  nopeReason: String? = null
): Boolean {
  // 1.若泄漏的对象都已经被回收,调用 showNoMoreRetainedObjectNotification,通知用户
  val applicationVisible = applicationVisible
  val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
  // 2.若当前泄漏数量发生变化,打印日志
  // 3.判断当前泄漏数量是否超过阈值,默认为 5
  if (retainedKeysCount < retainedVisibleThreshold) {
    // 3.1 若应用在前台或应用切换到后台的时间小于 watchDuration 默认为 5s    
    // 则发送通知,并延迟执行尝试 dump,return true
    if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
      if (countChanged) {
        onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
      }
      showRetainedCountNotification(
        objectCount = retainedKeysCount,
        contentText = application.getString(
          R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
        )
      )
      // 3.2 将任务提交到 HandlerThread 中执行
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
      )
      return true
    }
  }
  return false
}
复制代码

即 checkRetainedCount 方法 return true,不会发生 dump

dump

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  // 使用 AndroidHeadDumper 进行 dump
  when (val heapDumpResult = heapDumper.dumpHeap()) {
    is NoHeapDump -> {
      // 1.1.失败且可重试,则进行重试,默认延迟 5s
      // 1.2.发送通知
    }
    is HeapDump -> {
      // 2 已经 dump 完成,清除已经完成 dump 的观察对象,通过 HeapAnalyzerService 分析日志
    }
  }
}
复制代码

通过 AndroidHeapDumper 进行 Dump

override fun dumpHeap(): DumpHeapResult {
  val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump

  // 显示 toast,LeakCanary is dumping memory to investigate leaks.

  // 发送正在 dump 的通知

  return try {
    // 调用 VMDebug dumpHprofData 方法进行 dump
    val durationMillis = measureDurationMillis {
      Debug.dumpHprofData(heapDumpFile.absolutePath)
    }
    if (heapDumpFile.length() == 0L) {
      SharkLog.d { "Dumped heap file is 0 byte length" }
      NoHeapDump
    } else {
      HeapDump(file = heapDumpFile, durationMillis = durationMillis)
    }
  } catch (e: Exception) {
    SharkLog.d(e) { "Could not dump heap" }
    // Abort heap dump
    NoHeapDump
  } finally {
    cancelToast(toast)
    notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
  }
}
复制代码

通过 HeapAnalyzerService 分析 Hprof 文件,该 service 继承自 IntentService

猜你喜欢

转载自juejin.im/post/7035955929841729573