SoFunction
Updated on 2025-03-11

Android LeakCanary's principle of detecting memory leaks

LeakCanary 2.6 source code analyzes the principle of detecting memory leakage. In order to reduce the length of the article and highlight key points, it does not paste a large amount of source code. It needs to be eaten with the source code when reading.

How to get context

LeakCanary only needs to introduce dependencies and do not need to initialize code to perform memory leak detection. It obtains the application's context through ContentProvider. This way of obtaining context is very popular in open source third-party libraries. The following AppWatcherInstaller is registered in the manifest file in the aar package of LeakCanary.

internal sealed class AppWatcherInstaller : ContentProvider() {
 override fun onCreate(): Boolean {
  val application = context!!.applicationContext as Application
  (application)//1
  return true
 }
 ...
}

Detect memory leaks of which class objects are detected by default

(1) The method at the point will call the following method to register the object that needs to be detected:

 fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
 ): List<InstallableWatcher> {
  return listOf(
   ActivityWatcher(application, reachabilityWatcher),
   FragmentAndViewModelWatcher(application, reachabilityWatcher),
   RootViewWatcher(reachabilityWatcher),
   ServiceWatcher(reachabilityWatcher)
  )
 }

It can be seen that LeakCanary will include Activity, Fragment, ViewModel, RootView and Service in the detection. These objects have clear life cycles and occupy high memory. Their memory leaks need to be paid attention to.

How to incorporate these lifecycle objects into monitoring

(1) The manualInstall method at (1) will traverse the install method of the Watcher mentioned above to include these lifecycle objects in the detection in a timely manner.

ActivityWatcher

The install method in ActivityWatcher realizes detection of the Activity life cycle by registering an interface callback with the application. Here is a great trick. Using Kotlin delegation and Java dynamic proxy, we will give the default empty implementation of methods that do not need to be paid attention to. (2) (3) the code is extracted and can be used in places where there are requirements in daily development.

//ActivityWatcher
 private val lifecycleCallbacks =
  object :  by noOpDelegate() {
   override fun onActivityDestroyed(activity: Activity) {
    (
     activity, "${activity::} received Activity#onDestroy() callback"
    )//4
   }
  }

internal inline fun <reified T : Any> noOpDelegate(): T {
 val javaClass = T::
 return (
  , arrayOf(javaClass), NO_OP_HANDLER
 ) as T
}//2

private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
 // no op
}//3

(4) The method called is a general method for incorporating the object into the monitoring. As its name indicates, WeaklyReachable is compared to StronglyReachable. When an object is no longer needed, we want it to change from WeaklyReachable to StronglyReachable.

We can actively call the method when an object is no longer needed to detect memory leaks of any object (except the default object in the previous section):

(obj, "")

The onActivityDestroyed callback is used to include activity in monitoring.

Through the above analysis of the inclusion of the memory leaked source code of Activity, two key points can be found. First, it is necessary to obtain references to all objects to be detected in the application, and second, it is necessary to have a time when the life cycle of the object to be detected ends. These two points can be met at the same time through the registration interface, but there is no such convenient way for other types of objects.

The following describes how Fragment, ViewModel, RootView and Service are included in the detection.

FragmentAndViewModelWatcher

In order to be compatible with several implementations of different package names in the Android source code, the detection of them also needs to be implemented separately. In the FragmentAndViewModelWatcher, we only focus on the memory leak detection of Fragment in AndroidX FragmentDestroyWatcher, and the other implementations are similar.

FragmentAndViewModelWatcher first obtains the Activity reference in time through the registration callback, and obtains the Activity supportFragmentManager in AndroidXFragmentDestroyWatcher and registers with it. Include the Fragment and Fragment View into memory leak detection in the onFragmentDestroyed and onFragmentViewDestroyed callbacks.

For the detection of ViewModel, you need to pay attention to the ViewModelClearedWatcher. By adding a spy ViewModel named ViewModelClearedWatcher using the Activity reference obtained in the previous step, you can obtain the ability to receive the onCleared callback, because for a ViewModelStoreOwner (Activity, Fragment), if one of its own ViewModel calls back onCleared, the onCleared of other ViewModels should also be called. These ViewModels are obtained through the mMap property of ViewModelStore. In the onCleared callback of spy ViewModel, memory leak detection is included.

RootViewWatcher

For the RootView in the Window in Android, that is, DecorView, you can check it when the View's onViewDetachedFromWindow is registered by registering the addOnAttachStateChangeListener. Getting the reference to the object to be detected does not have callbacks to rely on like Activity and Fragment. LeakCanary adopts the Hook method to replace the RootView container in the install method. Specifically, it modifies the implementation of the ArrayList container of mViews in WindowManagerGlobal (including all DecorViews in Window) through the reflection mechanism, obtains the reference to the DecorView in its add method, and then sets the OnAttachStateChangeListener callback for detection.

ServiceWatcher

In Android, Service has no system callback to rely on whether it is to obtain references or determine the timing of monitoring. LeakCanary uses the Hook method to achieve its purpose. First, we get the mServices in ActivityThread through reflection, which is a map that contains all the services in the app. There are two Hook points in the install method. First, it is the transit center of the Android message mechanism, called H Handler. All callbacks on the system side to the application side need to go through its turnover. Because the priority of mCallback execution in the Handler is greater than the handleMessage method, Leakcanary replaces the mCallback implementation of H. When the message is STOP_SERVICE, the corresponding Service of the message is taken from the mServices as the Service Reference to be detected. The second Hook point is ActivityManagerService, which modifies its serviceDoneExecuting method through a dynamic proxy, adds memory leak detection before it is truly implemented, and the rest of the methods remain unchanged.

The timing of these categories inclusion detection can be summarized as follows:

How to get a reference When will it be included in the monitoring
Activity ActivityLifecycleCallbacks callback onActivityDestroyed
Fragment FragmentLifecycleCallbacks callback onFragmentDestroyed
View in Fragment FragmentLifecycleCallbacks callback onFragmentViewDestroyed
ViewModel Reflection to get the mMap of ViewModelStore spy ViewModel onCleared
DecorView in Window Hook mViews in WindowManagerGlobal onViewDetachedFromWindow
Service Hook H's mCallback implementation, when the message is STOP_SERVICE, get from mServices in ActivityThread Hook ActivityManagerService, detection in serviceDoneExecuting

How to determine the object that is leaked

After determining the object to be detected and the timing, check the expectedWeaklyReachable method of the ObjectWatcher, and you can know how to select the leaked object from the object to be detected (the default is the life-cycle class objects we analyzed in the previous section). The principle of determining memory leaked objects is the WeakReference we commonly use. Its two-parameter constructor supports passing a ReferenceQueue. When its associated object is recycled, the WeakReference will be added to the ReferenceQueue. LeakCanary's approach is to inherit the ReferenceQueue, add a property key with the value UUID, and add each object that needs to be monitored to a map with this UUID as a key. In this way, after GC, the removeWeaklyReachableObjects method traverses the ReferenceQueue and deletes the recycled objects in the map through the key value. The remaining objects can basically be determined that a memory leak has occurred.

How to determine the reference chain from GC root to leaked object

After determining the memory leaked object, other means are needed to determine the leaked object reference chain. This process begins with the checkRetainedObjects method. You can see that the foreground service HeapAnalyzerService is started, which can be seen in the notification bar when we use LeakCanary. The HeapAnalyzer analyze method is called in the service for heap memory analysis. The Shark library implements this function and will no longer be tracked.

The above is the detailed content of analyzing the principle of LeakCanary to detect memory leaks. For more information about LeakCanary to detect memory leaks, please pay attention to my other related articles!