preface

This article aims to comb through the entire workflow, and the source code is based on LeakCanary version 2.7

Introduction to the

1. What is LeakCanary

LeakCanary is square’s open source memory leak detection tool.

2. What are memory leaks

A memory leak is when the unreachable path of unused object resources remains with GcRoot, causing the system to fail to reclaim.

Process and analysis

1. Registration and observation

LeakCanary takes advantage of the fact that the onCreate life cycle of the ContentProvider is before the onCreate life cycle of the Application to write the registration part in the ContentProvider.

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

AppWatcher is the entry point to initialize an observation object.

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

The watchersToInstall parameter to the manualInstall method defaults to listening for activities, fragments, views, viewModels, and services.

1.1 ActivityWatcher

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

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

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

It is not difficult to see that through the application listening to the Activity life cycle, in onDestory callback expectlyReachable method (this method explained later), do memory leak check work.

1.2 FragmentAndViewModelWatcher

FragmentAndViewModelWatcher mainly tested the three

  1. Fragment#onDestroy()
  2. Fragment#onDestroyView()
  3. ViewModel#onCleared()
class FragmentAndViewModelWatcher( private val application: Application, private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>() (SDK_INT >= O) { fragmentDestroyWatchers.add( AndroidOFragmentDestroyWatcher(reachabilityWatcher) ) } GetWatcherIfAvailable (ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? . Let {fragmentDestroyWatchers. Add (it)} / / distinguish between support in fragments getWatcherIfAvailable ( ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? .let { fragmentDestroyWatchers.add(it) } fragmentDestroyWatchers } private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { for (watcher in fragmentDestroyWatchers) { watcher(activity) } } } override fun install() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks) } override fun uninstall() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) }Copy the code

1.3 RootViewWatcher

Through OnAttachStateChangeListener onViewAttachedToWindow and callback onViewDetachedFromWindow method can be used as a memory leak inspection work.

class RootViewWatcher( private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher {private val listener = OnRootViewAddedListener {rootView -> // Val trackDetached = when(rootView.windowtype) {PHONE_WINDOW -> {when (rootView.phoneWindow? .callback? .wrappedCallback) { is Activity -> false is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs) else -> true } } POPUP_WINDOW -> false  TOOLTIP, TOAST, UNKNOWN - > true} the if (trackDetached) {/ / addOnAttachStateChangeListener listening 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() { Curtains.onRootViewsChangedListeners += listener } override fun uninstall() { Curtains.onRootViewsChangedListeners -= listener } }Copy the code

1.4 ServiceWatcher

ServiceDoneExecuting is a memory leak check when AMS calls serviceDoneExecuting.

class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher { ... override fun install() { ... Try {/ / hook ActivityThread H class in the Callback swapActivityThreadHandlerCallback {mCallback - > uninstallActivityThreadHandlerCallback = { swapActivityThreadHandlerCallback { mCallback } } Callback {MSG -> if (MSG. What == STOP_SERVICE) {val key = MSG. Obj as IBinder activityThreadServices[key]? .let { onServicePreDestroy(key, it) } } mCallback? .handleMessage(msg) ? : False hook the AMS swapActivityManager {}} / / here activityManagerInterface, activityManagerInstance -> uninstallActivityManager = { swapActivityManager { _, > activityManagerInstance > activityManagerInstance > activityManagerInstance > activityManagerInstance > activityManagerInstance > activityManagerInstance activityManagerInterface.classLoader, arrayOf(activityManagerInterface) ) { _, method, args -> if (METHOD_SERVICE_DONE_EXECUTING == method.name) { val token = args!! [0] as IBinder if (servicesToBeDestroyed.containsKey(token)) { onServiceDestroyed(token) } } try { if (args == null) { method.invoke(activityManagerInstance) } else { method.invoke(activityManagerInstance, *args) } } catch (invocationException: InvocationTargetException) { throw invocationException.targetException } } } } catch (ignored: Throwable) { SharkLog.d(ignored) { "Could not watch destroyed services" } } } override fun uninstall() { checkMainThread() uninstallActivityManager? .invoke() uninstallActivityThreadHandlerCallback? .invoke() uninstallActivityManager = null uninstallActivityThreadHandlerCallback = null } // 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" ) } } } ... Omitted hook code part}Copy the code

2. Find the leaking object -ObjectWatcher

ObjectWatcher’s ExpectlyWeaklyreachable method is used to look for leaks when the object is recycled at the right time

@Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String) {/ / move unless the leak of a weak reference object removeWeaklyReachableObjects () / / for this object generates only random id val key = UUID. RandomUUID (), toString () val watchUptimeMillis = clock.uptimeMillis() val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, Queue) / / the object weakRefrence wrapped object Then join the queue watchedObjects [key] = after reference / / 5 s weak references remain have leakage risk checkRetainedExecutor. Execute {  moveToRetained(key) } }Copy the code

Next, detect the leaking object

private fun checkRetainedObjects() { ... Var retainedReferenceCount = objectWatcher. RetainedObjectCount manually GC / / there is leaking objects once Then test again if (retainedReferenceCount > 0) { GcTrigger. RunGc () retainedReferenceCount = objectWatcher. RetainedObjectCount} / / if leakage quantity does not exceed threshold value (the default) end of the if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) 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(  delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis ) return } dismissRetainedCountNotification() Val visibility = if (applicationVisible) "visible" else "not visible" //dump file Through native Debug. DumpHprofData (heapDumpFile. AbsolutePath); dumpHeap( retainedReferenceCount = retainedReferenceCount, retry = true, reason = "$retainedReferenceCount retained objects, app is $visibility" ) }Copy the code

This step does not trigger dump to generate hprof file analysis immediately. Instead, GC is triggered to collect objects and determine the number of uncollected objects again. If there are more than five objects, hprof file is generated in native mode and analyzed by HeapAnalyzerService. HeapAnalyzerService belongs to shark, another open source library of Square. It is a foreground service. The whole process analyzes files by analyzeHeap method. Wrap the result of the analysis into a HeapAnalysisSuccess object with the onHeapAnalyzedListener callback.

3. Analysis the Hprof

The standard protocol of hprof file is mainly composed of head and body. Body is composed of a series of different types of records. Record is mainly used to describe trace, Object, thread and other information, which is divided into four parts in turn: TAG, TIME, LENGTH, BODY, where the TAG is the Record type. The hprof file is arranged or nested among the records.

Multiple records are abstracted into HprofMemoryIndex, which can quickly locate the location of the corresponding object in the hprof file. Finally, Index and Hprof are combined to form HprofGraph. Graph, as the top description of Hprof, abstracts all data in the heap to gcRoots, objects, classes, instances and other collections.

interface HeapGraph {
  val gcRoots: List<GcRoot>

  val objects: Sequence<HeapObject>

  val classes: Sequence<HeapClass>

  val instances: Sequence<HeapInstance>

  val objectArrays: Sequence<HeapObjectArray>

  ...
}
Copy the code

Then find out the leaking object through the Objects collection of Graph, and find out its reference path chain to GcRoot through breadth-first traversal, and end the process.

To conclude/To conclude

LeakCanary is registered through ContentProvider, and when the object is destroyed, WeakRefrence + RefrenceQueue + GC is used to observe the leaked object, dump the hprof file, analyze the file through the shark library, and find the reference path of the leaked object.