For Android Developer, many open source libraries are essential knowledge points for development, from the use of ways to implementation principles to source code parsing, which require us to have a certain degree of understanding and application ability. So I’m going to write a series of articles about source code analysis and practice of open source libraries, the initial target is EventBus, ARouter, LeakCanary, Retrofit, Glide, OkHttp, Coil and other seven well-known open source libraries, hope to help you πŸ˜‡πŸ˜‡

Official account: byte array

Article Series Navigation:

  • Tripartite library source notes (1) -EventBus source detailed explanation
  • Tripartite library source notes (2) -EventBus itself to implement one
  • Three party library source notes (3) -ARouter source detailed explanation
  • Third party library source notes (4) -ARouter own implementation
  • Three database source notes (5) -LeakCanary source detailed explanation
  • Tripartite Library source note (6) -LeakCanary Read on
  • Tripartite library source notes (7) -Retrofit source detailed explanation
  • Tripartite library source notes (8) -Retrofit in combination with LiveData
  • Three party library source notes (9) -Glide source detailed explanation
  • Tripartite library source notes (10) -Glide you may not know the knowledge point
  • Three party library source notes (11) -OkHttp source details
  • Tripartite library source notes (12) -OkHttp/Retrofit development debugger
  • Third party library source notes (13) – may be the first network Coil source analysis article

LeakCanary is an open source memory leak detection tool for Android from Square that helps developers discover memory leaks and identify their source, helping to reduce OutOfMemoryError situations. In current application development, it is also an important way to achieve performance optimization. Many interviewers will ask how LeakCanary is implemented when examining performance optimization

This article is based on its current (2020/10/06) the latest submission for source analysis, the specific Git version node is: 9F62126e, to understand LeakCanary’s overall operation process and implementation principle πŸ˜‚πŸ˜‚

1. Supported memory leak types

We often say that LeakCanary detects memory leaks within an app, but what exactly does it support? LeakCanary website has this introduction:

LeakCanary automatically detects leaks of the following objects:

  • destroyed Activity instances
  • destroyed Fragment instances
  • destroyed fragment View instances
  • cleared ViewModel instances

We can also find the answer from LeakCanary’s appWatcher.config class. The Config class is used to configure whether to enable memory detection. From its configuration items, it can be seen that Leakcanary supports four types: Activity, Fragment, FragmentView, and ViewModel

data class Config(
    /** * 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./** * Whether AppWatcher should automatically watch cleared [androidx.lifecycle.ViewModel] * instances. * * Defaults to true. */
    val watchViewModels: Boolean = true./** * How long to wait before reporting a watched object as retained. * * Default to 5 seconds. */
    val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),

    /** * Deprecated, this didn't need to be a part of the API. * Used to indicate whether AppWatcher should watch objects (by keeping weak references to * them). Currently a no-op. * * If you do need to stop watching objects, simply don't pass them to [objectWatcher]. */
    @Deprecated("This didn't need to be a part of LeakCanary's API. No-Op.")
    val enabled: Boolean = true
  )
Copy the code

2. Initialization

Now, we just need to add the following dependencies to introduce LeakCanary into our project. Without any additional actions such as initialization behavior, LeakCanary will automatically start up and start monitoring when the app launches

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.4'
}
Copy the code

Normally, third-party libraries like this need to be initialized and started by passing in an ApplicationContext object from outside. This is also true of the 1.x version of LeakCanary, but in the 2.x version, LeakCanary submits the initial process to the AppWatcherInstaller ContentProvider to automate

internal sealed class AppWatcherInstaller : ContentProvider() {

    /** * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
    internal class MainProcess : AppWatcherInstaller(a)/** * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`, * [LeakCanaryProcess] automatically sets up the LeakCanary code */
    internal class LeakCanaryProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
        valapplication = context!! .applicationContextas Application
        AppWatcher.manualInstall(application)
        return true}...}Copy the code

Since the ContentProvider is initialized by the system calling its onCreate() method before the Application is created, Therefore, LeakCanary can get the Context initialized with the AppWatcherInstaller and launch with the app, simplifying the introduction cost for the user in this way. And since our reference is by debugImplementation, the official release will automatically remove all references to LeakCanary, further simplifying the cost of introduction

Jetpack also includes a component that implements the initialization logic through the ContentProvider: AppStartup. The implementation is similar, but if each of the three libraries is initialized with a custom ContentProvider, the AppStartup speed will be impressive :joy: so Jetpack’s official AppStartup should be the mainstream

The AppWatcherInstaller eventually passes the Application object to the install(Application) method of the InternalAppWatcher

/** * Note: this object must load fine in a JUnit environment */
internal objectInternalAppWatcher {...val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true})fun install(application: Application) {
    // Check whether it is in the main thread
    checkMainThread()
    // Avoid repeated initialization
    if (this::application.isInitialized) {
      return
    }
    InternalAppWatcher.application = application
    if (isDebuggableBuild) {
      SharkLog.logger = DefaultCanaryLog()
    }
	// Get the default configuration, which detects all four types by default
    val configProvider = { AppWatcher.config }
    / / testing Activity
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    // Check Fragment, FragmentView, ViewModelFragmentDestroyWatcher. Install (application, objectWatcher configProvider) onAppWatcherInstalled... (application)}}Copy the code

LeakCanary’s specific memory leak detection logic can be divided into three categories:

  • ObjectWatcher. Example Detect memory leaks of objects
  • ActivityDestroyWatcher. Detect memory leaks in the Activity
  • FragmentDestroyWatcher. Check Fragment, FragmentView, and ViewModel for memory leaks

Both ActivityDestroyWatcher and FragmentDestroyWatcher rely on ObjectWatcher, Because activities, fragments, FragmentView, and ViewModel are essentially different types of Objects

ObjectWatcher: Detects any object

We know that a memory leak has occurred when an object is no longer referred to by us, and if the object cannot be reclaimed due to code errors or other reasons. So how does LeakCanary know if your app has a memory leak?

This can be done by referring to the queue ReferenceQueue. Let’s do a little example

/ * * * the author: leavesC * time: 2020/10/06 14:26 * description: * GitHub:https://github.com/leavesC * /
fun main(a) {
    val referenceQueue = ReferenceQueue<Pair<String, Int>? > ()var pair: Pair<String, Int>? = Pair("name".24)
    val weakReference = WeakReference(pair, referenceQueue)

    println(referenceQueue.poll()) //null

    pair = null

    System.gc()
    // Hibernate for a period of time after GC to wait for the pair to be reclaimed
    Thread.sleep(4000)

    println(referenceQueue.poll()) //java.lang.ref.WeakReference@d716361
}
Copy the code

It can be seen that the return value of referencequeue.poll () becomes non-null after GC, which is caused by a combined feature of WeakReference and referenceQueue: When a WeakReference object is declared, if ReferenceQueue is passed in at the same time as the construction parameter, then when the object held by WeakReference is reclaimed by GC, the JVM will store the WeakReference into the ReferenceQueue associated with it. Relying on this feature, we can implement memory leak detection

For example, when the user presses the back key to exit an Activity, the Activity object should normally be reclaimed by the system shortly thereafter. We can listen for the Activity’s onDestroy callback. During the callback, the Activity object is saved to the WeakReference associated with the ReferenceQueue. After a period of time (several GC can be triggered actively), the value in the ReferenceQueue is detected. If it is null all the time, a memory leak has occurred. LeakCanary is achieved in this way

ObjectWatcher encapsulates this logic, and here’s a look at the implementation logic

The starting method of ObjectWatcher is watch(Any, String), which is used to listen to the specified object

	/** * References passed to [watch]. * Is used to save the object to be monitored. MapKey is the unique identifier of the object, mapValue is the weak reference of the object */
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

    //KeyedWeakReference Indicates the associated reference queue
    private val queue = ReferenceQueue<Any>()

	/**
     * Watches the provided [watchedObject].
     *
     * @param description Describes why the object is watched.
     */
    @Synchronized
    fun watch(watchedObject: Any, description: String) {
        if(! isEnabled()) {return
        }
        removeWeaklyReachableObjects()
        // Generate a unique identifier for the watchedObject
        val key = UUID.randomUUID().toString()
        val watchUptimeMillis = clock.uptimeMillis()
        // Create the weak reference associated with watchedObject
        valreference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, Queue)... watchedObjects [key] = reference checkRetainedExecutor. Execute {moveToRetained (key)}}Copy the code

The main logic of the watch() method:

  1. For eachwatchedObjectTo generate aUnique Identification key, and build one with the keywatchedObjectThe weak referencesKeyedWeakReference, save the weak reference towatchedObjectsIn the.ObjectWatcherYou can monitor multiple objects in sequence, and each object will be stored firstwatchedObjects δΈ­
  2. External by incomingcheckRetainedExecutorTo specify the trigger time for detecting memory leaks, passmoveToRetainedMethod to determine whether a memory leak has actually occurred

KeyedWeakReference is a user-defined subclass of WeakReference, which contains a unique key to identify a specific object and also contains a retainedUptimeMillis field to mark whether there is a memory leak

class KeyedWeakReference(
        referent: Any,
        val key: String,
        val description: String,
        val watchUptimeMillis: Long,
        referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
        referent, referenceQueue
) {
    /** * Time at which the associated object ([referent]) was considered retained, Or -1 if it hasn't * been yet. * is used to mark whether the referent has not been reclaimed. If yes, the value is not -1 */
    @Volatile
    var retainedUptimeMillis = -1L

    companion object {
        @Volatile
        @JvmStatic
        var heapDumpUptimeMillis = 0L}}Copy the code

MoveToRetained (moveToRetained) is used to determine if the key is retained, if not, remove the weak reference, and if it is, update the retainedUptimeMillis value to mark it as leaked. And at the same time through callbacks onObjectRetainedListeners to analyze the chain of memory leaks

    @Synchronized
    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects()
        val retainedRef = watchedObjects[key]
        if(retainedRef ! =null) {
            // Record the current time
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    // Remove weak references to an object if no memory leaks are detected
    // This method is called multiple times
    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 {
            ref = queue.poll() as KeyedWeakReference?
            if(ref ! =null) {
                // If ref is not null, then the reference to the object associated with ref is removed
                watchedObjects.remove(ref.key)
            }
        } while(ref ! =null)}Copy the code

ActivityDestroyWatcher: Detects an Activity

Once you understand the ObjectWatcher process, it will be easier to look at ActivityDestroyWatcher later. ActivityDestroyWatcher to the Application to register a ActivityLifecycleCallbacks callback, when I received after each Activity carried out onDestroy callback, The Activity object will be transferred to the ObjectWatcher for listening

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

}
Copy the code

FragmentDestroyWatcher: Detects fragments

As anyone who has done Android application development should know, Google now provides two basic dependency packages: Support and AndroidX. The Support version is no longer maintained, and the mainstream is AndroidX. In order to take care of the old project, LeakCanary has kindly provided the Fragment memory detection function for the two versions respectively

FragmentDestroyWatcher can be viewed as a dispenser that selects different detection methods depending on the external environment. Its main logic is:

  • The system version is greater than or equal to 8.0. Using AndroidOFragmentDestroyWatcher to detect fragments, FragmentView memory leaks
  • Developers use the Support package. Using AndroidSupportFragmentDestroyWatcher to detect fragments, FragmentView memory leaks
  • The developers are using the AndroidX package. Using AndroidXFragmentDestroyWatcher to detect fragments, FragmentView, ViewModel memory leaks
  • Reflect class.forname to determine whether the developer is using the Support package or the AndroidX package
  • Because the fragments needs to be mounted on the Activity, all the Application to register a ActivityLifecycleCallback, Whenever an Activity is created, listen for fragments that may exist in the Activity

One of the things that really puzzles me here is: If the system version is greater than or equal to 8.0, AndroidOFragmentDestroyWatcher will not and AndroidSupportFragmentDestroyWatcher or AndroidXFragmentDestroyWatcher repeated? What is this? Joy:

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"

  // Using a string builder to prevent Jetifier from changing this string to Android X Fragment
  @Suppress("VariableNaming"."PropertyName")
  private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =
    StringBuilder("android.").append("support.v4.app.Fragment")
        .toString()
  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)
      )
    }

    //AndroidX getWatcherIfAvailable( ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, objectWatcher, configProvider )? .let { fragmentDestroyWatchers.add(it) }//Support 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 inFragmentDestroyWatchers) {watcher(activity)}}})}Copy the code

Because AndroidXFragmentDestroyWatcher, AndroidSupportFragmentDestroyWatcher, AndroidOFragmentDestroyWatcher on logic is very similar, And just AndroidXFragmentDestroyWatcher provides ViewModel memory leak detection at the same time, so just see AndroidXFragmentDestroyWatcher here

AndroidXFragmentDestroyWatcher main logic is:

  • In the invoke method to FragmentManager Activity and a FragmentLifecycleCallback childFragmentManager registration, This callback is used to get notification of the onFragmentViewView and onfragmentView events, and when notified, checks are initiated through the ObjectWatcher
  • Start the ViewModel memory leak detection logic associated with the Fragment with the ViewModelClearedWatcher in the onFragmentCreated callback
  • Activate the memory leak detection of the ViewModel associated with the Activity with the ViewModelClearedWatcher in the Invoke method
internal class AndroidXFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {

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

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?). {
      ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
    }

    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)
      ViewModelClearedWatcher.install(activity, objectWatcher, configProvider)
    }
  }
}
Copy the code

The Fragment and FragmentView are not reusable and should be recapitled quickly by GC. They are only objects in nature, so use ObjectWatcher to check them

ViewModelClearedWatcher: Check ViewModel

Compared to fragments and FragmentViews, viewModels are special because it is possible for an Activity and multiple fragments to hold a ViewModel instance. Leakcanary cannot know whether the ViewModel is held by several holders at the same time, so it cannot initiate detection of the ViewModel through a single Activity and Fragment Destroyed callback. Fortunately, the ViewMode also provides an onCleared() callback event, which leakCanary uses to know when the ViewModel needs to be recycled. For those who are not clear about the implementation principle of ViewModel, you can see my article: Jetpack (6) from the source code -ViewModel source detailed explanation

The main logic of ViewModelClearedWatcher is:

  • ViewModelClearedWatcher inherits from ViewModel. When you get a ViewModelStoreOwner instance (Activity or Fragment), Create a ViewModelClearedWatcher object bound to the instance
  • The ViewModelClearedWatcher retrives the mMap variable in the ViewModelStore by reflection, which stores all Viewmodel instances
  • When the onCleared() method of the ViewModelClearedWatcher is called back, it indicates that all ViewModel instances bound to the Activity or Fragment are no longer needed. At this point, you can start monitoring all of your ViewModel instances
internal class ViewModelClearedWatcher(
        storeOwner: ViewModelStoreOwner,
        private val objectWatcher: ObjectWatcher,
        private val configProvider: () -> Config
) : ViewModel() {

    private val viewModelMap: Map<String, ViewModel>?

    init {
        // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
        // However that was added in 2.1.0 and we support AndroidX first stable release. Viewmodel -2.0.0
        // does not have ViewModelStore#keys. All versions currently have the mMap field.
        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}}override fun onCleared(a) {
        if(viewModelMap ! =null && configProvider().watchViewModels) {
            viewModelMap.values.forEach { viewModel ->
                objectWatcher.watch(
                        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback")}}}companion object {
        fun install(storeOwner: ViewModelStoreOwner, objectWatcher: ObjectWatcher, configProvider: () -> Config) {
            val provider = ViewModelProvider(storeOwner, object : Factory {
              @Suppress("UNCHECKED_CAST")
              override fun 
        create(modelClass: Class<T>): T =
                      ViewModelClearedWatcher(storeOwner, objectWatcher, configProvider) as T
            })
            provider.get(ViewModelClearedWatcher::class.java)
        }
    }
}
Copy the code

Flow after memory leak is detected

It is not possible to determine whether the ReferenceQueue has a value immediately after the Activity is called back to the onDestroy method, because the JVM’s GC timing is uncertain, and the Activity object may not be reclaimed that quickly. So it needs to be delayed for a period of time before detection. Even if the detection is delayed, there may be a case where the application does not have a memory leak but the system has not yet performed GC. Therefore, it is necessary to proactively trigger GC to determine whether the current application does have a memory leak after several rounds of detection

Here is a look at the specific detection process

The ObjectWatcher object contains an Executor parameter, checkRetainedExecutor. The timing of the test operation is determined by when will the task submitted to the checkRetainedExecutor be executed

class ObjectWatcher constructor(private val clock: Clock, private val checkRetainedExecutor: Executor,
        /** * Calls to [watch] will be ignored when [isEnabled] returns false */
        private val isEnabled: () -> Boolean = { true{}).../**
     * Watches the provided [watchedObject].
     *
     * @param description Describes why the object is watched.
     */
    @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
        / / the key
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }
    
    // Determine whether the object associated with the key has been leaked
    // If yes, the retainedUptimeMillis value will be updated to indicate that the leak occurred
    @Synchronized
    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects()
        val retainedRef = watchedObjects[key]
        if(retainedRef ! =null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            // Note that a memory leak may occurOnObjectRetainedListeners. ForEach {it. OnObjectRetained ()}}}...}Copy the code

The ObjectWatcher object is initialized in the InternalAppWatcher, and the checkRetainedExecutor is delayed by five seconds by the Handler after receiving the task

internal objectInternalAppWatcher {...private val mainHandler by lazy {
    Handler(Looper.getMainLooper())
  }

  private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }
  
  val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true...}})Copy the code

ObjectWatcher’s moveToRetained method will again notify out via onobjectrecracker that there may be a memory leak currently. InternalLeakCanary receives this notification and then passes it to the HeapDumpTrigger for testing

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
   
    private lateinit var heapDumpTrigger: HeapDumpTrigger
    
    override fun onObjectRetained(a) = scheduleRetainedObjectCheck()

	fun scheduleRetainedObjectCheck(a) {
    	if (this. : : heapDumpTrigger isInitialized) {heapDumpTrigger scheduleRetainedObjectCheck ()}}...}Copy the code

When LeakCanary determines that there is a real memory leak, DumpHeap will be performed to find the reference chain of the leaked object. This operation is time consuming and may directly cause the application page to be unresponsive. Therefore, LeakCanary performs a number of pre-checks and pre-conditions before performing DumpHeap, in order to minimize the number of DumpheAps and minimize interference with developers while doing DumpHeap

HeapDumpTrigger scheduleRetainedObjectCheck () method is the main logic:

  1. Gets the number of currently unreclaimed objects retainedKeysCount. If the number is greater than 0, the system triggers THE GC first and tries to reclaim the object to avoid misjudgment. Then the second step is performed. If the number is zero, the process is over
  2. The retainedKeysCount is updated again after the GC, and if the objects are reclaimed (that is, the retainedKeysCount value is 0), then the process is finished, otherwise step 3 is executed
  3. If the retainedKeysCount is less than the threshold of 5, and the current “application in the foreground” or “application in the background but back to the background for no more than five seconds,” then start a scheduled task to perform step 1 after 20 seconds, otherwise perform step 4
  4. If the last DumpHeap is less than a minute from now, start a scheduled task and re-execute step 1 after a full minute, otherwise execute step 5
  5. At this point, all of the conditions are met and it is possible to determine that a memory leak has occurred and proceed to DumpHeap
internal class HeapDumpTrigger(
        private val application: Application,
        private val backgroundHandler: Handler,
        private val objectWatcher: ObjectWatcher,
        private val gcTrigger: GcTrigger,
        private val heapDumper: HeapDumper,
        private valConfigProvider: () -> Config) {Β·Β·fun scheduleRetainedObjectCheck(
            delayMillis: Long = 0L
    ) {
        val checkCurrentlyScheduledAt = checkScheduledAt
        if (checkCurrentlyScheduledAt > 0) {
            // If the test is already in progress, return it directly
            return
        }
        checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
        backgroundHandler.postDelayed({
          checkScheduledAt = 0
          checkRetainedObjects()
        }, delayMillis)
    }

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

        val config = configProvider()

        if (iCanHasHeap is Nope) {
            if (iCanHasHeap is NotifyingNope) {
                // Before notifying that we can't dump heap, let's check if we still have retained object.
                var retainedReferenceCount = objectWatcher.retainedObjectCount

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

                val nopeReason = iCanHasHeap.reason()
                valwouldDump = ! checkRetainedCount( retainedReferenceCount, config.retainedVisibleThreshold, nopeReason )if (wouldDump) {
                    val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
                    onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
                    showRetainedCountNotification(
                            objectCount = retainedReferenceCount,
                            contentText = uppercaseReason
                    )
                }
            } else {
                SharkLog.d {
                    application.getString(
                            R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
                    )
                }
            }
            return
        }

        // Get the number of objects that have not been reclaimed
        var retainedReferenceCount = objectWatcher.retainedObjectCount

        if (retainedReferenceCount > 0) {
            // Trigger the GC and try to reclaim objects to avoid misjudgment
            gcTrigger.runGc()
            retainedReferenceCount = objectWatcher.retainedObjectCount
        }

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

        val now = SystemClock.uptimeMillis()
        val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis

        // If the last DumpHeap is less than a minute from now, start a scheduled task and check again after a full minute
        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(  delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis )return
        }

        dismissRetainedCountNotification()
        // Execute DumpiHeap when all conditions are met and a memory leak has been determined
        dumpHeap(retainedReferenceCount, retry = true)}/** * returns false * if the current condition of DumpHeap is true@paramRetainedKeysCount Number of objects that have not been reclaimed *@paramRetainedVisibleThreshold Threshold at which DumpHeap is triggered * Only if retainedKeysCount is greater than or equal to retainedVisibleThreshold, which defaults to 5 *@param nopeReason
     */
    private fun checkRetainedCount(
            retainedKeysCount: Int,
            retainedVisibleThreshold: Int,
            nopeReason: String? = null
    ): Boolean {
        // Used to mark whether the number of unreclaimed objects has changed compared to the last detection
        valcountChanged = lastDisplayedRetainedObjectCount ! = retainedKeysCount lastDisplayedRetainedObjectCount = retainedKeysCountif (retainedKeysCount == 0) {
            if (countChanged) {
                // If retainedKeysCount is 0 and the value has decreased since the last check, then something was reclaimed
                SharkLog.d { "All retained objects have been garbage collected" }
                onRetainInstanceListener.onEvent(NoMoreObjects)
                showNoMoreRetainedObjectNotification()
            }
            return true
        }

        // Whether the application is still in the foreground
        val applicationVisible = applicationVisible
        valApplicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod...if (retainedKeysCount < retainedVisibleThreshold) { // The threshold has not been reached
            if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
                if (countChanged) {
                    onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
                }
                // Display the number of unreclaimed objects in the notification bar
                showRetainedCountNotification(
                        objectCount = retainedKeysCount,
                        contentText = application.getString(
                                R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
                        )
                )
                //retainedKeysCount has not reached the threshold and "application is in the foreground" or "application is in the background but has not been in the background for more than 5 seconds"
                // At this point, start a scheduled task and check again after 20 seconds
                scheduleRetainedObjectCheck(
                        delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
                )
                return true}}return false}...}Copy the code

The latter part of the process involves the specific DumpHeap operation, and I won’t expand it here, because I don’t know much about it, and I will write a separate article about it later

Eight, tips

1. Detect any object

In addition to the four types supported by LeakCanary by default, we can proactively detect any object. For example, you can detect Service:

class MyService : Service {

  // ...

  override fun onDestroy(a) {
    super.onDestroy()
    AppWatcher.objectWatcher.watch(
      watchedObject = this,
      description = "MyService received Service#onDestroy() callback")}}Copy the code

2. Change the configuration item

The default Settings provided by LeakCanary are already good enough for us to use directly in most cases, but if we want to change the default Settings of LeakCanary (for example, we don’t want to detect FragmentView), we can do so in The Application:

class DebugExampleApplication : Application() {

  override fun onCreate(a) {
    super.onCreate()
    AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)}}Copy the code

Since LeakCanary is referenced by debugImplementation, it cannot be referenced in the Releas environment, so in order to avoid the need to proactively delete this configuration item when the release package is generated, You need to place the DebugExampleApplication in the SRC /debug/ Java folder

Nine, the end

Activity, Fragment, FragmentView, and ViewModel are all dependent on ObjectWatcher, because these four types are essentially different objects. ObjectWatcher relies on the ReferenceQueue ReferenceQueue, so LeakCanary’s basic implementation is based on native features from Java

LeakCanary overall source code is about the same, and then to write an extended read on memory leaks πŸ˜‚πŸ˜‚