Leakcanary2.0

Leakcanary

use

debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.0 - alpha - 2'

Copy the code

The principle of

Reference, ReferenceQueue

ReferenceQueue ReferenceQueue to which the garbage collector adds registered reference objects after detecting appropriate reachability changes

WeakReference weakReference = new WeakReference(list, new ReferenceQueue<WeakReference>());

When WeakReference is created, if a ReferenceQueue object is specified, after the garbage collector detects the change of the reachability of the referenced object, the garbage collector will add the registered reference object to the ReferenceQueue queue and wait for the ReferenceQueue processing. However, if the reference object is not added to the ReferenceQueue after GC, there may be a memory leak.


The implementation does not require initial startup

Are your Android libraries still initialized in Application?

Gradle packaging merges the different manifest files to initiate the provider when the application is started.

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

AppWatcherInstaller.kt

internal sealed class AppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { val application = context!! .applicationContext as Application InternalAppWatcher.install(application)return true}}Copy the code

Analysis of the code

InternalAppWatcher.kt

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

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

1. Install the Activity listener

ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
   fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
Copy the code

Construct the activityDestroyWatcher object and register it with the LifecyClecallbacks of the application

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

ConfigProvider () corresponds to the AppWather objectWatcher parameter passed in install, similar to the principle of leakCanary version 1.5. Lifecycle will be monitored when onActivityDestroyed is called. Watch ()

1.1 ObjectWatcher# watch

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
 
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
Copy the code
 @Synchronized fun watch(watchedObject: Any) {
    watch(watchedObject, "")}Copy the code

Familiar smell

  • RemoveWeaklyReachableObjects () to remove the weak up to object, in GC happened before
  • Val Reference =KeyedWeakReference() ObjectWatcher by which to judge whether it is reachable, with key to track whether the ReferenceQueue is associated
  • MoveToRetained (key) removeWeaklyReachableObjects first, in the implementation of all onObjectRetainedListeners onObjectRetained ()

1.1.1 removeWeaklyReachableObjects ()

First clean up the watchedObjects references that are already stored in the ReferenceQueue and are no longer monitored.

Weak references are enqueued as soon as they become weak and reachable. This will happen before Finalization or GC. That is, references to objects that are collected by the GC are stored in the queue.

private fun removeWeaklyReachableObjects() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if(ref ! = null) { watchedObjects.remove(ref.key) } }while(ref ! = null) }Copy the code

1.1.2 val reference = KeyedWeakReference ()

KeyedWeakReference weak references

A weak reference used by [ObjectWatcher] to determine which objects become weakly reachable
  and which don't. [ObjectWatcher] uses [key] to keep track of [KeyedWeakReference] instances that haven't made it into the associated [ReferenceQueue] yet.
 
Copy the code

Generate key randomly, pass watchUptimeMillis, key and Queue into KeyedWeakReference, and construct reference object

It’s kind of like

WeakReference Reference = new WeakReference(Activity instance, new ReferenceQueue<WeakReference>());Copy the code
 val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

    watchedObjects[key] = reference
Copy the code

Add Reference to the map that watchedObjects observes.

1.1.3 moveToRetained (key)

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

RemoveWeaklyReachableObjects again () again, if the corresponding key monitoring objects and not remove (join the queue queue), then is likely to be leak, will continue heapDumpTrigger operation.

InternalLeakCanary#onObjectRetained()
 override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }
Copy the code
HeapDumpTrigger#onObjectRetained()
  fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false)}Copy the code
  private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
   
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }
Copy the code

reason = “found new object retained”,

checkRetainedObjects(reason)

  private fun checkRetainedObjects(reason: String) {
   

    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if(retainedReferenceCount > 0) {/ / judgment. The number of remaining references gcTrigger runGc () retainedReferenceCount = objectWatcher. RetainedObjectCount }if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    if(! config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) { onRetainInstanceListener.onEvent(DebuggerIsAttached) showRetainedCountNotification( objectCount = retainedReferenceCount, ContentText = application.getString(R.string.canary_notification_retained_DEBUgger_Attached))// Send notification scheduleRetainedObjectCheck( reason ="debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

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

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

Same as 1.5 when general practice

 override fun runGc() {

      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perform a gc.
      Runtime.getRuntime()
          .gc()
      enqueueReferences()
      System.runFinalization()
    }
Copy the code
CheckRetainedCount ()

RetainedVisibleThreshold is 5 by default

 /**
     * When the app is visible, LeakCanary will wait for at least
     * [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
     * freezes the UI and can be frustrating for developers who are trying to work. This is
     * especially frustrating as the Android Framework has a number of leaks that cannot easily
     * be fixed.
     *
     * When the app becomes invisible, LeakCanary dumps the heap after
     * [AppWatcher.Config.watchDurationMillis] ms.
     *
     * The app is considered visible if it has at least one activity in started state.
     *
     * A higher threshold means LeakCanary will dump the heap less often, therefore it won't be * bothering developers as much but it could miss some leaks. * * Defaults to 5. */Copy the code
private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int ): Boolean { val countChanged = lastDisplayedRetainedObjectCount ! = retainedKeysCount lastDisplayedRetainedObjectCount = retainedKeysCountif(retainedKeysCount == 0) {// If there are no references leftreturn true
      SharkLog.d { "Check for retained object found no objects remaining" }
      if(countChanged) {// Check whether the number of the last display is the same, Inconsistent show notification onRetainInstanceListener. OnEvent (NoMoreObjects) showNoMoreRetainedObjectNotification ()}return true} // if less than 5if (retainedKeysCount < retainedVisibleThreshold) {
      if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
        if(countChanged) {/ / Event onRetainInstanceListener. OnEvent (BelowThreshold (retainedKeysCount)} / / send the notification showRetainedCountNotification( objectCount = retainedKeysCount, contentText = application.getString( R.string.leak_canary_notification_retained_visible, RetainedVisibleThreshold)) / / continue to arrange the remaining object check reason different delay = 2 s scheduleRetainedObjectCheck (= "reason"found only $retainedKeysCount retained objects (< $retainedVisibleThreshold while app visible)",
            rescheduling = true,
            delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
        )
        return true}} // Greater than 5falseTo continuereturn false
  }

Copy the code
Check whether isDebuggerAttached is used

(I don’t understand this.)

 scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
Copy the code
If the time between the last dump and the present dump is less than 60 seconds, continue, and return
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
     
      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
    }
Copy the code
dumpHeap(retainedReferenceCount, retry = true)

Determine if there is any hprof, if there is no scheduleRetainedObjectCheck again, if you have dumpheap

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

2. Install the Fragment listener

FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

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

2.1 AndroidOFragmentDestroyWatcher

  • onFragmentViewDestroyed
  • onFragmentDestroyed

During the above two callbacks, the Fragment and View objects are monitored through ObjectWatcher, with the same logic after ObjectWatcher.


internal class AndroidOFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

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

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

3. Perform initialization

onAppWatcherInstalled(application)

OnAppWatcherInstalled corresponds to internalLeakCanary in init, and you can see the reflection call.

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

3.1 InternalLeakCanary# invoke

override fun invoke(application: Application) {enclosing Application = Application checkRunningInDebuggableBuild () / / listener registration AppWatcher. ObjectWatcher. AddOnObjectRetainedListener (this) / / create AndroidHeapDumper val heapDumper = AndroidHeapDumper(application, LeakDirectoryProvider) // Initialize gctrigger val gctrigger = gctrigger.Default val configProvider = {LeakCanary HandlerThread = handlerThread (LEAK_CANARY_THREAD_NAME) HandlerThread.start () // Initialize handler to see the bound Looper Val BackgroundHandler = Handler(HandlerThread. looper) // Initialize HeapDumpTrigger HeapDumpTrigger = HeapDumpTrigger(Application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper, configProvider ) application.registerVisibilityListener { applicationVisible -> this.applicationVisible = applicationVisible heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible) } registerResumedActivityListener(application) addDynamicShortcut(application)disableDumpHeapInTests()
  }
Copy the code

This is a bunch of initialization operations, but only one is important

  heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
Copy the code

3.2 HeapDumpTrigger# onApplicationVisibilityChanged

This explains how leakCanary automatically listens for memory leaks

Check whether ApplicationVisible is visible. If not visible scheduleRetainedObjectCheck, delay the default 5 s

  fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
      applicationInvisibleAt = -1L
    } else {
      applicationInvisibleAt = SystemClock.uptimeMillis()
      // Scheduling for after watchDuration so that any destroyed activity has time to become
      // watch and be part of this analysis.
      scheduleRetainedObjectCheck(
          reason = "app became invisible",
          rescheduling = false,
          delayMillis = AppWatcher.config.watchDurationMillis
      )
    }
  }
Copy the code

Android developers, it’s time to learn about LeakCanary2.0

LeakCanary 2.0 principle