LeakCanary 2.0原理

LeakCanary 2.0原理

背景:

Android应用基于Java(kotlin)实现,因此它也将Java的优缺点继承了过来,典型的极就是内存回收问题,JVM在GC上让开发者尽量不需要关注垃圾对象的回收,但是如果开发真的不去关注内存问题就有可能造成应用的内存泄漏和OOM。比如错误使用handler、thread等都可能会带来OOM。应用程序在申请一定的内存后,内存又没有及时得到释放后就很容易发生OOM而导致crash,因此在实际开发过程中一方面要避免出现错误、另一方面要及时通过第三方工具(LeakCanary)排查问题。

简介

LeakCanary本质上就是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,通过集成工具代码到自己的Android工程当中就能够在程序调试开发过程中得到及时发现和定位内存泄漏问题。

LeakCanary示例图:

在这里插入图片描述

Github地址:仓库地址

用法

(1)1.6.1版本
引入LeakCanary到自己的项目中

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
    debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'

项目的自定义Application 类中添加,进行初始化任务。

    private fun initLeakCanary(){
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return
        }
        LeakCanary.install(this)
    }

(2)2.1版本
引入LeakCanary到自己的项目中,但是在2.0版本以后是非侵入式的,集成LeakCanary只需要引入库即可,内部是通过实现ContentProvider类完成初始化任务的。

PS:现在越来越多的库使用这种方式完成初始化任务,简化了第三方接入库的开发成本,但是也会导致业务方一旦想做启动优化等优化项就会显得不那么友好。

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

原理分析

前提:
继续阅读本文之前,请先熟悉:ReferenceQueue
在2.1的版本中代码是完全基于kotlin实现的,对于不熟悉kotlin的开发者可能不太友好。

问题:
Android 应用的完整生命周期由其组件的生命周期组成,如果想要分析应用是否存在内存泄漏只需要在组件比如在Activity或者Fragment 执行 onDestory() 方法之后,对Activity或者Fragment等对象的引用情况进行分析即可,如果在Activity执行onDestory之后,依旧有其他对象持有activity的引用,导致activity对象得不到及时释放,那么就有可能造成内存泄漏。

解决方案:

熟悉WeakReference的我们知道,WeakReference 创建时,如果指定一个ReferenceQueue 对象,在垃圾回收器检测到被引用对象的可到达性更改后,垃圾回收器会将已注册的引用对象添加到ReferenceQueue队列中,等待ReferenceQueue处理。但是如果当 GC 过后引用对象仍然不被加入 ReferenceQueue中,就有可能存在内存泄漏问题。

所以我们可以通过监听 Activity.onDestroy() 回调之后,通过弱引用(WeakReference)对象、ReferenceQueue和 GC来观测Activity引用的内存泄露情况,如果发现了未被回收的Activity对象,在找到该Activity对象是否被其他对象所引用,如果被其他对象引用,就进行 heap dump生成完整的内存引用链(最短引用链),并通过notification等方式展示出来。

LeakCanary启动

在2.0的版本中不需要业务方在application完成初始化任务,LeakCanary2.0利用了ContentProvider 在 Application 被创建之前被加载的原理,在ContentProvider的onCreate完成了初始化任务。

(1)ContentProvider的配置

   

(2)初始化任务
在onCreate方法中调用了InternalAppWatcher.install(application)进行初始化

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

LeakCanary初始化

LeakCanary通过InternalAppWatcher.install方法完成了主要的初始化任务。

(1)通过Looper检查是否在主线程中,否则抛出异常。

(2)通过ActivityDestroyWatcher完成监听 Activity.onDestroy()的操作。

(3)通过FragmentDestroyWatcher完成监听 Fragment.onDestroy()的操作。

(4)通过InternalLeakCanary初始化检测内存泄露过程中需要用到的对象。

  fun install(application: Application) {
    SharkLog.logger = DefaultCanaryLog()
    SharkLog.d { "Installing AppWatcher" }
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application

    val configProvider = { AppWatcher.config }
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
  }

其中onAppWatcherInstalled变量的初始化操作放在kotlin的init代码块里,对应的是InternalLeakCanary类的对象,然后调用InternalLeakCanary类的 override fun invoke(application: Application)完成检测内存泄露过程中需要用到的对象的初始化。

  init {
    val internalLeakCanary = try {
      val leakCanaryListener = Class.forName("leakcanary.internal.  init {
    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 as (Application) -> Unit
  }")
      leakCanaryListener.getDeclaredField("INSTANCE")
          .get(null)
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
    @kotlin.Suppress("UNCHECKED_CAST")
    onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
  }

ActivityDestroyWatcher.install()

(1)在install方法中创建了ActivityDestroyWatcher类对象activityDestroyWatcher

(2)注册了 Activity 生命周期监听类,在监听到 onDestroy() 时,调用 ObjectWatcher.watch() 方法监测 Activity的引用。

(3)其中objectWatcher在InternalAppWatcher类中创建。


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)
    }
  }
}

FragmentDestroyWatcher.install()

(1)FragmentDestroyWatcher为静态类,install方法为静态方法

(2)如果SDK是android 8.0及以上,则使用AndroidOFragmentDestroyWatcher监听Fragment的生命周期。
(3)通过getWatcherIfAvailable方法加载supportfragment的生命周期监听类AndroidSupportFragmentDestroyWatcher。

(4)通过getWatcherIfAvailable方法加载androidx的生命周期监听类AndroidXFragmentDestroyWatcher。

(5)通过监听activity的onCreate方法,在activity方法之后,在调用Fragment监听类的invoke方法,完成Fragment监听类的初始化任务。这里监听activity是因为Fragment需要依附于activity,当activity创建的时候,可能会有Fragment对象的创建。

总结一下,其实fragment有三个监听类,分别是AndroidOFragmentDestroyWatcher、AndroidSupportFragmentDestroyWatcher、AndroidXFragmentDestroyWatcher。但是这三个类的本质实现没有差别。


internal object FragmentDestroyWatcher {

  private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
  private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
    "leakcanary.internal.AndroidXFragmentDestroyWatcher"

  private const val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"
  private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
    "leakcanary.internal.AndroidSupportFragmentDestroyWatcher"

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

    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
          AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
      )
    }

    getWatcherIfAvailable(
        ANDROIDX_FRAGMENT_CLASS_NAME,
        ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

    getWatcherIfAvailable(
        ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
        ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
        objectWatcher,
        configProvider
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

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

    application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })
  }

  private fun getWatcherIfAvailable(
    fragmentClassName: String,
    watcherClassName: String,
    objectWatcher: ObjectWatcher,
    configProvider: () -> AppWatcher.Config
  ): ((Activity) -> Unit)? {

    return if (classAvailable(fragmentClassName) &&
        classAvailable(watcherClassName)
    ) {
      val watcherConstructor = Class.forName(watcherClassName)
          .getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
      @Suppress("UNCHECKED_CAST")
      watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit

    } else {
      null
    }
  }

  private fun classAvailable(className: String): Boolean {
    return try {
      Class.forName(className)
      true
    } catch (e: Throwable) {
      // e is typically expected to be a ClassNotFoundException
      // Unfortunately, prior to version 25.0.2 of the support library the
      // FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
      // Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
      // the FragmentManager.FragmentLifecycleCallbacks class, leading to the
      // AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
      // thrown here. So we're just covering our butts here and catching everything, and assuming
      // any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
      false
    }
  }
}

AndroidSupportFragmentDestroyWatcher

(1)本质上是通过监听Fragment的view来实现fragment的监听任务。

(2)注册了 fragment 生命周期监听类,在监听到 onDestroy() 时,调用 ObjectWatcher.watch() 方法监测 fragment的引用。


internal class AndroidSupportFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

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

InternalLeakCanary

(1)初始化一些检测内存泄露过程中需要的对象。

(2)addOnObjectRetainedListener设置可能存在内存泄漏的回调。

(3)通过AndroidHeapDumper进行内存泄漏之后进行 heap dump 任务。

(4)通过GcTrigger 手动调用 GC 再次确认内存泄露。

(5)启动内存泄漏检查的线程。

(6)通过registerVisibilityListener监听应用程序的可见性。


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

    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    addDynamicShortcut(application)

    disableDumpHeapInTests()
  }

监听ObjectWatcher

通过上面的分子,我们知道无论是activity还是frgment都是通过ObjectWatcher的watch方法来完成监听任务的。

(1)removeWeaklyReachableObjects移除若可达的对象(即将被GC的对象),也就是不会存在内存泄漏的对象。

(2)创建watchedObject引用的弱引用对象,并关联引用队列 ReferenceQueue中

(3)watchedObjects是LinkedHashMap对象,用于存储WeakReference对象。通常是指尚未确定是否存在内存泄露的对象引用。

(4)在主线程(mainHandler)中执行moveToRetained方法。(5s 后监测对象是否可达)。

  @Synchronized fun watch(
    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)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

 private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }

moveToRetained

(1)removeWeaklyReachableObjects方法主要是移除watchedObjects队列中 中将被 GC 的引用。

(2)如果当前引用retainedRef未被移除,仍在 watchedObjects队列中,说明仍未被 GC,说明可能会存在内存泄露,需要在次做判断。

(3)在InternalLeakCanary类中我们调用了addOnObjectRetainedListener了传入了OnObjectRetainedListener类对象。通过调用onObjectRetained方法,又回到了InternalLeakCanary类中。

(4)removeWeaklyReachableObjects方法中,移除了 watchedObjects 队列中的会被 GC 的引用对象,其他的就是可能内存泄露的对象。

  @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)
  }

onObjectRetained

通过上面分析,最终又会回调到InternalLeakCanary类的onObjectRetained方法。

(1)判断heapDumpTrigger是否完成了初始化。调用了HeapDumpTrigger的onObjectRetained方法。

(2)在HeapDumpTrigger的onObjectRetained方法调用了scheduleRetainedObjectCheck。

  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }

scheduleRetainedObjectCheck

(1)backgroundHandler的handle对象对应的子线程名称为:private const val LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump"

(2)通过调用checkRetainedObjects完成可能存在内存泄漏对象的检测任务。


  private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
      SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
      return
    } else {
      val verb = if (rescheduling) "Rescheduling" else "Scheduling"
      val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
      SharkLog.d { "$verb check for retained objects${delay} because $reason" }
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }

checkRetainedObjects

(1)retainedReferenceCount获取到objectWatcher可能存在内存泄漏的对象个数

(2)如果retainedReferenceCount的数量大于0,则手动执行GC,再次更新retainedReferenceCount数值。
(3)当前retainedReferenceCount泄露实例个数小于 5 时,不进行 heap dump。

(4)如果两次 heap dump的间隔小于60s,则不在进行heapdump处理,只做showRetainedCountNotification通知栏通知并在次发起scheduleRetainedObjectCheck检测。

(5)启动前台服务(ForegroundService) HeapAnalyzerService 来heap dump 文件处理。


  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
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_debugger_attached
          )
      )
      scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      scheduleRetainedObjectCheck(
          reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
          rescheduling = true,
          delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
    dismissRetainedCountNotification()
    dumpHeap(retainedReferenceCount, retry = true)
  }

GcTrigger

(1)可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。这时候应该主动调用一次 GC,可能可以避免一次 heap dump。

涞源:https://blog.csdn.net/u012551350/article/details/97079164

object Default : GcTrigger {
    override fun runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perform a gc.
      Runtime.getRuntime()
          .gc()
      enqueueReferences()
      System.runFinalization()
    }

checkRetainedCount

(1)如果不存在内存泄漏的对象,则不进行heap dump

(2)如果存在内存泄漏的对象个数小于 val retainedVisibleThreshold: Int = 5,,则不进行heap dump


  private fun checkRetainedCount(
    retainedKeysCount: Int,
    retainedVisibleThreshold: Int
  ): Boolean {
    val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
    lastDisplayedRetainedObjectCount = retainedKeysCount
    if (retainedKeysCount == 0) {
      SharkLog.d { "Check for retained object found no objects remaining" }
      if (countChanged) {
        showNoMoreRetainedObjectNotification()
      }
      return true
    }

    if (retainedKeysCount < retainedVisibleThreshold) {
      if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
        showRetainedCountNotification(
            objectCount = retainedKeysCount,
            contentText = application.getString(
                R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
            )
        )
        scheduleRetainedObjectCheck(
            reason = "found only $retainedKeysCount retained objects (< $retainedVisibleThreshold while app visible)",
            rescheduling = true,
            delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
        )
        return true
      }
    }
    return false
  }

dumpHeap

(1)showRetainedCountNotification显示泄漏个数的通知栏

(2)启动一个前台服务 HeapAnalyzerService 来分析 heap dump 文件。


  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    val heapDumpFile = heapDumper.dumpHeap()
    if (heapDumpFile == null) {
      if (retry) {
        SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
        scheduleRetainedObjectCheck(
            reason = "failed to dump heap",
            rescheduling = true,
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
        )
      } else {
        SharkLog.d { "Failed to dump heap, will not automatically retry" }
      }
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_dump_failed
          )
      )
      return
    }
    lastDisplayedRetainedObjectCount = 0
    lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }

dump

待更新

你可能感兴趣的:(LeakCanary 2.0原理)