Analysis of classic open source libraries is a necessary process for growth and advancement. Through learning excellent open source libraries of top engineers, we can improve our ability to read and analyze source code, learn their design ideas and improve our code design ability.

Note: This analysis is based onLeakcanary v2.4 version

LeakCananry is an open source memory leak detection tool for Android from Square, and LeakCanary’s knowledge of the internal architecture of the Android framework gives it a unique ability to narrow down the cause of each leak, Helps developers significantly reduce OutOfMemoryError crashes.

A small leak will sink A great ship. (A thousand miles’ dam is broken.)

What is a memory leak?

In Java, when an object is no longer needed, it is collected by the garbage collector. However, if another object in use holds a reference to that object, it cannot be collected by the garbage collector, and eventually the object remains in memory. This phenomenon is called a memory leak. Continuous memory leaks will eventually cause OOM problems.

A typical memory leak in Android is when an Activity that has performed onDestroy is still held by some static variable, resulting in a memory leak.

1. Basic use

The version after 2.0 simplifies this by adding the dependency LeakCanary to dependencies. At the same time, the official website recommends using debugImplementation for dependency, which is only effective in the Debug package, so there is no need to worry about the performance impact caused by user misoperation in the release package.

Add the dependent

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

Integration with LeakCanary is complete without writing any code. LeakCanary automatically completes memory leak detection for the following objects:

  • Destruction of theActivityThe instance
  • Destruction of theFragmentThe instance
  • Destruction of theFragmentViewThe instance
  • The emptyViewModelThe instance

2. Process Overview

The LeakCananry workflow can be found in the official guide How LeakCanary Works.

2.1 Principle Overview

Refer to the How LeakCanary Works documentation, LeakCanary through ActivityLifecycleCallbacks and FragmentLifecycleCallbacks finish for life cycle to monitor the Activity and the Fragment, When listening on activities or fragments that have been destroyed (onDestroy()), they are placed in a WeakReference, which is then associated to a ReferenceQueue (ReferenceQueue). If a reference to an Activity or Fragment is thrown into the reference queue, GC is performed. If the object is still in the reference queue after 5 seconds, the object is proved to have a memory leak, and the dump instruction is executed to analyze the memory.

2.2 Basic Process

The registration process for LeakCanary is as follows:

It can be summarized as the following steps:

  1. registeredAppWatcherInstallerContentProvider
  2. callAppWatchermanualInstallmethods
  3. InternalAppWatcherInitialize, callinitMethod, and then callInternalAppWatcher.install()Complete theActivityFragmentLife cycle monitor. Also register to handle callbacksInternalLeakCanary.

3 LeakCanary Object detection

3.1. LeakCanary The installation is initialized

In versions after 2.0, LeakCanary can be initialized without the user having to write a line of code, taking advantage of the ContentProvider’s ability to initialize before Application. Register the ContentProvider and initialize it in the onCreate method.

<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="${applicationId}.leakcanary-installer"
    android:enabled="@bool/leak_canary_watcher_auto_install"
    android:exported="false"/>
Copy the code

Here is registered leakcanary. Internal. $MainProcess AppWatcherInstaller.

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

Within the onCreate method, call AppWatcher.manualInstall(application) to initialize it so that it can be integrated without writing code. Student: So why don’t you turn on auto initialization? This is controlled using the Android :enable property of the ContentProvider component. As you can see from the Provider declaration above, Android: The value of the enable attribute is controlled by leak_canary_watcher_auto_install, which defaults to true.

<resources>
  <bool name="leak_canary_watcher_auto_install">true</bool>
</resources>
Copy the code

So if we want to customize it, we need to set Android :enable to false and initialize it manually by calling AppWatcher. MunualInstall (application).

// Override to false in the res directory<resources>
  <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>// Initialize AppWatcher. MunualInstall (Application)Copy the code
2.1 AppWatcher. ManualInstall (Application)

The AppWatcher class is the API entry for Android development using ObjectWatcher. Its interior contains mainly the configuration items used by LeakCanary.

object AppWatcher {

  /**
   * AppWatcher configuration data class. Properties can be updated via [copy].
   *
   * @see [config]
   */
  data class Config(
  	// Whether to automatically detect the Activity
    val watchActivities: Boolean = true.// Whether to automatically detect the Fragment
    val watchFragments: Boolean = true.// Whether to automatically detect the Fragment View
    val watchFragmentViews: Boolean = true./ / whether to automatically detect androidx. Lifecycle. The ViewModel
    val watchViewModels: Boolean = true.// The duration of detecting the remaining objects. The default duration is 5s
    val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)) {}/** * The [ObjectWatcher] used by AppWatcher to detect retained objects. */
  val objectWatcher
    get() = InternalAppWatcher.objectWatcher

  /** * [AppWatcher] is automatically installed in the main process on startup. You can * disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource: * * ``` * 
       * 
      
        * 
       
        false
        * 
       * ``` * * If you disabled automatic install then you can call this method to install [AppWatcher]. */
  fun manualInstall(application: Application) {
    InternalAppWatcher.install(application)
  }
}
Copy the code

AppWatcher, as the external API to LeakCanary, contains a Config Config class that configates what types of objects LeakCanary detects.

  • watchActivities: Indicates whether to automatically detectActivity
  • watchFragments: Indicates whether to automatically detectFragment
  • watchFragmentViews: Indicates whether to automatically detectFragment View
  • watchViewModels: Indicates whether to automatically detectandroidx.lifecycle.ViewModel

If we initialize manually, we can set it as follows.

val watcherConfig = AppWatcher.config.copy(watchViewModels = false)
AppWatcher.config = watcherConfig
AppWatcher.manualInstall(application)
Copy the code

In the interior of the manualInstall method call InternalAppWatcher. Install () to initialize installation.

2.2 InternalAppWatcher. Install ()

This is the final execution method of the initial installation.

internal object InternalAppWatcher {

  val isInstalled
    get() = ::application.isInitialized

  private val onAppWatcherInstalled: (Application) -> Unit

  lateinit var application: Application

  private val clock = object : Clock {
    override fun uptimeMillis(a): Long {
      return SystemClock.uptimeMillis()
    }
  }

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

  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
  }

  private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
  }
  val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true})fun install(application: Application) {
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    SharkLog.logger = DefaultCanaryLog()
    InternalAppWatcher.application = application

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

  inline fun <reified T : Any> noOpDelegate(a): T {
    val javaClass = T::class.java
    val noOpHandler = InvocationHandler { _, _, _ ->
      // no op
    }
    return Proxy.newProxyInstance(
        javaClass.classLoader, arrayOf(javaClass), noOpHandler
    ) as T
  }

  private fun checkMainThread(a) {
    if(Looper.getMainLooper().thread ! == Thread.currentThread()) {throw UnsupportedOperationException(
          "Should be called from the main thread, not ${Thread.currentThread()}")}}object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application){}override fun onObjectRetained(a){}}}Copy the code

InternalAppWatcher is an Object class that completes the LeakCanary initialization install core code call.

The init method

The initialization method is first performed, creating the InternalLeakCanary object through reflection in the init method and assigning it to the onAppWatcherInstalled object.

The install method

  1. First check if the current thread is in the main thread.LeakCanaryRequires initialization on the main thread
  2. forSharkLogConfigThe initialization
  3. registeredActivityDestroyWatcherFragmentDestroyWatcherListening to the
  4. callInternalLeakCanaryOf the classinvokemethods

3.2 ActivityDestroyWatcher

ActivityDestroyWatcher is used to listen for the Activity’s life cycle.

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

By registering registerActivityLifecycleCallbacks monitoring, implementation of the Activity lifecycle when the Activity triggers onActivityDestroyed

“, the Activity is added to the ObjectWatcher for detection. ObjectWatcher is covered in a later section.

3.3 FragmentDestroyWatcher

FragmentDestroyWatcher is used to listen for the life cycle of fragments. There are three types of Fragments in Android:

  • androidx.fragment.app.FragmentIn:AndroidxSupport is provided in the library
  • android.support.v4.app.FragmentIn:supportSupport is provided in the library
  • android.app.Fragment:AndroidSupport is provided in the library

Androidx library and support library can not coexist, so we need to do compatibility in processing.

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
  ) {
    // Create a Fragment detection list
    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)
        }
      }
    })
  }
}
Copy the code

The flow of the core method Install can be summarized in three flows:

  1. registeredandroid.app.FragmentListening inAndroidOFragmentDestroyWatcher
  2. registeredandroidx.fragment.app.FragmentListening inAndroidXFragmentDestroyWatcher
  3. registeredandroid.support.v4.app.FragmentListening inAndroidSupportFragmentDestroyWatcher

When supporting the support library and androidX library, the getWatcherIfAvailable method is used to judge whether the current App introduces the corresponding library through reflection. If so, the corresponding listener is added.

1. AndroidOFragmentDestroyWatcher

Through FragmentManager. FragmentLifecycleCallbacks callback for listening, fragments of life cycle. The FragmentManager here is an android app. FragmentManager. Add fragments to ObjectWatcher for detection while onFragmentDestroyed and onFragmentViewDestroyed.

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")}}}Copy the code
2. AndroidXFragmentDestroyWatcher

Through FragmentManager. FragmentLifecycleCallbacks callback fragments of life cycle monitor, Here is FragmentManager androidx. Fragments. App. FragmentManager. Add fragments to ObjectWatcher for detection while onFragmentDestroyed and onFragmentViewDestroyed. A check for the ViewModel is added in onFragmentCreated.

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")}}}Copy the code
3. AndroidSupportFragmentDestroyWatcher

Through FragmentManager. FragmentLifecycleCallbacks callback fragments of life cycle monitor, The FragmentManager here is android. Support. The v4. App. FragmentManager. Add fragments to ObjectWatcher for detection while onFragmentDestroyed and onFragmentViewDestroyed.

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")}}}Copy the code

This completes listening on the Fragment.

3.4 ObjectWatcher

In the previous section, you can see that calling the watch() method of the ObjectWatcher adds activities and fragments to the monitor collection. ObjectWatcher is the Memory Leak monitor object management class for LeakCanary. It stores objects as weak references, and then checks to see if the object reference is cleared when checkRetainedExecutor executes. If not, a memory leak may occur.

Look at the basic structure of the source code.

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val isEnabled: () -> Boolean = { true{})private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  // Monitor object collection
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

  private val queue = ReferenceQueue<Any>()

  // If there are any uncleared objects, occur inside
  val hasRetainedObjects: Boolean

  // Number of memory leak objects
  val retainedObjectCount: Int

  /** * Returns true if there are watched objects that aren't weakly reachable, even * if they haven't been watched for long enough to be considered retained. */
  val hasWatchedObjects: Boolean

  // Memory leak object collection
  val retainedObjects: List<Any>
}
Copy the code

ObjectWatch is also simple in composition, focusing on the use of watchedObjects and checkRetainedExecutor for storing monitor objects and performing monitoring, respectively. Call the Watch method to add the monitor object.

@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)
	}
}
Copy the code

The process of Watch method is briefly summarized as follows:

  1. Whether openLeakCanary, if it is not openedreturn
  2. removewatchedObjectsIn theGCReclaimed reference
  3. generatekeyAnd buildKeyedWeakReferenceWeak reference, while adding towatchedObjectsIn the collection
  4. performmoveToRetainedMethod to detect the current object

I need to add a point about reference queues here.

ReferenceQueue (ReferenceQueue)

Reference classifies memory into four states: Active, Pending, Enqueued, and Inactive.

  • Active: Generally speakingReferenceThe states that are created and assigned areActive
  • Pending: Will be queued soon (ReferenceQueue), which is the object to be reclaimed soon
  • EnqueuedReferenceThe object has been queued, i.eReferenceThe object has been reclaimed
  • InactiveReferenceThe final state after being removed from the queue cannot be changed to any other state.

In Java, a SoftReference and a WeakReference can be associated with a reference queue when they are created. When the GC (garbage collection thread) is about to reclaim an object and finds that only soft (or weak, or virtual) references point to it, it adds the soft (or weak, or virtual) reference to its associated ReferenceQueue (ReferenceQueue) before recycling the object. If a soft-reference (or weak-reference, or vref) object is itself in the reference queue, the object to which the reference object points has been reclaimed.

The implementation of LeakCanary puts all Activity or Fragment instances into weak references and associates a reference queue. If the instance for the recovery, so a weak reference will be placed into the ReferenceQueue, and call the removeWeaklyReachableObjects methods will have recycled object is removed from the watchedObjects collection, then the rest is not recycled, A memory leak occurred. If the monitored instance does not appear in the ReferenceQueue after a certain period of time, then a memory leak has occurred and the instance has not been reclaimed.

3.5 InternalLeakCanary

In the previous introduction InternalAppWatcher. Install (), the last one performs operations is InternalLeakCanary class, the use of the following around the class usage, to introduce some.

Again, let’s look at the basic components.

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {

  private const val DYNAMIC_SHORTCUT_ID = "com.squareup.leakcanary.dynamic_shortcut"

  private lateinit var heapDumpTrigger: HeapDumpTrigger
  
  private var _application: Application? = null. }Copy the code

InternalLeakCanary implements the OnObjectRetainedListener interface, which holds a HeapDumpTrigger object for in-memory calculations.

The InternalAppWatcher class creates the InternalLeakCanary object through reflection in the init method and calls the invoke method within the Install method. The invoke logic is described in more detail below.

override fun invoke(application: Application) {
	_application = application
	// Check whether it is a Debug build
	checkRunningInDebuggableBuild()
	// Add AppWatcher listener
	AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
	// Create the AndroidHeapDumper analysis tool class
	val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
	// GC trigger
	val gcTrigger = GcTrigger.Default

	val configProvider = { LeakCanary.config }

	val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
	handlerThread.start()
	val backgroundHandler = Handler(handlerThread.looper)
	// Initialize HeapDumpTrigger
	heapDumpTrigger = HeapDumpTrigger(
	    application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
	    configProvider
	)
	// Register the listener
	application.registerVisibilityListener { applicationVisible ->
	  this.applicationVisible = applicationVisible
	  heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
	}
	registerResumedActivityListener(application)
	// Add desktop shortcuts
	addDynamicShortcut(application)
	// Disable Dump analysis
	disableDumpHeapInTests()
}
Copy the code

In the Invoke method, initialization of the related class is performed. For example, add OnObjectRetainedListener, AndroidHeapDumper analysis, GcTrigger trigger, add desktop shortcuts and so on. At this point, the entire LeakCanary main process is complete. This is followed by the process of memory detection via HeapDumpTrigger.

4. LeakCanary Memory analysis and detection

As can be seen from the above process analysis, InternalLeakCananry class is finally used as the tipping point of memory analysis. The invoke method is used to complete the initialization of the relevant member object, and then the OnObjectRetainedListener is implemented to listen to the callback. When an object is detected in the ObjectWatcher that has not been cleaned up, the OnObjectRetainedListener callback is triggered and memory leak analysis and detection is completed for that object.

Key call flow:

ObjectWatcher.onObjectRetained()
	-> InternalLeakCanary.onObjectRetained()
		-> HeapDumpTrigger.scheduleRetainedObjectCheck()
			-> HeapDumpTrigger.checkRetainedObjects()
			-> GcTrigger.runGc()
			-> HeapDumpTrigger.dumpHeap()
				-> HeapAnalyzerService.runAnalysis()
Copy the code

1) InternalLeakCanary.onObjectRetained()

// InternalLeakCanary
override fun onObjectRetained(a) {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.onObjectRetained()
  }
}
// HeapDumpTrigger
fun onObjectRetained(a) {
  scheduleRetainedObjectCheck(
    reason = "found new object retained",
    rescheduling = false)}Copy the code

If HeapDumpTrigger has completed initialization, the onobjectre谴 责 () callback is called. In internal call scheduleRetainedObjectCheck HeapDumpTrigger not recycled Object check.

2) HeapDumpTrigger.scheduleRetainedObjectCheck()

private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
) {
	// Check the timestamp, the default is 0
	val checkCurrentlyScheduledAt = checkScheduledAt
	// If the value is greater than 0, the check is in progress
	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"}}// Set the check timestamp
	checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
	backgroundHandler.postDelayed({
	  checkScheduledAt = 0
	  // Check for unreclaimed objects
	  checkRetainedObjects(reason)
	}, delayMillis)
}
Copy the code

In scheduleRetainedObjectCheck () method, main is to check whether an analysis is performed, if it is already open, then return. Finally, the asynchronous check task checkRetainedObjects() is triggered in backgroundHandler.

3) HeapDumpTrigger.checkRetainedObjects()

private fun checkRetainedObjects(reason: String) {
  val config = configProvider()
  // A tick will be rescheduled when this is turned back on.
  // If dumpHeap is ignored
  if(! config.dumpHeap) { SharkLog.d {"Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
    return
  }
  // The number of unreclaimed objects
  var retainedReferenceCount = objectWatcher.retainedObjectCount
	// If the number of uncollected objects is greater than 0, GC is triggered again
  if (retainedReferenceCount > 0) {// If there are uncollected objects, GC is triggered
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }
  // Check whether the number of uncollected objects exceeds the dump heap threshold after GC is triggered. The default is 5
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
  // Whether to dump heap only in Debug environment, and in Debug environment
  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
  // Check the last two dump intervals. Default is 60s
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    onRetainInstanceListener.onEvent(DumpHappenedRecently)
    / / Notification promptshowRetainedCountNotification( 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)}Copy the code

CheckRetainedObjects () is used to validate triggering dump heap operations. It mainly includes the following conditions:

  1. Whether opendumpHeapoperation
  2. Whether the number of unreclaimed objects reachesdumpHeapThe default value is 5. If not, the execution continuesscheduleRetainedObjectCheckCheck the operation
  3. Check whether theDebugOnly whendumpHeapIf the current is inDebugContinue to executescheduleRetainedObjectCheckCheck the operation
  4. Check execution twicedumpHeapWhether the interval of operations has been exceeded60sIf not, return
  5. The last executiondumpHeapoperation

4) GcTrigger. RunGc ()

In object detection, when the number of unreclaimed objects is greater than 0, the GC operation is actively triggered again.

interface GcTrigger {

  /** * Attempts to run garbage collection. */
  fun runGc(a)

  /** * Default implementation of [GcTrigger]. */
  object Default : GcTrigger {
    override fun runGc(a) {
      // 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()
    }

    private fun enqueueReferences(a) {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100)}catch (e: InterruptedException) {
        throw AssertionError()
      }
    }
  }
}
Copy the code

In Default, the Default implementation of the GcTrigger interface, the runGc() operation performs gc operations by calling Runtime.geTruntime ().gc().

5) HeapDumpTrigger. DumpHeap ()

Call the dumpHeap() method to calculate the stack information.

private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    // Calculate the stack information
    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)
}
Copy the code

The key in the dumpHeap() method is heapDumper.dumpheap (). HeapDumper is an instance of heapDumper whose default implementation is the AndroidHeapDumper class.

// AndroidHeapDumper
override fun dumpHeap(a): File? {
  // Create a heap file
  valheapDumpFile = leakDirectoryProvider.newHeapDumpFile() ? :return null

  valwaitingForToast = FutureResult<Toast? >() showToast(waitingForToast)if(! waitingForToast.wait(5, SECONDS)) {
    SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
    return null
  }

  val notificationManager =
  context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
  if (Notifications.canShowNotification) {
    val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
    val builder = Notification.Builder(context)
    .setContentTitle(dumpingHeap)
    val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
    notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
  }

  val toast = waitingForToast.get(a)return try {
    // Call the debug. dumpHprofData method to generate heap information and return
    Debug.dumpHprofData(heapDumpFile.absolutePath)
    if (heapDumpFile.length() == 0L) {
      SharkLog.d { "Dumped heap file is 0 byte length" }
      null
    } else {
      heapDumpFile
    }
  } catch (e: Exception) {
    SharkLog.d(e) { "Could not dump heap" }
    // Abort heap dump
    null
  } finally {
    cancelToast(toast)
    notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
  }
}
Copy the code

The key way to get heap information is to use debug.dumphprofData ().

6) HeapAnalyzerService. RunAnalysis ()

After the above processing, finally can obtain the stack information, the next process is to analyze the stack information.

// HeapAnalyzerService
companion object {
    private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
    private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"

    fun runAnalysis(
      context: Context,
      heapDumpFile: File
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      startForegroundService(context, intent)
    }

    private fun startForegroundService(
      context: Context,
      intent: Intent
    ) {
      if (SDK_INT >= 26) {
        context.startForegroundService(intent)
      } else {
        // Pre-O behavior.
        context.startService(intent)
      }
    }
}
Copy the code

The implementation of the runAnalytsis() method starts a HeapAnalyzerService to analyze stack information. HeapAnalyzerService is a service that inherits ForegroundService. The service is ultimately implemented based on IntentService, so the key information is implemented in onHandleIntent.

 // ForegroundService
 override fun onHandleIntent(intent: Intent?). {
    onHandleIntentInForeground(intent)
  }

  protected abstract fun onHandleIntentInForeground(intent: Intent?).

// HeapAnalyzerService
override fun onHandleIntentInForeground(intent: Intent?). {
  if (intent == null| |! intent.hasExtra(HEAPDUMP_FILE_EXTRA)) { SharkLog.d {"HeapAnalyzerService received a null or empty intent, ignoring." }
    return
  }

  // Since we're running in the main process we should be careful not to impact it.
  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
  val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File

  val config = LeakCanary.config
  val heapAnalysis = if (heapDumpFile.exists()) {
    analyzeHeap(heapDumpFile, config)
  } else {
    missingFileFailure(heapDumpFile)
  }
  onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
  config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}

private fun analyzeHeap(
    heapDumpFile: File,
    config: Config
  ): HeapAnalysis {
    val heapAnalyzer = HeapAnalyzer(this)

    val proguardMappingReader = try {
      ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
    } catch (e: IOException) {
      null
    }
    return heapAnalyzer.analyze(
        heapDumpFile = heapDumpFile,
        leakingObjectFinder = config.leakingObjectFinder,
        referenceMatchers = config.referenceMatchers,
        computeRetainedHeapSize = config.computeRetainedHeapSize,
        objectInspectors = config.objectInspectors,
        metadataExtractor = config.metadataExtractor,
        proguardMapping = proguardMappingReader?.readProguardMapping()
    )
}
Copy the code

The final analysis is done using the Analyze () method of the HeapAnalyzer class. Because the process behind is more complex, I did not study too understand, will not expand. 😂

At this point, we have a rough idea of the main flow for LeakCanary.

5. To summarize

LeakCanary is an Android memory leak detection tool that will help you learn more about Java memory and object management. For example, the use of Reference is a classic example, and the development of Android SYSTEM API is fully exploited. It has high reference value. After watching it, I have a feeling of, “I understand the principle, there is no embarrassment I can’t design.”