欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

分析LeakCanary原理

程序员文章站 2022-08-20 15:30:43
引入包 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'debug只在项目运行中出现,如果想要添加检测其他对象或者查看源码引入包 implementation 'com.squareup.leakcanary:leakcanary-android:2.0'可以观察你想观察的任何对象AppWatcher.INSTANCE.getObjectWatcher().watch();LeakCanary2...

LeakCanary 是检测内存泄漏的工具

引入包 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0' 就可以检测内存泄漏,不用进行初始化。

只在项目运行中出现,如果想要添加检测其他对象或者查看源码
引入包 implementation 'com.squareup.leakcanary:leakcanary-android:2.0'可以观察你想观察的任何对象AppWatcher.INSTANCE.getObjectWatcher().watch();

引发的问题 - 为什么添加 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0' application中不用初始化就可以检测内存泄漏 ?是在那里进行初始化的?

LeakCanary初始化的原理

在LeakCannary 中的AndroidManifest.xml 中

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

在 AppWatcherInstaller 类中的onCreate()中调用InternalAppWatcher.install()去初始化LeakCanary。

internal sealed class AppWatcherInstaller : ContentProvider() {

  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()

  /**
   * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
   * [LeakCanaryProcess] automatically sets up the LeakCanary code
   */
  internal class LeakCanaryProcess : AppWatcherInstaller() {
 
	  override fun onCreate(): Boolean {
	    val application = context!!.applicationContext as Application
	     //provider的onCreate方法中,调用了install方法
	    InternalAppWatcher.install(application)
	    return true
	  }
	
	  override fun query(
	    uri: Uri,
	    strings: Array<String>?,
	    s: String?,
	    strings1: Array<String>?,
	    s1: String?
	  ): Cursor? {
	    return null
	  }
	
	  override fun getType(uri: Uri): String? {
	    return null
	  }
	
	  override fun insert(
	    uri: Uri,
	    contentValues: ContentValues?
	  ): Uri? {
	    return null
	  }
	
	  override fun delete(
	    uri: Uri,
	    s: String?,
	    strings: Array<String>?
	  ): Int {
	    return 0
	  }
	
	  override fun update(
	    uri: Uri,
	    contentValues: ContentValues?,
	    s: String?,
	    strings: Array<String>?
	  ): Int {
	    return 0
	  }
	}
	
	fun install(application: Application) {
	    SharkLog.logger = DefaultCanaryLog()
	    SharkLog.d { "Installing AppWatcher" }
	    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)
	    onAppWatcherInstalled(application)
  }

app 在打包的时候,会merge-Mainfest 引入其他的androidMainfest 这样在自己的项目中就会合并成一个AndroidMainfest.xml 文件。

继续探索,ContentProvider是什么时候被创建的(什么时候调用onCreate函数)?
探索源码,在ActivityThread 中有

	private void handleBindApplication(AppBindData data){
	...
            mInitialApplication = app;
            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }
            // Do this after providers, since instrumentation tests generally start their
            // test thread at this point, and we don't want that racing.
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            ...
     }
    ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
 private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
            ...
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } 
        ...
    }
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
                mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
                setAuthorities(info.authority);
            }
            //调用 ContentProvider 的onCreate
            ContentProvider.this.onCreate();
        }

得到的结论:
LeakCanary在2.0以后的版本中不需要在application完成初始化任务,LeakCanary2.0以后利用了ContentProvider 在 Application 被创建之前被加载的原理,在ContentProvider的onCreate完成了初始化任务。

LeakCanary2.0 用kotlin 写的
LeakCanary 主要有两个配置文件 LeakCanary 和 AppWatcher

/**
 * The entry point API for LeakCanary. LeakCanary builds on top of [AppWatcher]. AppWatcher
 * notifies LeakCanary of retained instances, which in turns dumps the heap, analyses it and
 * publishes the results.
 *
 * LeakCanary can be configured by updating [config].
 */
object LeakCanary {

  /**
  *  通过Config 设置LeakCanary的一些配置
  */
  data class Config(
    /**
    * 是否堆转储 默认为true
    *
    */
    val dumpHeap: Boolean = true,
    /**
     * If [dumpHeapWhenDebugging] is false then LeakCanary will not dump the heap
     * when the debugger is attached. The debugger can create temporary memory leaks (for instance
     * if a thread is blocked on a breakpoint).
     *
     * Defaults to false.
     */
    val dumpHeapWhenDebugging: Boolean = false,
    /**
     * When the app is visible, LeakCanary will wait for at least
     * [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
     * freezes the UI and can be frustrating for developers who are trying to work. This is
     * especially frustrating as the Android Framework has a number of leaks that cannot easily
     * be fixed.
     *
     * When the app becomes invisible, LeakCanary dumps the heap after
     * [AppWatcher.Config.watchDurationMillis] ms.
     *
     * The app is considered visible if it has at least one activity in started state.
     *
     * A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
     * bothering developers as much but it could miss some leaks.
     *
     * Defaults to 5.
     */
    val retainedVisibleThreshold: Int = 5,

     /**
    * 观察的对象list 也可以自定义观察的对象
    *
    */
    val referenceMatchers: List<ReferenceMatcher> = AndroidReferenceMatchers.appDefaults,

    /**
     * List of [ObjectInspector] that provide LeakCanary with insights about objects found in the
     * heap. You can create your own [ObjectInspector] implementations, and also add
     * a [shark.AppSingletonInspector] instance created with the list of internal singletons.
     *
     * Defaults to [AndroidObjectInspectors.appDefaults]
     */
    val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,

    /**
     * Called on a background thread when the heap analysis is complete.
     * If you want leaks to be added to the activity that lists leaks, make sure to delegate
     * calls to a [DefaultOnHeapAnalyzedListener].
     *
     * Defaults to [DefaultOnHeapAnalyzedListener]
     */
    val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),

    /**
     * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
     * Called on a background thread during heap analysis.
     *
     * Defaults to [AndroidMetadataExtractor]
     */
    val metatadaExtractor: MetadataExtractor = AndroidMetadataExtractor,

    /**
     * Whether to compute the retained heap size, which is the total number of bytes in memory that
     * would be reclaimed if the detected leaks didn't happen. This includes native memory
     * associated to Java objects (e.g. Android bitmaps).
     *
     * Computing the retained heap size can slow down the analysis because it requires navigating
     * from GC roots through the entire object graph, whereas [shark.HeapAnalyzer] would otherwise
     * stop as soon as all leaking instances are found.
     *
     * Defaults to true.
     */
    val computeRetainedHeapSize: Boolean = true,

    /**
     * How many heap dumps are kept on the Android device for this app package. When this threshold
     * is reached LeakCanary deletes the older heap dumps. As several heap dumps may be enqueued
     * you should avoid going down to 1 or 2.
     *
     * Defaults to 7.
     */
    val maxStoredHeapDumps: Int = 7,

    /**
     * LeakCanary always attempts to store heap dumps on the external storage if the
     * WRITE_EXTERNAL_STORAGE is already granted, and otherwise uses the app storage.
     * If the WRITE_EXTERNAL_STORAGE permission is not granted and
     * [requestWriteExternalStoragePermission] is true, then LeakCanary will display a notification
     * to ask for that permission.
     *
     * Defaults to false because that permission notification can be annoying.
     */
    val requestWriteExternalStoragePermission: Boolean = false,

    /**
     * When true, [objectInspectors] are used to find leaks instead of only checking instances
     * tracked by [KeyedWeakReference]. This leads to finding more leaks and shorter leak traces.
     * However this also means the same leaking instances will be found in every heap dump for a
     * given process life.
     *
     * Defaults to false.
     */
    val useExperimentalLeakFinders: Boolean = false
  )

  /**
   * The current LeakCanary configuration. Can be updated at any time, usually by replacing it with
   * a mutated copy, e.g.:
   *
   * ```
   * LeakCanary.config = LeakCanary.config.copy(computeRetainedHeapSize = true)
   * ```
   */
  @Volatile
  var config: Config = if (AppWatcher.isInstalled) Config() else InternalLeakCanary.noInstallConfig
    set(newConfig) {
      val previousConfig = field
      field = newConfig
      logConfigChange(previousConfig, newConfig)
    }

  private fun logConfigChange(
    previousConfig: Config,
    newConfig: Config
  ) {
    SharkLog.d {
      val changedFields = mutableListOf<String>()
      Config::class.java.declaredFields.forEach { field ->
        field.isAccessible = true
        val previousValue = field[previousConfig]
        val newValue = field[newConfig]
        if (previousValue != newValue) {
          changedFields += "${field.name}=$newValue"
        }
      }
      "Updated LeakCanary.config: Config(${if (changedFields.isNotEmpty())
        changedFields.joinToString(", ") else "no changes"})"
    }
  }

  /**
   * Returns a new [Intent] that can be used to programmatically launch the leak display activity.
   */
  fun newLeakDisplayActivityIntent() = InternalLeakCanary.leakDisplayActivityIntent

  /**
   * Dynamically shows / hides the launcher icon for the leak display activity.
   * Note: you can change the default value by overriding the leak_canary_add_launcher_icon
   * boolean resource:
   *
   * ```
   * <?xml version="1.0" encoding="utf-8"?>
   * <resources>
   *   <bool name="leak_canary_add_launcher_icon">false</bool>
   * </resources>
   * ```
   */
  fun showLeakDisplayActivityLauncherIcon(showLauncherIcon: Boolean) {
    InternalLeakCanary.setEnabledBlocking(
        "leakcanary.internal.activity.LeakLauncherActivity", showLauncherIcon
    )
  }

  /**
   * Immediately triggers a heap dump and analysis, if there is at least one retained instance
   * tracked by [AppWatcher.objectWatcher]. If there are no retained instances then the heap will not
   * be dumped and a notification will be shown instead.
   */
  fun dumpHeap() = InternalLeakCanary.onDumpHeapReceived(forceDump = true)
}

AppWatcher 也有同样的配置文件

object AppWatcher {

  data class Config(
    /**
     * Whether AppWatcher should watch objects (by keeping weak references to them).
     *
     * Default to true in debuggable builds and false is non debuggable builds.
     */
    val enabled: Boolean = InternalAppWatcher.isDebuggableBuild,

    /**
     * Whether AppWatcher should automatically watch destroyed activity instances.
     *
     * Defaults to true.
     */
    val watchActivities: Boolean = true,

    /**
     * Whether AppWatcher should automatically watch destroyed fragment instances.
     *
     * Defaults to true.
     */
    val watchFragments: Boolean = true,

    /**
     * Whether AppWatcher should automatically watch destroyed fragment view instances.
     *
     * Defaults to true.
     */
    val watchFragmentViews: Boolean = true,

    /**
     * How long to wait before reporting a watched object as retained.
     *
     * Default to 5 seconds.
     */
    val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
  )

  /**
   * The current AppWatcher configuration. Can be updated at any time, usually by replacing it with
   * a mutated copy, e.g.:
   *
   * ```
   * LeakCanary.config = LeakCanary.config.copy(enabled = false)
   * ```
   */
  @Volatile
  var config: Config = if (isInstalled) Config() else Config(enabled = false)
    set(newConfig) {
      val previousConfig = field
      field = newConfig
      logConfigChange(previousConfig, newConfig)
    }

  private fun logConfigChange(
    previousConfig: Config,
    newConfig: Config
  ) {
    SharkLog.d {
      val changedFields = mutableListOf<String>()
      Config::class.java.declaredFields.forEach { field ->
        field.isAccessible = true
        val previousValue = field[previousConfig]
        val newValue = field[newConfig]
        if (previousValue != newValue) {
          changedFields += "${field.name}=$newValue"
        }
      }
      "Updated AppWatcher.config: Config(${if (changedFields.isNotEmpty())
        changedFields.joinToString(", ") else "no changes"})"
    }
  }

  /**
   * The [ObjectWatcher] used by AppWatcher to detect retained objects.
   */
  val objectWatcher
    get() = InternalAppWatcher.objectWatcher

  /** @see [manualInstall] */
  val isInstalled
    get() = InternalAppWatcher.isInstalled

  /**
   * [AppWatcher] is automatically installed on main process start by
   * [leakcanary.internal.AppWatcherInstaller] which is registered in the AndroidManifest.xml of
   * your app. If you disabled [leakcanary.internal.AppWatcherInstaller] or you need AppWatcher
   * or LeakCanary to run outside of the main process then you can call this method to install
   * [AppWatcher].
   */
  fun manualInstall(application: Application) = InternalAppWatcher.install(application)

}

LeakCanary检测内存泄漏原理

原理

是利用了WeakRefrence + RefrenceQueue的机制(仅被弱引用持有的对象,当对象被回收时,会存入到引用队列中),从引用队列中不断的获取对象,将已确认被GC的对象剔除,剩余未被回收的对象则定义为可能泄露的对象,当达到一定的判断条件时,通知用户内存泄露

监控activity:

在LeakCanary初始化的时候,主要通过在Application中注册registerActivityLifecycleCallbacks绑定Activity生命周期回调,并在Activity destory时,调用ObjectWatcher的watch方法进行检测

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {

  //activity destory 的时候,对Activity watch
  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中注册 ActivityLifecycleCallbacks  
       application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

监控Fragment:

监控AndroidSupportFragmentDestroyWatcher类中,主要通过在Activity中注册FragmentLifecycleCallbacks回调,当触发onFragmentViewDestroyed回调时,通过ObjectWatcher#watch检查fragment.mView对象(即Fragment#onCreateView创建的View)。当触发onFragmentDestroyed回调时,检查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)
    }
  }
}

核心代码类 ObjectWatcher

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  /**
   * References passed to [watch].
   * 正在被观察的对象,此时还未泄漏
   */
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
  //引用队列 配合弱引用 定位泄漏的对象。
  private val queue = ReferenceQueue<Any>()
  //泄漏监听
  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  /**
   * Watches the provided [watchedObject].
   *
   * @param description Describes why the object is watched.
   */
  @Synchronized fun watch(watchedObject: Any,description: String) {
    if (!isEnabled()) {
      return
    }
      
    //清除watchedObjects中不存在泄露的弱引用对象  
    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"
    }

    //将本次watch的对象加入到watchedObjects 的map中
    watchedObjects[key] = reference
      
    //通过checkRetainedExecutor,5秒后执行moveToRetained
    // 5s 后进行
    checkRetainedExecutor.execute {
      //若依旧存在引用,则通知onObjectRetainedListeners
      moveToRetained(key)
    }
  }

  
 
  @Synchronized private fun moveToRetained(key: String) {
      //清除无需观察的对象
    removeWeaklyReachableObjects()
     //通过key 去找引用,若存在引用,这证明5s后,本该回收的对象未能回收,可能存在内存泄露
    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.
      
    //queue是一个 ReferenceQueue队列,当一个对象被GC掉之后,会被加入到这个队列当中,即queue中存在的对象,都可以认为是不会存在内存泄露的对象
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
}

将要观察的对象放入 watchedObjects 这个map中( key值是生成的UID)

当对象销毁的时候,等待5秒(GC不是及时的,预留时间为5s,等待GC。MVP模式中,P等待网络请求,P持有Activity的引用,Activity销毁后,不能释放。可以手动配置这个时间为10s,10s后P回收,Activity回收,这时可认为长时间内不存在内存泄漏),去ReferenceQueue 中遍历,如果对象被回收,queue队列会存入这个引用,得到这个引用,从watchedObjects 移除。

在Activity 不可见的时候,会判断watchedObjects 保留的对象 如果大于0,会手动GC
在手动gc后发现
(1)如果不存在内存泄漏的对象,则不进行heap dump
(2)如果存在内存泄漏的对象个数小于 val retainedVisibleThreshold: Int = 5,,则不进行heap dump( config 配置 可以设置retainedVisibleThreshold)
就会dumpHeap 然后利用Shark(2.0以前是HAHA进行分析, 分析最短引用路径,进行通知。
onObjectRetainedListeners.forEach { it.onObjectRetained() } 最终会调用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
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      // 执行一次GC
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
	// 如果内存泄漏对象数量在阈值内,不生成dump文件分析
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      onRetainInstanceListener.onEvent(DebuggerIsAttached)
      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) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      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()
    // 创建dump文件,创建通知提示dump
    dumpHeap(retainedReferenceCount, retry = true)
  }

本文地址:https://blog.csdn.net/weixin_45427063/article/details/108200265