“What about memory optimization? Do you know how to locate a memory problem?” The interviewer, sitting amiably at the side of the small conference room, kindly asked xiao Zhang, who was somewhat stiff.

“Is… Well, use LeakCanary to detect the leak, find the leak, change the wrong code, recycle the unrecycled references, and optimize the dependencies of long and short life cycle threads.”

“Do you know how LeakCanary analyzes memory leaks?”

“I’m sorry. I don’t usually see it.”

Xiao Zhang thought: why do you always ask that in an interview? I’m just an ordinary rookie.

preface

App performance optimization is always an essential part of app development, and memory optimization is one of the key points. Memory leak caused by memory overflow crash, memory jitter caused by the card is not smooth. Are actually affecting the user experience. We often use LeakCanary to locate memory leak problems. It’s time to find out how they did it.

The noun understanding

Hprof: The hprof file is a Java memory snapshot file (short for Heap Profile) with a.hprof format and is used in leakCanary for memory save analysis

WeakReference : Weak references. When an object is referred to only by weak references, but not by any other strong references, if GC runs, the object will be reclaimed, regardless of whether the current memory space is sufficient. Used in leakCanary to monitor whether the recycled unwanted object has been released.

Curtains: Another open source framework for Square, Curtains provides a centralized API for working with Android Windows. LeakCanary is used in leakCanary to monitor window rootView memory leak after detached.

directory

This paper mainly from the following points of analysis

  1. How to use it in a projectLeakCanarytool
  2. Official rationale
  3. How to listen for Activity, View,fragment and ViewModel by default
  4. How does watcher.watch (object) listen for memory leaks
  5. How to save a memory leak memory file
  6. How to analyze memory leak files
  7. Show the memory leak stack into the UI

One, how does it work?

A look at the official documentation shows how simple it is to use, with the basic usage simply requiring the addition of dependencies

/ / (1)
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'
Copy the code

DebugImplementation is only valid for compilation in debug mode and the final debug APK packaging

Note (1): The code for the annotation is initialized in one line. How? By looking at the source code, we can see that LeakCanary initializes through the ContentProvider, The real initialization code appWatcher.manualInstall (Application) is called in the onCreate method of the AppWatcherInstaller class. Register the provider in androidManifest.xml. The registered ContentProvider will automatically call back to the onCreate method when the application starts.

internal sealed class AppWatcherInstaller : ContentProvider() {
  /**[MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
  / / (1)
  internal class MainProcess : AppWatcherInstaller(a)internal class LeakCanaryProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
    valapplication = context!! .applicationContextas Application
    / / / (2)
    AppWatcher.manualInstall(application)
    return true
  }
  / /...
  }
Copy the code

Explain the number annotation in the source code

Two inner classes are defined in code (1) that inherit from the AppWatcherInstaller. When users rely on the Leakcanary – Android-process module in addition, automatically register the provider in process=”: Leakcanary “as well.

See The LeakCanary – Android-Process module androidManifest.xml for the code

Code (2), which is the real initialization code registry entry

Second, the official statement

The official instructions

This section is simplified from the official website’s working principle description

Once LeakCanary is installed, it automatically detects and reports memory leaks through four steps:

  1. Detect the object being held

LeakCanary hooks into the Android lifecycle to automatically detect when activity and clips are destroyed and should be garbage collected. These destroyed objects are passed to an ObjectWatcher, which holds a weak reference to them. You can actively observe an object that is no longer needed, such as a Dettached view or a presenter that has been destroyed

AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
Copy the code

If ObjectWatcher does not clear the held weak references after waiting 5 seconds and running garbage collection, the monitored object is considered reserved and may leak. LeakCanary records this to Logcat:

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
  (Activity received Activity#onDestroy() callback) 

... 5 seconds later ...

D LeakCanary: Scheduling check for retained objects because found new object
  retained
Copy the code
  1. Dump the heap information into a file

When the number of retained objects reaches the threshold, LeakCanary dumps Java memory snapshotThe dumpTo the Android file system.hprofFile (Heap memory snapshot). The dump heap freezes the application for a short time and shows the toast below:

  1. Analyzing heap memory

LeakCanary uses Shark to parse the.hprof file and locate the retained leak object in the memory snapshot file. For each retained object, LeakCanary finds the reference path for the object, which prevents the garbage collector from collecting it. Leak tracking. LeakCanary creates a signature (sha1Hash for the addition of the held reference properties) for each leak trace and groups together the leaks that have the same signature (that is, leaks caused by the same error). How to create a signature and pass a signature group will be analyzed later.

  1. Classified memory leak

LeakCanary divides the Leaks it finds in your app into two categories: Application Leaks and Library Leaks. A Library Leaks is caused by a known third-party Library over which you have no control. This leak is affecting your application, but unfortunately fixing it may not be within your control, so LeakCanary isolated it. The two categories are printed separately in the Logcat results:

====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
│
...
Copy the code

LeakCanary uses this in its leak list displayLibrary LeakLabel:LeakCanary ships with a database of known leaks, identified by pattern matching of reference names. Such as:

Leak pattern: instance field android.app.Activity$1#this$0 Description: Android Q added a new IRequestFinishCallback$Stub class [...] ┬─── │ GC Root: Global variable in Native code │ ├─ Android.app.activity $1 instance Leaking: UNKNOWN │ Anonymous ttf_subclass of android. App. IRequestFinishCallback $Stub │ left Activity down $1. This $0 │ ~ ~ ~ ~ ~ ~ ╰ - > com.example.MainActivity instanceCopy the code

Library Leaks are usually not aligned for repair. You can see the full list of known Leaks in the AndroidReferenceMatchers class. If you find an unrecognized Android SDK leak, report it. You can also customize the list of known library leaks.

Monitor activity, fragment, rootView and ViewModel

The initialization code mentioned earlier is as follows, so let’s look at the internal details of manualInstall.

// Initialize the code
AppWatcher.manualInstall(application)

///AppWatcher manualInstall code
@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
   //******* Check whether the main thread ********/
  checkMainThread()
  if (isInstalled) {
    throw IllegalStateException(
      "AppWatcher already installed, see exception cause for prior install call", installCause
    )
  }
  check(retainedDelayMillis >= 0) {
    "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
  }
  installCause = RuntimeException("manualInstall() first called here")
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // Requires AppWatcher.objectWatcher to be set
  / / / (2)
  LeakCanaryDelegate.loadLeakCanary(application)
  / / / (1)
  watchersToInstall.forEach {
    it.install()
  }
}
Copy the code

AppWatcher serves as the API hub for the Android platform wrapped in ObjectWatcher. Automatic installation configures default listening. Key points in the above code are numbered

(1) Install the default listening observation

The code in (1) performs the install operation of InstallableWatcher. It does not pass the watchersToInstall parameter, so it uses appDefaultWatchers(Application). The code below provides four default listening watchers

fun appDefaultWatchers(
  application: Application./ / / (1.1)
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    / / / (1.2)
    ActivityWatcher(application, reachabilityWatcher),
    / / / (1.3)
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    / / / (1.4)
    RootViewWatcher(reachabilityWatcher),
    / / / (1.5)
    ServiceWatcher(reachabilityWatcher)
  )
}
Copy the code

The four that are numbered we’re going to analyze one by one

(1.1) reachabilityWatcher parameter

The code in (1.1) is a ReachabilityWatcher parameter that will be used in the next four instances, In the code you can see that the reachabilityWatcher instance is a member variable of AppWatcher: objectWatcher, and the corresponding instantiation code is as follows.

/** * The [ObjectWatcher] used by AppWatcher to detect retained objects. * Only set when [isInstalled] is true. */
val objectWatcher = ObjectWatcher(
  clock = { SystemClock.uptimeMillis() },
  checkRetainedExecutor = {
    check(isInstalled) {
      "AppWatcher not installed"
    }
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  isEnabled = { true})Copy the code

As you can see, objectWatcher is an objectWatcher object, which is responsible for detecting leaks in the holding objects and is analyzed in section 3. Back to the creation of the ActivityWatcher instance, proceed to the annotated code

(1.2) The ActivityWatcher instance completes the listening of the Activity instance

Back in (1.2), the code creates an instance of ActivityWatcher and installs it at install time. Look at the source code of the ActivityWatcher class

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
     //(1.2.1) Construct the implementation class of life cycle callback through dynamic proxy
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        / / (1.2.3)
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}override fun install(a) {
    / / (1.2.3)
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall(a) {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
Copy the code

(1.2.1) lifecycleCallbacks instance

Note (1.2.1) code creates ActivityLifecycleCallbacks instance, the instance realized Application. ActivityLifecycleCallbacks. By ‘ ‘*noOpDelegate*’ ‘() implements other callback methods using dynamic proxies. For those interested, see the noOpDelegate source code

(1.2.2) Install method of the Activity listener

The code in the annotation (1.2.2) is the primary code for initialization. The method is simply to register lifecycleCallbacks in the application and go to the method implemented there when the activity is destroyed

Monitor the onActivityDestroyed callback on the activity

Annotations (1.2.3) code is the main initialization code, the activity is destroyed, the callback, the method in which the check for leaks the instance, call AppWatcher. ObjectWatcher. ExpectWeaklyReachable method, This is where leak monitoring for the activity is completed. At this point, it is back to the ObjectWatcher source mentioned in 1.1. See section 4 for related analysis.

(1.2-end)Activity monitoring related summary

The ActivityInstaller is done looking at the Activity initialization code and the details of adding listeners. Summarized into the following steps:

  1. Call ActivityInstaller. Install initialization method
  2. Register ActivityLifecycleCallbacks in Application
  3. When all the activity onDestroy, call the ObjectWatcher expectlyWeaklyReachable method to check whether the activity object has been reclaimed after five seconds. Flag memory leaks. I’ll do that in the next video.
  4. Follow-up operations when a memory leak is detected. Analysis after.

(1.3) FragmentAndViewModelWatcher monitoring fragments and Viewodel instance

(1.3) is to create a FragmentAndViewModelWatcher instance. Monitor fragment and ViewModel for memory leaks.

This class implements SupportFragment compatibility with androidxFragment and androidO. As SDK development, this compatibility method can be learned.

private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?). {
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  }

override fun install(a) {
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
Copy the code

As with ActivityWatcher, install registers life cycle listeners. This is done by the fragmentDestroyWatchers element on each activity create. So fragmentDestroyWatchers is the true fragment and ViewModel listener. Let’s look at the fragmentDestroyWatchers elements to create:

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit> ()//(1.3.1) The Android framework supports fragment leak detection starting in Android (26).
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
   / / (1.3.2)getWatcherIfAvailable( ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? .let { fragmentDestroyWatchers.add(it) }/ / (1.3.3)getWatcherIfAvailable( ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? .let { fragmentDestroyWatchers.add(it) } fragmentDestroyWatchers }Copy the code

You can see internal created AndroidOFragmentDestroyWatcher for intercepting fragments. Principle is to use the registered in FragmentManager FragmentManager. FragmentLifecycleCallbacks to monitor fragment and fragments. The view and viewmodel instance of the leak. The fragment in Android was added in Api 26. Therefore, LeakCanary’s fragment leak detection support for the Android framework also starts in Android (26), see code (1.3.1). Marked 1.3.1, 1.3.2, The three wathcers instantiated by 1.3.3 are AndroidOFragmentDestroyWatcher AndroidXFragmentDestroyWatcher, AndroidSupportFragmentDestroyWatcher. The internal implementation code is much the same, using reflection to instantiate different watchers to achieve compatibility between androidX and support and between android versions.

(1.3.1) AndroidOFragmentDestroyWatcher instance

The code at (1.3.1) adds an observer instance of androidO. See the code for details, because the implementation is much the same, see 1.3.2 for analysis.

(1.3.2) AndroidXFragmentDestroyWatcher instance

(1.3.2) code calls getWatcherIfAvailable created AndroidXFragmentDestroyWatcher instance through reflection, if there is no Androidx library it returns null. Now jump to AndroidXFragmentDestroyWatcher source code analysis

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

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

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?). {
    //(1.3.2.1) Initialize ViewModelClearedWatcher
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
     // Monitor fragment.view for leaks
      val view = fragment.view
      if(view ! =null) {
        reachabilityWatcher.expectWeaklyReachable(
          view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
          "(references to its views should be cleared to prevent leaks)")}}override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      // Monitor the fragment for leaks
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback")}}/ / / initialization, fragmentLifecycleCallbacks registration
  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // Register the viewModel listener callback for the activity
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}
Copy the code

As you can see from the source code, the following steps are used to initialize the Watcher.

  1. FragmentManager.registerFragmentLifecycleCallbacks Register the listen callback
  2. ViewModelClearedWatcher.installInit foractivity.viewModelListening in
  3. In the callbackonFragmentCreatedIs used in the callbackViewModelClearedWatcher.installRegistered forfragment.viewModelListening in.
  4. inonFragmentViewDestroyedListening to thefragment.viewThe leakage
  5. inonFragmentDestroyedListening to thefragmentThe leakage.

Monitoring methods and ActivityWatcher same, different is more than a ViewModelClearedWatcher. Install. Now analyze the source code for this section, which is in the annotation (1.3.2.1).

// The watcher inherits from the ViewModel, and the lifecycle is managed by the ViewModelStoreOwner.
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    //(1.3.2.3) Get all viewModelMaps from all stores by reflection
    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) {
    ///(1.3.2.4) When the viewmodle is cleared to release callback, check all viewmodle leakviewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback")}}companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun 
        create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      ///(1.3.2.2) Get the ViewModelClearedWatcher instance
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}
Copy the code

From the code, you can see that leak detection for the viewModel is done by creating a new viewModel instance. Listen for leaks in the remaining viewModels of the storeOwner at the instance’s onCleared. The highlighted code is analyzed one by one:

(1.3.2.2) Code:

Get the ViewModelClearedWatcher instance and pass the storeOwner and reachabilityWatcher in the custom Factory.

(1.3.2.3) Code:

Get the viewModelMap of the storeOwner through reflection

(1.3.2.4) Code:

When the ViewModel completes its mission OnClear, start monitoring all viewModels owned by storeOwner for memory leaks.

(1.3-end)Fragment and ViewModel

The monitoring method is through expectlyreachable method of ObjectWatcher. Fragments using FragmentLifecyclerCallback callback is registered, the ViewModel is created under the corresponding StoreOwner monitoring the ViewModel to implement the life cycle of the response. We can also learn how to use reflection to create corresponding platform-compatible implementation objects. And the idea of listening to the rest of the viewModel lifecycle by creating a viewModel.

(1.4) Source analysis of RootViewWatcher

By default, the four Watchers come to the next RootViewWatcher. The Window RootView listener relies on squre’s own Curtains framework.

implementation "Com. Squareup. Curtains, curtains, 1.0.1." "
Copy the code

The key source code for the class is as follows:

 private val listener = OnRootViewAddedListener { rootView ->
 // If it is Dialog TOOLTIP, TOAST, UNKNOWN and other types of Windows
 / / trackDetached to true
    if (trackDetached) {
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback")}override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install(a) {
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall(a) {
    Curtains.onRootViewsChangedListeners -= listener
  }
}
Copy the code

See the key code, it is added in the Curtains onRootViewsChangedListeners listeners. Listen for leaks while onViewDetachedFromWindow when windowsType is **Dialog** ***TOOLTIP**,** *TOAST*****,** or unknown. The listener in SMB will be called globally if the Windows rootView changes. Curtains, another open source library from SquareUp, provides a centralized API for working with Android Windows. Go to his official warehouse.

(1.5) ServiceWatcher listens to Service memory leak

Here’s the last Watcher in AppWatcher. ServiceWatcher. The code is longer, and the key point analysis is intercepted.

Look at the member variables firstactivityThreadServices

private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread")}private val activityThreadInstance by lazy {
  activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!!!! }private val activityThreadServices by lazy {
  val mServicesField =
    activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

  @Suppress("UNCHECKED_CAST")
  mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
Copy the code

ActivityThreadServices is a Map with all

pairs installed. In the code, you can see that the mServices variable is taken rudely from the ActivityThread instance directly by reflection. Assign to activityThreadServices. There are multiple swap operations in the source code, which are executed at install time. The main purpose is to add hooks to some of the original service lifecycle callbacks to detect memory leaks and will be swapped back when unInstall occurs.
,>

(1.5.2) swapActivityThreadHandlerCallback:

Take ActivityThread’s Handler and replace its Callback handleMessage with a loaded handler. Callback

Handler.Callback { msg ->
  if (msg.what == STOP_SERVICE) {
    val key = msg.obj asIBinder activityThreadServices[key]? .let { onServicePreDestroy(key, it) } } mCallback? .handleMessage(msg) ? :false
}
Copy the code

As you can see in the code, the main hook to the STOP_SERVICE operation is onServicePreDestroy. The main effect is to create a weak reference for the service and add it to servicesToBeDestroyed[token].

(1.5.3) and then lookswapActivityManagerMethods.

This method completes the replacement of ActivityManager with a dynamic proxy class for IActivityManager. The code is as follows:

Proxy.newProxyInstance(
  activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
//private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
  if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
    valtoken = args!! [0] as IBinder
    if (servicesToBeDestroyed.containsKey(token)) {
       / / / (1.5.3)
      onServiceDestroyed(token)
    }
  }
  try {
    if (args == null) {
      method.invoke(activityManagerInstance)
    } else {
      method.invoke(activityManagerInstance, *args)
    }
  } catch (invocationException: InvocationTargetException) {
    throw invocationException.targetException
  }
}
Copy the code

If the service is in the servicesToBeDestroyed map that you joined before, you will have to create a new Service from the servicesToBeDestroyed Map. In executing a serviceDoneExecuting method, you will have to create a new service from the servicesToBeDestroyed Map. OnServiceDestroyed is called to monitor the memory leak of the service.

(1.5.4) codeonServiceDestroyedThe specific code is as follows

private fun onServiceDestroyed(token: IBinder){ servicesToBeDestroyed.remove(token)? .also { serviceWeakReference -> serviceWeakReference.get()? .let { service -> reachabilityWatcher.expectWeaklyReachable( service,"${service::class.java.name} received Service#onDestroy() callback")}}}Copy the code

The code here is familiar and is the same as the code used to monitor activities and so on. Go back to the swapActivityManager method and see the specific type of agent ActivityManager. You can see that the proxy object looks like the code below, which may be an ActivityManager instance or an ActivityManagerNative instance, depending on the version. Proxy interface is Class. Class.forname (” android. App. IActivityManager “).

val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  "android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
  "android.app.ActivityManagerNative" to "gDefault"
}
Copy the code

(1.5-end) Service leak monitoring summary

In summary, the leak analysis of the Service listens for some system executions by adding hooks. It is mainly divided into the following steps:

  1. Get the mService variable in ActivityThread to get a reference to the service instance
  2. Through swapActivityThreadHandlerCallback Handler in ActivityThread sendMessage add hooks, in the executionmsg.what == STOP_SERVICEwhen

Fourth, ObjectWatcher retains object inspection analysis

Let’s switch to ObjectWatcher’s ExpectlyWeaklyreachable method

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
   ObjectWatcher held by AppWatcher is enabled by default
  if(! isEnabled()) {return
  }
  /// Remove the listener that has been reclaimed before
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
   //(1) Create a weak reference
  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 {
    / / (2)
    moveToRetained(key)
  }
}
Copy the code

Continue to analyze where the annotations are in the source code.

(1) Create a weak reference

The code in the annotation (1.2.4) is the main code for initialization. It creates the WeakReference of the object to be observed and passes into queue as the object information storage queue after gc. In WeakReference, when holding the object to be collected by gc, its wrapper object will be pushed into the queue. The queue can be observed later.

MoveToRetained (key) moveToRetained(key) moveToRetained(key

Executed as Executor Runner, in AppWatcher, the default delay is five seconds before executing this method to view source analysis

@Synchronized private fun moveToRetained(key: String) {
/// Remove an observation object that has been reclaimed
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if(retainedRef ! =null) {
  // Record the leak time
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    // Callback leak listener
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}
Copy the code

From the code above, ObjectWatcher monitors memory leaks in the following steps

  1. Clear the listener that has been reclaimed
  2. Create a weak reference and pass in the ReferenceQueue to hold the queue for GC information
  3. After the delay of the specified time, check again whether the target object is reclaimed (by checking whether the ReferenceQueue queue has the WeakReference instance)
  4. Callback after detecting that the object is not reclaimedonObjectRetainedListenersonObjectRetained

Five, dumpHeap, what is the dumpHeap process

(1.1) objectWatcher add OnObjectRetainedListeners listening in

Go back to the manualInstall method of the original AppWatcher. You can see that the loadLeakCanary method is executed. The code is as follows:

/ / / (2)
  LeakCanaryDelegate.loadLeakCanary(application)
 // Reflection gets the InternalLeakCanary instance
  val loadLeakCanary by lazy {
  try {
    val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
    leakCanaryListener.getDeclaredField("INSTANCE").get(null) as (Application) -> Unit
  } catch (ignored: Throwable) {
    NoLeakCanary
  }
}
Copy the code

This method gets a static instance of InternalLeakCanary through reflection. And it calls his invoke(Application: Application) method, so let’s look at the InternalLeakCanary method:

override fun invoke(application: Application) {
  _application = application

  checkRunningInDebuggableBuild()
  / / add addOnObjectRetainedListener (1.2)
  AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

  val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    / / Gc triggers
  val gcTrigger = GcTrigger.Default

  val configProvider = { LeakCanary.config }

  val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
  handlerThread.start()
  val backgroundHandler = Handler(handlerThread.looper)
/ / / (1.3)
  heapDumpTrigger = HeapDumpTrigger(
    application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
    configProvider
  )
  /// (1.4) Add the application change front and back listener
  application.registerVisibilityListener { applicationVisible ->
    this.applicationVisible = applicationVisible
    heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
  }
  / / (1.5)
  registerResumedActivityListener(application)
  / / (1.6)
  addDynamicShortcut(application)

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

So we see that when we initialize, we do six steps like this

  • (1.2) Add yourself to ObjectWatcher’s object exception holding listener
  • (1.3) Create the memory snapshot dump trigger HeapDumpTrigger
  • (1.4) Monitor the changes in the front and background of the application and record the time when it comes to the background, which is convenient for LeakCanary to do leak monitoring for some destroy operations just entered into the background
  • (1.5) Register the Activity lifecycle callback to get the current resumed activity instance
  • (1.6) Add dynamic Desktop Shortcuts
  • (1.7) In an asynchronous thread, determine whether the thread is in a dumpHeap state. If it is, trigger a memory leak check

The most important of these is 1.2, where we focus on what he does in the callback as an ObjectRetainedListener.

(1.2) Add an object holding listener abnormally

You can see the code (1.2) that adds itself to the leak detection callback in objectWatcher. When ObjectWatcher detects that the object is still held abnormally, the onObjectretrace method is called back. From the source code, which calls the heapDumpTrigger scheduleRetainedObjectCheck method, the code is as follows.

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

HeapDumpTrigger is, as the name suggests, the trigger for a memory snapshot dump. The checkRetainedObjects method of the HeapDumpTrigger is finally called in the callback to check for memory leaks.

(1.3) Checking memory leaks checkRetainedObjects

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

  val config = configProvider()
 // Omit some code, mainly to judge iCanHasHeap.
 // If the memory snapshot is not in the dump state, the process is not processed. Send a notification prompt if a new exception - held object is found
 //%d retained objects, tap to dump heap
  / * *... * /

  var retainedReferenceCount = objectWatcher.retainedObjectCount

   // Triggers the GC
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    // Retrieve the exception held object
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }
  // If the number of leaks is less than the threshold and the app is in the foreground or has just been transferred to the background, display the leak notification and return first
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

// If the number of leaks reaches the dumpHeap requirement, keep going
   // Dump memory snapshot when WAIT_BETWEEN_HEAP_DUMPS_MILLIS (60 seconds by default) is triggered only once. If it has been triggered previously, it will not generate a memory snapshot and send a notification directly.
// Omit the snapshot dump timing. If the snapshot dump time is not satisfied, the Last heap dump was less than a minute ago is displayed
/ * *... * /

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

This section also shows whether detection requires dumpHeap to be divided into four steps.

  1. Returns if no exception held object is detected
  2. If there is an exception object, the GC is actively triggered
  3. If there are still exception objects, it’s a memory leak.
  4. Determine if the number of leaks is large enough to require dumping
  5. Determine whether a dump is performed within one minute
  6. dumpHeap

This is all judgment code, but the key focus is the dumpHeap method

(1.4) dumpHeap Dumps memory snapshots

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  saveResourceIdNamesToMemory()
  val heapDumpUptimeMillis = SystemClock.uptimeMillis()
  KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
  when (val heapDumpResult = heapDumper.dumpHeap()) {
    is NoHeapDump -> {
     // Omit dump failure, wait for retry code and send failure notification code
    }
    is HeapDump -> {
      lastDisplayedRetainedObjectCount = 0
      lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
      /// Delete objects in objectWatcher that were held before heapDumpUptimeMillis, that is, objects that have been dumped
      objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
       // Send the file to HeapAnalyzerService for parsing
      HeapAnalyzerService.runAnalysis(
        context = application,
        heapDumpFile = heapDumpResult.file,
        heapDumpDurationMillis = heapDumpResult.durationMillis,
        heapDumpReason = reason
      )
    }
  }
}
Copy the code

AndroidHeapDumper#dumpHeap is called in the HeapDumpTrigger#dumpHeap method. And the call immediately after the dump HeapAnalyzerService. RunAnalysis memory analysis work, the method in the next section. First look at the source code of AndroidHeapDumper#dumHeap

override fun dumpHeap(a): DumpHeapResult {
// Create a new hprof file
  valheapDumpFile = leakDirectoryProvider.newHeapDumpFile() ? :return NoHeapDump

  valwaitingForToast = FutureResult<Toast? > ()/// Show dump toast
  showToast(waitingForToast)

  /// If the toast is displayed for more than five seconds, the dump will not be performed
  if(! waitingForToast.wait(5, SECONDS)) {
    SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
    return NoHeapDump
  }

  // Omit the dumpHeap notification bar message code
  val toast = waitingForToast.get(a)return try {
    val durationMillis = measureDurationMillis {
    / / call DumpHprofData
      Debug.dumpHprofData(heapDumpFile.absolutePath)
    }
    if (heapDumpFile.length() == 0L) {
      SharkLog.d { "Dumped heap file is 0 byte length" }
      NoHeapDump
    } else {
      HeapDump(file = heapDumpFile, durationMillis = durationMillis)
    }
  } catch (e: Exception) {
    SharkLog.d(e) { "Could not dump heap" }
    // Abort heap dump
    NoHeapDump
  } finally {
    cancelToast(toast)
    notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
  }
}
Copy the code

Within this method, the debug.dumphProfData method is finally called to complete the generation of the Hprof snapshot.

6. Analyze memory HeapAnalyzerService

As you can see from the code analysis above, dumpHeap is followed by a method to start the memory analysis service. Now we jump to the source code for HeapAnalyzerService.

override fun onHandleIntentInForeground(intent: Intent?). {
     // Omit the parameter fetching code
  val config = LeakCanary.config
  val heapAnalysis = if (heapDumpFile.exists()) {
    analyzeHeap(heapDumpFile, config)
  } else {
    missingFileFailure(heapDumpFile)
  }
   // Omit the code to improve the attributes of the analysis result
  onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
  config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
Copy the code

As you can see, the focus is analyzeHeap, where HeapAnalyzer# Analyze HeapAnalyzer is called in the shark module.

(1) HeapAnalyzer# analyze

The memory analysis method code is as follows:

fun analyze(
  heapDumpFile: File,
  leakingObjectFinder: LeakingObjectFinder,
  referenceMatchers: List<ReferenceMatcher> = emptyList(),
  computeRetainedHeapSize: Boolean = false,
  objectInspectors: List<ObjectInspector> = emptyList(),
  metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
  proguardMapping: ProguardMapping? = null
): HeapAnalysis {
  
 // Omit the processing code for the memory snapshot file that does not exist

  return try {
    listener.onAnalysisProgress(PARSING_HEAP_DUMP)
   /// I/O reads the memory snapshot
    val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
    sourceProvider.openHeapGraph(proguardMapping).use { graph ->
      val helpers =
        FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
     // Key code: find the result of the leak and its corresponding call stack here
      val result = helpers.analyzeGraph(
        metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
      )
      val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
      /// I/O read status
      val randomAccessStats =
        "RandomAccess[" +
          "bytes=${sourceProvider.randomAccessByteReads}," +
          "reads=${sourceProvider.randomAccessReadCount}," +
          "travel=${sourceProvider.randomAccessByteTravel}," +
          "range=${sourceProvider.byteTravelRange}," +
          "size=${heapDumpFile.length()}" +
          "]"
      val stats = "$lruCacheStats $randomAccessStats"
      result.copy(metadata = result.metadata + ("Stats" to stats))
    }
  } catch (exception: Throwable) {
  // Omit exception handling}}Copy the code

Analyzing the code shows that the memory snapshot analysis is divided into the following five steps:

  1. Read the hprof memory snapshot file
  2. Find the number of leak objects marked by LeakCanary and the weak reference wrapper IDS, class name iscom.squareup.leakcanary.KeyedWeakReference

The code in KeyedWeakReferenceFinder# findLeakingObjectIds

  1. Find the path where the leaking object’s gcRoot started

The code in PathFinder# findPathsFromGcRoots

  1. Return the analysis result and walk the result callback
  2. A notification bar message is displayed in the callback indicating the success or failure of the memory analysis, and the leak list is stored in the database

Code for details see DefaultOnHeapAnalyzedListener# onHeapAnalyzed and LeaksDbHelper

  1. Click on the notification bar to jump toLeaksActivityDisplay memory leak information.

Seven,

Finally, from start to finish, finally combed a wave of LeakCanary source code

Learned so much in the process — >

  • The way Gc is invoked activelyGcTrigger.Default.runGc()
Runtime.getRuntime().gc()
Copy the code
  • The Seald class seals classes to express states, such as the following (the key benefit is that using WHEN can directly override all cases instead of using else).
sealed class ICanHazHeap {
  object Yup : ICanHazHeap()
  abstract class Nope(val reason: () -> String) : ICanHazHeap()
  class SilentNope(reason: () -> String) : Nope(reason)
  class NotifyingNope(reason: () -> String) : Nope(reason)
}
sealed class Result {
  data class Done(
    val analysis: HeapAnalysis,
    val stripHeapDumpDurationMillis: Long? = null
    ) : Result()
  data class Canceled(val cancelReason: String) : Result()
}
Copy the code
  • Learned about the system API for creating memory snapshots
 Debug.dumpHprofData(heapDumpFile.absolutePath)
Copy the code
  • Known that the ReferenceQueue detects whether the memory object is GC, WeakReference is rarely used before.
  • Learned leakCanary’s sub-module idea. As an SDK, many function modules are introduced automatically on. For example, leakcanary-android-process automatically starts the corresponding process.
  • Learn how to add hooks by reflecting hook code and replacing the instance. For example, in the Service leak listening code, replaceHandlerandactivityManagerIn the operation.

It’s good to read more source code. No wonder I couldn’t get a job before. Too little.