Leakcanary Principle Analysis (based on Leakcanary 2.7)

What is a memory leak

A memory leak refers to an application that allocates memory space to the system and does not release it after it is used. As a result, the memory unit is occupied and the application cannot use the memory unit any more. In Android, it generally refers to the fact that the object has not been reclaimed after it has exceeded its life cycle. The types of leaks include:

  • The Java heap leaks
  • Native wild pointer
  • The FD handle leaks

Leaks can easily cause application process memory to surge and eventually cause OOM or Too Many Open Files related crashes. These crash points are usually the straw that breaks the camel’s back, rather than the root cause of the crash. It is necessary to dump the memory or open the handle to repair the problem intuitively

A solution to detect memory leaks

Liko 1 byte

When OOM and memory reach the top, HPROF file can be obtained through user insensitive dump. When App exits the background and memory is sufficient, HPROF can be cut and sent back for analysis. Online MAT analyzes HPROF file and generates links and reports. You can report large objects or create too many small objects frequently, which causes memory tightness

2. Well quickly KOOM

The system kernel copy-on-write (COW) mechanism is used to suspend the VM each time before dumping the memory image, and then fork the child process to perform the dump operation. The parent process immediately resumes the VM after the fork succeeds. The whole process takes only a few milliseconds for the parent process. The memory image is analyzed locally by an independent process in a single thread at idle time and deleted immediately after analysis. Github.com/KwaiAppTeam…

3. Leakcanary customization

Leverage the customization to LeakCanary and report the leak trace to the business Server

Leakcanary overview

LeakCanary: It automatically watches destroyed activities and fragments, triggers a heap dump, runs Shark Android and then displays the result.

LeakCanary, pictured as a bird, is actually a literal translation of Canary. In the early days, canaries were often used in mines to detect mine gases because of their sensitivity to harmful gases

LeakCanary comes in five parts

  • Application layer facing leakage listening, memory dump
  • Memory analysis Service
  • Shark, a heap analysis library based on the Kotlin implementation, is similar to the MAT tool
  • Leak trace UI display
  • Leak database

Leakcanary introduced

The new version of Leakcanary is introduced as simple as a gradle dependency

debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'
Copy the code

or

debugImplementation 'com. Squareup. Leakcanary: leakcanary - android - process: 2.7'
Copy the code

Similarities:

  • Both use AppWatcherInstaller$MainProcess(because you want to dump the MainProcess’s memory) and use ContentProvider to start the process

Automatic initialization when moving

  • Object detection and memory dump are in the main process
  • You can customize the initialization timing by configuring the LEAK_CANary_watcher_AUTO_install attribute of XML

The difference between the two:

  • Leakcanary – Android Memory analysis service HeapAnalyzerService is running on the main process child thread;
  • Leakcanary – Android-Process The memory analysis service HeapAnalyzerService runs in an independent leakCanary process

Leakcanary Startup time

Earlier versions need to handle the initialization of Leakcanary in the onCreate Application. In the new version, to reduce the access cost, the initialization of Leakcanary is stored in the AppWatcherInstaller defined by the library. The principle is that using the ContentProvider onCreate initializes before the Application onCreate (PS: later than the Application attachBaseContext)

AcitivtyThread.java

private void handleBindApplication(AppBindData data) {... Omit codetry {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        app = data.info.makeApplication(data.restrictedBackupMode, null); // Application#attachBaseContext(). Omit code// 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);/ / create a ContentProvider}}... Omit codetry {
            mInstrumentation.callApplicationOnCreate(app);// Application#onCreate()}... Omit code}Copy the code

Leakcanary initialization

AppWatcherInstaller. OnCreate call Leakcanary initialization

  /** * Start to initialize Leakcanary */ when contentProvider is created
  override fun onCreate(a): Boolean { val application = context!! .applicationContext as Application AppWatcher.manualInstall(application)/ / initialization
    return true
  }
Copy the code

AppWatcher. ManualInstall sets a default object detection delay and default object type

  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5).// After adding watchedObjects, enable detection for 5s
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) // Check the object
  ) {
    / /... Omit code

    // LeakCanary core component, which implements leak detection and triggers heap dump. The default implementation class is InternalLeakCanary. Kt
    LeakCanaryDelegate.loadLeakCanary(application)

    // Register the object detected by default
    watchersToInstall.forEach {
      it.install()
    }
  }
Copy the code

The default detected objects are

  • Activity
  • Fragment (View and Fragment itself)
  • ViewMoel
  • RootView(Window)
  • Service
  fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher), // Activity
      FragmentAndViewModelWatcher(application, reachabilityWatcher), / / fragments and the viewModel
      RootViewWatcher(reachabilityWatcher), // window
      ServiceWatcher(reachabilityWatcher) // service)}Copy the code

Each type is added to watchedObjects (a map that holds weak references to objects that need to be checked, defined in objectwatcher.kt) and waits for detection, mainly through component declarations that periodically listen for callbacks and hook points

Actiity detection timing

ActivityLifecycleCallbacks callback through registration, the activity to join in onActivityDestroyed watchedObjects for test

ActivityWatcher.kt

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate(a) {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}Copy the code

Fragment(AndroidX) Detection timing

By registering FragmentLifecycleCallbacks callback, OnFragmentViewDestroyed and onFragmentDestroyed add View and Fragment to watchedObjects for detection

AndroidXFragmentDestroyWatcher.kt

    override fun onFragmentViewDestroyed( fm: FragmentManager, fragment: Fragment ) {
      val view = fragment.view
      if (view ! =null) {
        reachabilityWatcher.expectWeaklyReachable(
          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 ) {
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback")}}Copy the code

ViewModel detects timing

Leakcanary adds a ViewModel to the current Fragment when Fragment onCreate, and this ViewModel follows the life cycle of the host, When onClear is executed, hook all viewModels of the current host and iterate to add those viewModels to watchedObjects

AndroidXFragmentDestroyWatcher.kt

    override fun onFragmentCreated( fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle? ) {
      // Add a ViewModel to the current Fragment when the Fragment executes onCreate
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }



ViewModelClearedWatcher.kt
  fun install( storeOwner: ViewModelStoreOwner, reachabilityWatcher: ReachabilityWatcher ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")override fun <T : ViewModel? > create(modelClass: Class<T>): T = ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T }) provider.get(ViewModelClearedWatcher::class.java)// Add to storeOwner
    }

    // Hook all viewModels of the current Fragment
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      @Suppress("UNCHECKED_CAST")
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null
    }

  // When the ViewModel is onCleared, all viewModels are added to the detection queue
  override fun onCleared(a) { viewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback")}}Copy the code

Service Detection Opportunity

A Service is similar to an Activity. It also adds a Service object to watchedObjects when onDestroy occurs, but since the Service does not expose a callback to the declaration cycle, So it is also through the hook to get the Service declaration cycle

ServiceWatcher.kt

  override fun install(a) {
    checkMainThread()
    check(uninstallActivityThreadHandlerCallback == null) {
      "ServiceWatcher already installed"
    }
    check(uninstallActivityManager == null) {
      "ServiceWatcher already installed"
    }
    try {
      // Hook ActivityThread mCallback from mH
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        // Proxy object
        Handler.Callback { msg ->
          // https://github.com/square/leakcanary/issues/2114
          // On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
          // instead of an IBinder. This crashes on a ClassCastException. Adding a type check
          // here to prevent the crash.
          if(msg.obj ! is IBinder) {return@Callback false
          }

          // Intercepts the STOP_SERVICE message, which preprocesses the service object to be destroyed
          if(msg.what == STOP_SERVICE) { val key = msg.obj as IBinder activityThreadServices[key]? .let { onServicePreDestroy(key, it) } }// Execute the original logicmCallback? .handleMessage(msg) ? :false}}// hook Activity Manage object
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        // Dynamic proxy objects
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          // Hook service Destroy. The servcie object is not available, so use onServicePreDestroy
          if(METHOD_SERVICE_DONE_EXECUTING == method.name) { val token = args!! [0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              // Encapsulate the service as a weak reference and trigger the retention check after 5 seconds
              onServiceDestroyed(token)
            }
          }
          // Execute the original logic
          try {
            if (args == null) {
              method.invoke(activityManagerInstance)
            } else {
              method.invoke(activityManagerInstance, *args)
            }
          } catch (invocationException: InvocationTargetException) {
            throw invocationException.targetException
          }
        }
      }
    } catch (ignored: Throwable) {
      SharkLog.d(ignored) { "Could not watch destroyed services"}}}Copy the code

Look again at the onServiceDestroyed method

  private fun onServiceDestroyed(token: IBinder) {
    // Match the token to the service object obtained during preprocessingservicesToBeDestroyed.remove(token)? .also { serviceWeakReference -> serviceWeakReference.get()? .let { service ->// Add the service object to watchedObjects
        reachabilityWatcher.expectWeaklyReachable(
          service, "${service::class.java.name} received Service#onDestroy() callback")}}}Copy the code

All default detected objects have been read above, after which the object will be encapsulated as a weak reference and associated with a reclaim queue. The principle is:

  • An object containing a weak reference will be added to the collection queue if GC is performed without other references

For example, an Activity A is encapsulated as A weak-reference weakA, and the weak-reference weakA is added to watchedObjects. After 5s, GC is triggered. If A’s reference is added to the recycle queue, then A can be recycled. That removes weakA from watchedObjects. On the other hand, if the reference of A is not added to the recycle queue and the object is referenced by another object, the memory leak is considered and the Heap dump and Analyze processes are triggered

Leakcanary Heap dump and Analyze processes

As mentioned in the previous article, the target object can be added to watchedObjects at a specific time, and heap dump is started after it is judged to be leaked. This process is quite complicated. Let’s take an example of Activity leakage and take a look at a UML diagram

sequenceDiagram autonumber ActivityWatcher ->>+ Activity: registerLifecycler Activity ->> Activity: onDstroy: Activity ->>- ActivityLifecycleCallbacks: ActivityLifecycleCallbacks ->>+ ObjectWatcher: onActivityDestroyed ObjectWatcher ->> ObjectWatcher: KeyedWeakReference ObjectWatcher -->>+ InternalLeakCanary: MoveToRetained (post a 5s delayed runnable) InternalLeakCanary ->> InternalLeakCanary: onObjectRetained InternalLeakCanary ->>+ HeapDumpTrigger: scheduleRetainedObjectCheck HeapDumpTrigger ->> HeapDumpTrigger: scheduleRetainedObjectCheck HeapDumpTrigger ->>+ HeapDumpControl: checkRetainedObjects HeapDumpControl ->>- HeapDumpTrigger: iCanHasHeap HeapDumpTrigger -->>+ ObjectWatcher: get retainedObjectCount ObjectWatcher -->>- HeapDumpTrigger : retainedObjectCount > 0 HeapDumpTrigger ->> HeapDumpTrigger: gc HeapDumpTrigger -->>+ ObjectWatcher: get retainedObjectCount ObjectWatcher -->>- HeapDumpTrigger : retainedObjectCount HeapDumpTrigger ->> HeapDumpTrigger: checkRetainedCount HeapDumpTrigger ->> HeapDumpTrigger: dumpHeap HeapDumpTrigger -->>+ ObjectWatcher: ObjectWatcher ->> ObjectWatcher: clearObjectsWatchedBefore ObjectWatcher -->>- HeapDumpTrigger: HeapDumpTrigger -->>- HeapAnalyzerService: HeapAnalyzerService ->> HeapAnalyzerService: runAnalysis HeapAnalyzerService ->> HeapAnalyzer: analyzeHeap HeapAnalyzer ->> HeapAnalyzer: analyze

Go back to when the Activity object is listening

ActivityWatcher.kt

private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate(a) {
      override fun onActivityDestroyed(activity: Activity) {
        // Add watchedObjects to Activity onDestroy
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}Copy the code
  • Tips: In the above code, the noOpDelegate uses Kotlin’s delegate mechanism and Java’s dynamic proxy mechanism to implement only the methods of the interface of interest, and the other methods are automatically completed by the delegate to clean up the code

ReachabilityWatcher is actually an ObjectWatcher, and look at expectweak Reachable methods

  @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) {
    if(! isEnabled()) {return
    }
    // Clean up the objects that have been reclaimed
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // Encapsulate a weak reference. If there are no other references, the objects in this weak reference will be collected during GC and added to the 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"
    }

    // Add a retention monitor map. If objects are still in the map after GC, they are considered leaked
    watchedObjects[key] = reference
    // Add a monitor runnable, execute after 5s
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
Copy the code

First take a look at removeWeaklyReachableObjects implementation, this method has called in many places, is to be able to keep clear of in time has been recycled object records

  private fun removeWeaklyReachableObjects(a) {
    // 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 {
      /** * Weak references contain objects that, if reclaimed, are added to the associated reclaim queue */
      ref = queue.poll() as KeyedWeakReference?
      if(ref ! =null) {
        // The reclaimed object removes the record from watchedObjects
        watchedObjects.remove(ref.key)
      }
    } while(ref ! =null)}Copy the code

After the cleanup is complete, Leakcanary encapsulates the Activity object as a weak reference and associates it with a UUID generated key and a reclaim queue. Add this key and weak reference key-value to watchedObjects, and randomly checkRetainedExecutor posts a runnable to the main thread at a default initialization delay of 5 minutes. Let’s take a look at moveToRetained(key) implementation

  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if(retainedRef ! =null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      / / onObjectRetainedListeners actual is initialized in the incoming InternalLeakCanary. Kt object
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }
Copy the code

Let’s go back to the initialization logic handled by InternalLeakCanary at initialization

InternalLeakCanary.kt

  override fun invoke(application: Application) {
    // Pass in the Application object
    _application = application

    checkRunningInDebuggableBuild(a)

    // Register object retention detection listeners to ObjectWatcher
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    // heap dump
    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    // Check whether GC conditions are met
    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

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

    // Determine whether heap dump conditions are met
    heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
    )
    // Apply front-end and background listener logic differentiation processing
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)

    // Add the LeakCanary icon on the desktop
    addDynamicShortcut(application)

    // We post so that the log happens after Application.onCreate()
    mainHandler.post {
      // https://github.com/square/leakcanary/issues/1981
      // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
      // which blocks until loaded and that creates a StrictMode violation.
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
  }
Copy the code

Back to front onObjectRetained method, you will call to HeapDumpTrigger. The kt of scheduleRetainedObjectCheck ()

HeapDumpTrigger.kt

  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) { // If the value is greater than 0, it indicates that the system is already monitoring
      return
    }
    // Record the current detection time
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({ / / the child thread
      checkScheduledAt = 0
      // Test the retained objects
      checkRetainedObjects()
    }, delayMillis)
  }
Copy the code

Frequent repeated checks are avoided by setting the timestamp and a runnable is posted to the child thread

HeapDumpTrigger.kt

  private fun checkRetainedObjects(a) {
    // Whether dump heap can be triggered
    val iCanHasHeap = HeapDumpControl.iCanHasHeap()

    val config = configProvider()

    if (iCanHasHeap is Nope) {
      if (iCanHasHeap is NotifyingNope) { // Send a notification, the user clicks, can force trigger monitoring
        / /... Omit code
      }
      return
    }

    // Get the number of remaining objects
    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc() / / triggers the GC
      retainedReferenceCount = objectWatcher.retainedObjectCount // Get the number of objects that were not collected again
    }

    /** * Determine whether to enable the dump heap based on the number of retained heap stores * to minimize the impact, the number of heap stores applied on the front and back ends is different. By default, there are at least 5 heap stores for the front end and at least 1 heap stores for the back end (the time required to dump the back end exceeds the monitoring period) */
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { // The heap dump is not repeated within one minuteonRetainInstanceListener.onEvent(DumpHappenedRecently) showRetainedCountNotification( objectCount = retainedReferenceCount, contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait) ) scheduleRetainedObjectCheck(  delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis )return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    dumpHeap( // Triggers dump heap
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility")}Copy the code

According to the number of watchedObjects checked after GC is triggered, the default value is greater than or equal to 5 in the foreground and 1 in the background. When the condition is met, heap dump is triggered

HeapDumpTrigger.kt

  private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean, reason: String ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // Dump heap result
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> { / / fail
        / /... Omit code
      }
      is HeapDump -> { / / success
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
         // Start an IntentService to analyze hprof in the child thread
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
        )
      }
    }
  }
Copy the code

The dumped hprof files are handed to the HeapAnalyzerService for analysis, and the HeapAnalyzerService starts Shark to analyze the memory files. Shark is not familiar with it, so I’m not going to analyze it. Each step in the analysis process has a corresponding callback:

OnAnalysisProgressListener.kt

  enum class Step {
    PARSING_HEAP_DUMP,
    EXTRACTING_METADATA,
    FINDING_RETAINED_OBJECTS,
    FINDING_PATHS_TO_RETAINED_OBJECTS,
    FINDING_DOMINATORS,
    INSPECTING_OBJECTS,
    COMPUTING_NATIVE_RETAINED_SIZE,
    COMPUTING_RETAINED_SIZE,
    BUILDING_LEAK_TRACES, // Build the leak path
    REPORTING_HEAP_ANALYSIS
  }
Copy the code

Notably, Leakcanary has opened a callback for the results of memory analysis, allowing users to implement the interface and report the results to their own service servers

HeapAnalyzerService.kt#onHandleIntentInForeground()
    /** * Can be customized onHeapAnalyzedListener, A trace * leaktracewrapper. wrap(heapAnalysis.tostring (), 120) formats the trace */
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
Copy the code

The official recommended method is

class LeakUploader : OnHeapAnalyzedListener {

  // The default implementation of LeakCanary is to record future leaks and avoid repeated reporting
  val defaultListener = DefaultOnHeapAnalyzedListener.create()

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    TODO("Upload heap analysis to server")

    // Delegate to default behavior (notification and saving result) 
    defaultListener.onHeapAnalyzed(heapAnalysis)
  }
}

class DebugExampleApplication : ExampleApplication(a){

  override fun onCreate(a) {
    super.onCreate()
    // Use the custom OnHeapAnalyzedListener to delegate the original logic after processing your own services
    LeakCanary.config = LeakCanary.config.copy(
        onHeapAnalyzedListener = LeakUploader()
    )
  }
}
Copy the code

Finally attach Leakcanary official documentation about how to fix the leak, it illustrates how to check the leakage source square. The dead simple. IO/Leakcanary /…

Finally, the final excerpt from LeakCanary explains how to fix memory leaks using weak references, using weak references with caution and addressing the problem at the source of the leak

Memory Leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.