Speaking of leakCanary we should all be familiar with, the principle of asking should all know whether the object is recovered through WeakReference+ReferenceQueue detection when the object is destroyed, delay the second detection has not been recovered is considered as a suspect, and then dump heap and analyze it…

But do you know which objects leakCanary can detect? How do you get the objects that are about to be destroyed?

First, the conclusion of Question 1:

Leakcanary2.6 Can only monitor activities and fragments.

Leakcanary2.6 has added monitoring for ViewModel, RootView, and Service.

As for how to detect the destruction time of these objects, the following is a simple example of code leakcanary- Android :2.7.

1. Initialization

As we all know, LeakCanary does not require manual initialization starting with version 2.0, and is implemented without manual initialization via ContentProvider:

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

In its onCreate(), it does a specific initialization:

override fun onCreate(a): Boolean { val application = context!! .applicationContext as Application AppWatcher.manualInstall(application)return true
}
Copy the code

Now look at what appwatcher.manualInstall () does:

fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    ...
    watchersToInstall.forEach {
      it.install()
    }
}
Copy the code

WatchersToInstall () = install() = install(); Look at its default implementation appDefaultWatchers(Application) :

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
        / / monitor the Activity
        ActivityWatcher(application, reachabilityWatcher),
        // Monitor the Fragment and ViewModel
        FragmentAndViewModelWatcher(application, reachabilityWatcher),
        / / monitor RootView
        RootViewWatcher(reachabilityWatcher),
        / / monitor Service
        ServiceWatcher(reachabilityWatcher)
    )
}
Copy the code

A List of four watchers is returned to monitor the destruction of activities, fragments, ViewModel, RootView, and Service. Get the object to be destroyed through WeakReference and ReferenceQueue to conduct preliminary judgment of memory leakage, and finally Dump HeapProfile for specific analysis.

Here’s how Watcher implements the monitoring object destruction process.

2, ActivityWatcher

ActivityWatcher is very simple. It registers Activity life cycle callbacks with the Application to monitor the destruction of each Activity. The current Activity object is added to the monitoring queue by reachabilityWatcher during Activity destruction, and then analyzed.

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

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

3, FragmentAndViewModelWatcher

This Watcher implements Fragment and ViewModel destruction monitoring. Let’s first look at Fragment destruction monitoring:

3.1 Monitoring Fragment Destruction

There are three types of fragments: native to the framework, in the supportv4 package, and on androidx. So you need to deal with these three cases separately, but the idea is the same, the difference is the guide package. Take a look at how the framework’s own fragments are monitored.

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

  private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit> ()// Add Watcher for three types of fragments
    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(reachabilityWatcher)
      )
    }
    ...
    fragmentDestroyWatchers
  }

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

  override fun install(a) {
    // Register the Activity lifecycle
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

}
Copy the code

Then look at how to deal with in the AndroidOFragmentDestroyWatcher fragments of the framework.

internal class AndroidOFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if(view ! =null) {
        // Add the view in the Fragment to the monitor queue
        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
    ) {
      // Add the Fragment to the monitoring queue
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback")}}override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    // Register the Fragment's lifecycle callback with fragmentManager
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)}}Copy the code

The higher-order function to use, look directly at Invoke (), where you get the Activity’s fragmentManager and register the Fragment’s lifecycle callback.

Obtain the view in the Fragment in the onFragmentViewDestroyed callback and add the view to the monitor queue.

Add the Fragment to the monitor queue in the onFragmentDestroyed callback.

3.2 Monitor ViewModel destruction

ViewModel monitoring is implemented for Fragment monitoring on AndroidX because ViewModel is only available on AndroidX. The code is as follows:

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

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

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?). {
      // Add the Fragment ViewModel to the monitor queue
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }
	...
  }

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      // Register the Fragment lifecycle callback
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // Add the ViewModel in the activity to the monitor queue
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}
Copy the code

Viewmodels exist in both activities and fragments, so you need to monitor the viewmodels of both by first registering the Fragment lifecycle callback in invoke(), The onFragmentCreated callback monitors the ViewModel in the Fragment through ViewModelClearedWatcher, and then directly monitors the current Activity through ViewModelClearedWatcher.

Now let’s see how ViewModelClearedWatcher works:

internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  private val viewModelMap: Map<String, ViewModel>?

  init {
    // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
    // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
    // does not have ViewModelStore#keys. All versions currently have the mMap field.
    viewModelMap = try {
      val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
      mMapField.isAccessible = true
      // Reflection gets the mMap object in the ViewModelStore instance
      mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
      null}}override fun onCleared(a) {
    // Walk through the mMap object and add each ViewModel object to the monitor queueviewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback")}}companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      // Create the ViewModelProvider and set a Factory,
      val provider = ViewModelProvider(storeOwner, object : Factory {
        override fun 
        create(modelClass: Class<T>): T =
          // Create ViewModelClearedWatcher and pass the parameters
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}
Copy the code

Note that ViewModelClearedWatcher is a ViewModel, and an instance of ViewModelClearedWatcher was created in Install (), The mMap object that holds the ViewModel when it gets the current ViewModelStoreOwner object is reflected at its initialization, and each ViewModel object is added to the monitor queue through the mMap when it is onCleared.

3.3 summary:

1. There are three types of Fragment monitoring, which are included in the Framework, supportV4 package, and AndroidX.

2. Monitor fragments by getting the fragmentManager of the current Activity when the Activity is created. Add the Fragment lifecycle callback using the fragmentManager. Add the Fragment object and its View to the monitor queue in the callback.

3. Monitor the ViewModel in the Activity and Fragment respectively.

4. The ViewModel object created from the current ViewModelStoreOwner instance will be destroyed along with the ViewModelStoreOwner instance.

5. Get the mMap of the collection used to hold viewModels in the current ViewModelStoreOwner by reflection, and iterate over the mMap when the ViewModel created in 4 is destroyed, adding each of its objects to the monitor queue.

4, RootViewWatcher

RootViewWatcher monitors a DecorView, and is involved in the creation of a DecorView during Activity, Dialog, ToolTip, and Toast creation.

Those familiar with View processes should know that executing an Activity’s onResume in an ActivityThread adds its DecorView to a Collection of Windows ManagerGlobal decorviews that can be retrieved by reflection. For this collection agent can monitor DecorView add and delete, give DecorView Settings can be monitored DecorView AttachStateChangeListener Attached and Detached state.

The code for LeakCanary is too complex, so the following is a simple code to implement the general flow:

class RootViewSpy {

    fun install(a) {
        val windowManagerClass = Class.forName("android.view.WindowManagerGlobal")
        // 1, reflection gets WindowManagerGlobal instance object
        val windowManagerInstance = windowManagerClass.getMethod("getInstance").invoke(null)

        // 2, reflection gets the mViews set in the WindowManagerGlobal instance object
        val mViewsField =
            windowManagerClass.getDeclaredField("mViews").apply { isAccessible = true }
        val mViews = mViewsField.get(windowManagerInstance) as ArrayList<View>

        // 3, store the contents of mViews into the agent collection
        delegatingViewList.apply { addAll(mViews) }
        // 4, replace the original mViews object with the proxy collection
        mViewsField.set(windowManagerInstance, delegatingViewList)
    }

    // Proxy object
    private val delegatingViewList = object : ArrayList<View>() {
        override fun add(rootView: View): Boolean {
            // Add AttachStateChange status listener to the DecorView
            rootView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {

                override fun onViewAttachedToWindow(v: View) {
                    LogUtil.e("onViewAttachedToWindow")}override fun onViewDetachedFromWindow(v: View) {
                    LogUtil.e("onViewDetachedFromWindow")}})return super.add(rootView)
        }
    }
}
Copy the code

Dynamic proxies on Windows Manager IMPl can also listen for DecorView additions and deletions

summary

Reflection gets the collection of Windows ManagerGlobal used to hold the DecorView

2, proxy this collection and monitor the DecorView addition process

For each add AttachStateChangeListener DecorView, 3, monitoring the process Attached and Detached

4. Since Activity detection is already in place, RootViewWatcher mainly monitors rootViews of Toast, ToolTip and Dialog (not detected by default)

5, ServiceWatcher

Monitoring the destruction of a Service requires understanding the Stop process of a Service. There are three cases of a Service Stop: stopService() in an Activity, stopSelf() in a Service, and unBindService() in a Service. All three cases enter AMS in different ways, but all end up calling ApplicationThread’s scheduleStopService() method with Binder, The ApplicationThread sends a message to the ActivityThread through the handler. The ActivityThread executes the Stop logic of the Service, and the ActivityThread notifies AMS to do the final finishing work. The general process is as follows:

There are two points to listen on a Service Stop:

  1. When an ActivityThread receives an ApplicationThread message (Hook 1), the Service does this by adding a callback to the Handler object in the ActivityThread. So you can get an example.

  2. After executing a Service Stop in ActivityThread, AMS is notified of the final work through a binder call, which can be listened on by Hook AMS. At this point, the service has already executed onDestory() and may not be able to retrieve the actual instance, so you need to save the service instance at hook 1 and retrieve the instance here.

LeakCanary gets an instance of the Service to be stopped at Hook point 1 and saves it by weak reference, then gets a weak reference to the instance at Hook point 2 and listens on it.

The code uses higher-order functions, like this:

class ServiceWatcher {
    private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()

    private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread")}// Reflection gets the ActivityThread instance
    private val activityThreadInstance by lazy {
        activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!!!! }// Get the mService object in ActivityThread
    private val activityThreadServices by lazy {
        val mServicesField =
            activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

        mServicesField.get(activityThreadInstance) as Map<IBinder, Service>
    }

    fun install(a) {
        try {
            swapActivityThreadHandlerCallback { mCallback ->
                // Create a Handler Callback object
                Handler.Callback { msg ->
                    if (msg.what == STOP_SERVICE) {
                        val key = msg.obj as IBinder
                        // Obtain Service object from mService based on key(token)activityThreadServices[key]? .let { onServicePreDestroy(key, it) } } mCallback? .handleMessage(msg) ? :false
                }
            }
            swapActivityManager { activityManagerInterface, activityManagerInstance ->
                // Dynamic proxy
                Proxy.newProxyInstance(
                    activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
                ) { _, method, args ->
                    if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
                        valtoken = args!! [0] as IBinder
                        if (servicesToBeDestroyed.containsKey(token)) {
                            // Execute onServiceDestroyed() when calling AMS servicedOneMethod
                            onServiceDestroyed(token)
                        }
                    }
                    try {
                        if (args == null) {
                            method.invoke(activityManagerInstance)
                        } else {
                            method.invoke(activityManagerInstance, *args)
                        }
                    } catch (invocationException: InvocationTargetException) {
                        throw invocationException.targetException
                    }
                }
            }
        } catch (ignored: Throwable) {
            LogUtil.e("Could not watch destroyed services")}}private fun onServicePreDestroy(
        token: IBinder,
        service: Service
    ) {
        LogUtil.e("onServicePreDestroy")
        servicesToBeDestroyed[token] = WeakReference(service)
    }

    private fun onServiceDestroyed(token: IBinder){ servicesToBeDestroyed.remove(token)? .also { serviceWeakReference -> serviceWeakReference.get()? .let { service -> LogUtil.e("${service::class.java.name} received Service#onDestroy() callback")}}}/** * Hook ActivityThread handler */
    private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?). ->Handler.Callback?). {
        val mHField =
            activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
        val mH = mHField[activityThreadInstance] as Handler

        val mCallbackField =
            Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
        // Get mCallback from mH
        val mCallback = mCallbackField[mH] as Handler.Callback?
        // Replace the mH callback object
        mCallbackField[mH] = swap(mCallback)
    }

    /** * hook ams binder proxy */
    private fun swapActivityManager(swap: (Class< * >,Any) - >Any) {
        val singletonClass = Class.forName("android.util.Singleton")
        val mInstanceField =
            singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }

        val singletonGetMethod = singletonClass.getDeclaredMethod("get")

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

        val activityManagerClass = Class.forName(className)
        val activityManagerSingletonField =
            activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
        val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]

        // Calling get() instead of reading from the field directly to ensure the singleton is
        // created.
        // Get the AMS binder proxy object
        val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)

        val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
        // Replace the original object with a dynamic proxy object
        mInstanceField[activityManagerSingletonInstance] =
            swap(iActivityManagerInterface, activityManagerInstance!!)
    }

    companion object {
        private const val STOP_SERVICE = 116

        private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"}}Copy the code

Note that the method parameters of the two Hook points are IBinder type tokens, so how to obtain the instance according to the token? View the ActivityThread code:

public final class ActivityThread {...// Save all Service instances and their ibinders
	final ArrayMap<IBinder, Service> mServices = newArrayMap<>(); .// The current instance of ActivityThread
	private staticActivityThread sCurrentActivityThread; . }Copy the code

MServices stores all Service instances and their corresponding IBinder. The sCurrentActivityThread is a static instance of the sCurrentActivityThread, so it can be directly reflected to obtain the sCurrentActivityThread instance. Then the mServices object can be reflected, and the Service instance can be obtained according to the token parameter of the Hook point.

summary
  1. Reflection gets in the ActivityThreadmServicesInstance, so you can get an instance of each Service later
  2. Get the ActivityThread handler and add a callback to the handler
  3. In step 2, from the token argumentmServicesTo get an instance of the Service to be stopped, and then save it by weak reference
  4. Hook AMS, when the ActivityThread notifies AMS that it has completed the stop operation of the Service, obtains the reference to the Service from the weak reference set in 2, and queues it to monitor
  5. The Hook AMS process is not introduced in detail here, you can refer to juejin.cn/post/700695…

Six, the last

The above has obtained the detection object destruction time, the next step is to determine whether these destroyed objects have been leaked. As for how to dump heap, how to analyze, how to notify users, etc., it is not the focus of this article, related articles are quite many, no specific analysis here.

LeakCanary is a foolproof tool that does a great job of detecting memory leaks, but in order to use it well it needs to know which objects it can detect leaks.

We all think that reflection and hook are monsters, but LeakCanary has all kinds of reflection and hook, understanding these ideas is very helpful to solve some special problems.