A brief introduction and use of LeakCanary

1 Introduction:

LeakCanray is Square's open source Java memory leak analysis tool, which is used to detect common memory leaks in Android applications during the development phase.

  • It supports memory leak monitoring in the following five Android scenarios:
    1. Destroyed Activity object (enters DESTROYED state)
    2. Destroyed Fragment objects and Fragment View objects (entered DESTROYED state)
    3. Cleared ViewModel object (enters CLEARED state)
    4. Destroyed Service object (enters DESTROYED state)
    5. RootView object removed from WindowManager

1.2 Use

  • Just add the LeakCanary dependency in build.gradle (LeakCanary uses ContentProvider by default to achieve non-intrusive initialization). Note: LeakCanary is a tool only used in the development environment, so remember to use debugImplementationto configure dependencies
// 普通ContentProvider方式
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'

// 可选,使用WorkerManager多进程分析堆快照提升分析速度
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.12'

2. Source code analysis

2.1 Initialization

  • LeakCanary in the 1.x era required adding initialization code to Application.onCreate(). After 2.x, LeakCanary used the startup ContentProvidermechanism to indirectly call the initialization API, achieving non-invasive LeakCanary initialization.
  • The specific location is in the submodule leakcanary-object-watcher-android

 <application>
   <!--可以通过配置leak_canary_watcher_auto_install变量设置是否自行初始化,默认为true -->
   <provider
      android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
      android:authorities="${applicationId}.leakcanary-installer"
      android:enabled="@bool/leak_canary_watcher_auto_install"
      android:exported="false"/>

 </application>
internal class MainProcessAppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // LeakCanary初始化代码
    AppWatcher.manualInstall(application)
    return true
  }
  ...
}

 The real initialization code isAppWatcher.manualInstall(application)

// AppWatcher.kt
/** LeakCanary初始化 */
@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 默认5s后进行泄漏检测
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  checkMainThread()
  ...
  // 初始化 InternalLeakCanary 内部引擎
  LeakCanaryDelegate.loadLeakCanary(application)
  // 遍历五种监听器进行分别注册
  watchersToInstall.forEach {
    it.install()
  }
  ...
}

/** 创建监听集合 */
fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    // 对应5中Android泄漏场景(Activity、Fragment和ViewModel、View、Service)
    // 传入的reachabilityWatcher均为ReachabilityWatcher的唯一实现类ObjectWatcher
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

 It is divided into four monitoring categories to monitor Activity, Fragment, ViewModel, RootView and Service respectively. Next, let’s analyze how these four categories are specifically monitored.

2.2 Four major monitoring categories realize monitoring of five leakage scenarios

2.2.1 ActivityWatcher

  • ActivityWatcher mainly realizes the monitoring of Activity
/** Activity的泄漏监听 */
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

  override fun install() {
    // 注册监听的方式
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

  • Activity monitoring is relatively simple. You can directly use the provided global monitoring API application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback), and then hand the activity to objectWatcher for analysis in onDestroy of each Activity.

2.2.2 FragmentAndViewModelWatcher

  • FragmentAndViewModelWatcher mainly implements monitoring of Fragment, Fragment view and ViewModel
/**
 * 主要负责Fragment的泄漏监听(通过Fragment.onDestroy())
 * Fragment View的泄漏监听(通过Fragment.onDestroyView())
 * ViewModel的泄漏监听(通过ViewModel.onCleared())
 */
class FragmentAndViewModelWatcher(
    private val application: Application,
    private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

    /** Fragment监测集合(主要包含几种包下的Fragment) */
    // 集合的类型是(Activity)->Unit的函数类型
    private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
        val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

        // Android O(8.0)后使用AndroidOFragmentDestroyWatcher监听
        if (SDK_INT >= O) {
            fragmentDestroyWatchers.add(
                AndroidOFragmentDestroyWatcher(reachabilityWatcher)
            )
        }

        // androidx包下的Fragment使用AndroidXFragmentDestroyWatcher监听
        getWatcherIfAvailable(
            ANDROIDX_FRAGMENT_CLASS_NAME,
            ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
            reachabilityWatcher
        )?.let {
            fragmentDestroyWatchers.add(it)
        }

        // android.support.v4包下的Fragment使用AndroidSupportFragmentDestroyWatcher监听
        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?
            ) {
                // 监听每个Activity的onActivityCreated(),再在其中进行Fragment监听
                for (watcher in fragmentDestroyWatchers) {
                    // 由于fragmentDestroyWatchers里面本身存储的是一个(Activity)->Unit的函数类型
                    // 所以这里可以直接使用watcher(activity),直接调用,参数为activity,
                    // 实际执行的是watcher中的invoke()
                    watcher(activity)
                }
            }
        }

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

    override fun uninstall() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
    }
    ...
}

  • Fragment and Fragment View monitoring is mainly FragmentAndViewModelWatcherimplemented through. First, it Application.registerActivityLifecycleCallbacks(…)monitors the Activity.onCreate() event through the interface, and then FragmentManager.registerFragmentLifecycleCallbacks(…)monitors the life cycle of Fragment through the interface.
  • There are three states according to the Android version and Fragment package, namely AndroidOFragmentDestroyWatcher, AndroidXFragmentDestroyWatcher, AndroidSupportFragmentDestroyWatcher. Here we take AndroidXFragmentDestroyWatcher as an example.
/** Androidx包下的Fragment、FragmentView以及ViewModel监听 */
internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {

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

    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      // 注册Fragment级别的ViewModel Hook
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null) {
        // 监听FragmentView.onDestroy()将Fragment.View交给ObjectWatcher分析
        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
    ) {
      // 监听Fragment.onDestroy()将Fragment交给ObjectWatcher分析
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

  override fun invoke(activity: Activity) {
    // 这段代码会在Activity.onCreate()中执行
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      // 注册Fragment生命周期监听
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 注册Activity级别的ViewModel Hook
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

  • This also includes the monitoring logic of ViewModel, which is used in Activity.onCreate() and Fragment.onCreate() ViewModelClearedWatcherfor monitoring. Since the system does not provide a method for global monitoring of ViewModel, ViewModelClearedWatcher is Hookimplemented through methods. Let’s take a look. ViewModelClearedWatcher
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  // 直接通过反射获取ViewModelStore类中的map变量(后面改成mMap),即可获得作用域中的所有ViewModel对象
  private val viewModelMap: Map<String, ViewModel>? = try {
    val storeClass = ViewModelStore::class.java
    val mapField = try {
      storeClass.getDeclaredField("map")
    } catch (exception: NoSuchFieldException) {
      storeClass.getDeclaredField("mMap")
    }
    mapField.isAccessible = true
    @Suppress("UNCHECKED_CAST")
    mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
    null
  }

  override fun onCleared() {
    // 遍历当前作用域所有ViewModel对象
    viewModelMap?.values?.forEach { viewModel ->
      // 使用ObjectWatcher.expectWeaklyReachable
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    // 在storeOwner作用域实例化ViewModelClearedWatcher对象
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          // 直接在storeOwner作用域实例化ViewModelClearedWatcher对象
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

  • It can be seen that the monitoring of ViewModel is to create a custom ViewModel (i.e. ViewModelClearedWatcher) in Activity.onCreate()/Fragment.onCreate(), then listen to the onCleared() of ViewModelClearedWatcher, and obtain the corresponding response by Hook ViewModelStore. All ViewModel objects in the domain (Activity/Fragment) and finally handed over to ObjectWatcher for monitoring

2.2.3 RootViewWatcher

  • RootViewWatcher is a listener that handles RootView memory leaks. Since the system does not provide global RootView monitoring, LeakCannary is still handled through Hook. However, instead of hooking itself, Curtains, another open source library of Square, is used to handle it. , mainly through Hook WMS service internal WindowManagerGlobal(sdk>16) or WindowManagerImpl(sdk<=16) to obtain the timing of adding or removing all RootViews
/**
 * RootView泄漏监听,主要利用了Curtains库实现对
 */
class RootViewWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val listener = OnRootViewAddedListener { rootView ->
    // 判断rootView的窗口类型
    // 是否需要使用RootViewWatcher监听
    val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
          // 由于Activity已经有ActivityWatcher监听,这里直接返回false,即无需通过RootViewWatcher监听
          is Activity -> false
          is Dialog -> {
            // Use app context resources to avoid NotFoundException
            // https://github.com/square/leakcanary/issues/2137
            // 通过配置开启,默认不开启
            val resources = rootView.context.applicationContext.resources
            resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          }
          // Probably a DreamService
          // 屏保等
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      POPUP_WINDOW -> false
      // Tooltip、Toast等进行监听
      TOOLTIP, TOAST, UNKNOWN -> true
    }
    if (trackDetached) {
      // 注册监听事件
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          // 接收发送的消息,使用ObjectWatcher对rootView进行监听
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          // 添加时移除消息
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          // 监听RootView的移除事件,使用Handler发送消息处理
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install() {
    // 注册RootView监听
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}

  • Mainly for Dialog(有监听开关配置)、DreamService、Tooltip、Toastmonitoring other types of RootView, by calling the rootView.addOnAttachStateChangeListenermonitoring onViewDetachedFromWindowmethod, monitoring the removal event of RootView, and handing over the RootView to ObjectWatcher for monitoring in the removal event.

2.2.4 ServiceWatcher

  • ServiceWatcher is a listener that handles Service memory leaks. The system does not provide global Service removal monitoring, so LeaakCanary is also implemented through Hook.
@SuppressLint("PrivateApi")
class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {

  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>
  }

  private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null
  private var uninstallActivityManager: (() -> Unit)? = null

  override fun install() {
    checkMainThread()
    check(uninstallActivityThreadHandlerCallback == null) {
      "ServiceWatcher already installed"
    }
    check(uninstallActivityManager == null) {
      "ServiceWatcher already installed"
    }
    try {
      // Hook ActivityThread类中的mH.mCallback
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        Handler.Callback { msg ->
          if (msg.obj !is IBinder) {
            return@Callback false
          }

          // 监听Service.onStop()事件消息
          if (msg.what == STOP_SERVICE) {
            val key = msg.obj as IBinder
            // activityThreadServices是通过反射获取的ActivityThread类中的mServices成员变量<IBinder, Service>
            activityThreadServices[key]?.let {
              // 服务销毁前的处理,这里主要是暂存
              onServicePreDestroy(key, it)
            }
          }
          // Hook后继续执行Framework本身的逻辑
          mCallback?.handleMessage(msg) ?: false
        }
      }

      // Hook AMS IActivityManager
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          // 代理serviceDoneExecuting()方法
          if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
            val token = args!![0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              // 处理Service销毁,主要是将service交给ObjectWatcher进行监控
              onServiceDestroyed(token)
            }
          }
          // 继续执行serviceDoneExecuting()本身的方法
          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 onServicePreDestroy(
    token: IBinder,
    service: Service
  ) {
    servicesToBeDestroyed[token] = WeakReference(service)
  }

  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"
        )
      }
    }
  }

  /**
   * Hook修改ActivityThread类中的mH.mCallback
   * swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象
   */
  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 }
    val mCallback = mCallbackField[mH] as Handler.Callback?
    mCallbackField[mH] = swap(mCallback)
  }

  /**
   * Hook修改ActivityThread类中的mH.mCallback
   * swap 是一个 lambda 表达式,参数为 IActivityManager 的 Class 对象和接口原实现对象,返回值为注入的新对象
   */
  @SuppressLint("PrivateApi")
  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]

    val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)

    val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
    // 将swap的返回值作为新对象,实现 Hook
    mInstanceField[activityManagerSingletonInstance] =
      swap(iActivityManagerInterface, activityManagerInstance!!)
  }

  companion object {
    private const val STOP_SERVICE = 116

    private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
  }
}

  • The general steps of Hook are:
    • Hook the callback of the main thread message loop mH.mCallback, monitor STOP_SERVICEthe messages, and temporarily store the Service object that is about to be destroyed (since there is no DESTROY_SERVICE message in ActivityThread.H, the onDestroy() event cannot be directly monitored, and the following steps are required);
    • Use the dynamic proxy Hook AMS to communicate with the Binder object of the App IActivityManager, and proxy the methods in it serviceDoneExecuting(). This is regarded as the execution time of Service.onDestroy(), and the temporary Service object is obtained and handed over to ObjectWatcher for monitoring.

2.3 How ObjectWatcher monitors memory leaks

2.3.1 Java’s four major reference related knowledge

  • strong reference

    • The most common reference is that when the object has a strong reference and is not nulled, the virtual machine will not recycle it even if it goes OOM.
  • soft reference

    • When the memory is sufficient, the virtual machine will not recycle it. When the memory is insufficient, it will be automatically recycled. It is often used for image caching.
    SoftReference<String> softReference = new SoftReference<>(str);
    
    
  • weak quotation

    • Regardless of whether the memory is sufficient, as long as you manually call garbage collection System.gc() or wait for the virtual machine to automatically GC, weak references will be recycled. Mainly used to place in locations that are prone to memory leaks, such as Handler in Android
    WeakReference<String> weakReference = new WeakReference<>(str);
    
    
  • virtual reference

    • Virtual references do not determine the life cycle of the object. If an object holds only phantom references, then it may be reclaimed by the garbage collector at any time as if it had no references. Mainly used to track the activity of objects being recycled by the garbage collector, created using PhantomReference
  • ReferenceQueueReferenceQueue

    • When gc is ready to recycle an object, if it finds that it only has a soft reference (or weak reference, or virtual reference) pointing to it, it will add this soft reference (or weak reference, or virtual reference) before recycling the object. to the reference queue (ReferenceQueue) associated with it. If a soft reference (or weak reference, or virtual reference) object itself is in the reference queue, it means that the object pointed to by the reference object has been recycled;
// 构造一个强引用  
Object obj = new Object();  
// 创建引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();  
// 利用强引用和引用队列构造弱引用  
WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue);  
System.out.println("调用GC前弱引用:" + weakReference.get()); // java.lang.Object@6108b2d7  
System.out.println("调用GC前引用队列:" + referenceQueue.poll()); // null  
  
// 将强引用手动置null  
obj = null;  
// 手动GC或虚拟机自动GC都会回收弱引用,这里手动调用GC  
System.gc();  
  
System.out.println("调用GC后弱引用:" + weakReference.get()); // null  
System.out.println("调用GC后引用队列:" + referenceQueue.poll()); // java.lang.Object@6108b2d7

2.3.2 ObjectWatcher object monitoring

class ObjectWatcher constructor(
    private val clock: Clock,
    private val checkRetainedExecutor: Executor,
    private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

    private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

    /** 被监控对象的映射表 */
    private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

    /** 弱引用引用队列,与KeyedWeakReference相关联,对象正常销毁会存在这里面 */
    private val queue = ReferenceQueue<Any>()

    ...
    /** 监控对象泄漏 */
    @Synchronized
    override fun expectWeaklyReachable(
        watchedObject: Any,
        description: String
    ) {
        if (!isEnabled()) {
            return
        }
        // 移除watchedObjects中未泄漏的对象
        removeWeaklyReachableObjects()
        // 构造KeyedWeakReference 引用对象
        val key = UUID.randomUUID()
            .toString()
        val watchUptimeMillis = clock.uptimeMillis()
        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
        // 默认5秒后执行检查
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }
    ...

    @Synchronized
    private fun moveToRetained(key: String) {
        removeWeaklyReachableObjects()
        // 移除watchedObjects中未泄露的对象后剩余的判定为发生泄漏
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            // 回调通知LeakCanary内部处理
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    /** 移除队列中未泄漏的对象 */
    private fun removeWeaklyReachableObjects() {
        var ref: KeyedWeakReference?
        do {
            ref = queue.poll() as KeyedWeakReference?
            if (ref != null) {
                watchedObjects.remove(ref.key)
            }
        } while (ref != null)
    }
}

/** 弱引用包装类 */
class KeyedWeakReference(
  /** 被监控对象 */
  referent: Any,
  /** 映射表的Key */
  val key: String,
  /** 描述 */
  val description: String,
  /** 监控开始时间(引用创建时间) */
  val watchUptimeMillis: Long,
  /** 关联的引用队列 */
  referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
  referent, referenceQueue
) {
  /** 判定对象为泄漏对象的时间,-1表示非泄漏对象或还未判定完毕 */
  @Volatile
  var retainedUptimeMillis = -1L

  override fun clear() {
    super.clear()
    retainedUptimeMillis = -1L
  }

  companion object {
    /** 记录最近一次触发Heap Dump的时间 */
    @Volatile
    @JvmStatic var heapDumpUptimeMillis = 0L
  }
}

  • Mainly went through three steps
  1. watchedObjectCreate a weak reference for the monitored object KeyedWeakReferenceand store it in the mapping table of <UUID, KeyedWeakReference>;
  2. PostDelay checks whether the reference object appears in the reference queue after five seconds. If it appears in the queue, it means that the monitored object has not leaked. Subsequently, the non-leaked records in the mapping table are removed, and retainedUptimeMillisthe fields of the leaked reference objects are updated to mark them as leaked.
  3. onObjectRetainedNotify LeakCanary that a new memory leak has occurred internally through a callback

2.4 Dump heap to obtain memory snapshot file

  • After ObjectWatcher determines that the monitored object has leaked, it will OnObjectRetainedListener.onObjectRetained()call back to the internal manager of LeakCanary through the interface method.InternalLeakCanary
  • Since the analysis work is time-consuming, LeakCanary will naturally not perform analysis work every time it finds a memory leak object, but will perform two interceptions:
  • 1. The count of leaked objects does not reach the threshold, or the time to enter the background does not reach the threshold.
  • 2. The calculation distance from the last HeapDump has not exceeded 60 seconds.
// InternalLeakCanary.kt
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}

// HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  // 避免重复postDelayed
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    checkRetainedObjects()
  }, delayMillis)
}

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

  val config = configProvider()

  if (iCanHasHeap is Nope) {
    if (iCanHasHeap is NotifyingNope) {
      // Before notifying that we can't dump heap, let's check if we still have retained object.
      // 泄漏计数
      var retainedReferenceCount = objectWatcher.retainedObjectCount
      // 泄漏计数>0时主动调用GC
      if (retainedReferenceCount > 0) {
        // 这个方法是调用RunTime.getRuntime().GC()并休眠等待100ms
        gcTrigger.runGc()
        // GC后再获取泄漏计数
        retainedReferenceCount = objectWatcher.retainedObjectCount
      }

      val nopeReason = iCanHasHeap.reason()
      // 这里会判断泄漏计数是否>5(默认阈值)
      val wouldDump = !checkRetainedCount(
        retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
      )

      if (wouldDump) {
        val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
        // 回调onEvent
        onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
        // 展示通知
        showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = uppercaseReason
        )
      }
    } else {
      SharkLog.d {
        application.getString(
          R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
        )
      }
    }
    return
  }

  var retainedReferenceCount = objectWatcher.retainedObjectCount

  if (retainedReferenceCount > 0) {
    gcTrigger.runGc()
    retainedReferenceCount = objectWatcher.retainedObjectCount
  }

  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  // 计算距离上一次HeapDump时间未超过60s会拦截
  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"
  // 触发dumpHeap分析
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

2.5 Analyze heap snapshot dumpHeap

  • LeakCanary has successfully generated .hprofa heap snapshot file and sent a LeakCanary internal event HeapDump. Multiple event consumers EventListener are set up in LeakCanary's configuration items. These three will select the optimal execution strategy based on the current dependencies of the App:
  • 1 - WorkerManager multi-process analysis
  • 2 - WorkManager Asynchronous Analysis
  • 3 - Asynchronous thread analysis (cover-up strategy)
// LeakCanary.kt
val eventListeners: List<EventListener> = listOf(
  LogcatEventListener,
  ToastEventListener,
  LazyForwardingEventListener {
    if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
  },
  when {
     // WorkManager多进程分析
      RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
        RemoteWorkManagerHeapAnalyzer
      // WorkManager 异步分析
      WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
      // 异步线程分析(兜底策略)
      else -> BackgroundThreadHeapAnalyzer
  }
),

2.5.1 WorkerManager multi-process analysis

object RemoteWorkManagerHeapAnalyzer : EventListener {

  private const val REMOTE_SERVICE_CLASS_NAME = "leakcanary.internal.RemoteLeakCanaryWorkerService"

  // 这里通过RemoteLeakCanaryWorkerService这个类是否加载成功来判断
  // 是否有'com.squareup.leakcanary:leakcanary-android-process:2.9.1'这个依赖
  internal val remoteLeakCanaryServiceInClasspath by lazy {
    try {
      Class.forName(REMOTE_SERVICE_CLASS_NAME)
      true
    } catch (ignored: Throwable) {
      false
    }
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val application = InternalLeakCanary.application
      // 创建并分发 WorkManager 多进程请求
      val heapAnalysisRequest =
        OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply {
          val dataBuilder = Data.Builder()
            .putString(ARGUMENT_PACKAGE_NAME, application.packageName)
            .putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME)
          setInputData(event.asWorkerInputData(dataBuilder))
          with(WorkManagerHeapAnalyzer) {
            addExpeditedFlag()
          }
        }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val workManager = WorkManager.getInstance(application)
      workManager.enqueue(heapAnalysisRequest)
    }
  }
}

internal class RemoteHeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) :
  RemoteListenableWorker(appContext, workerParams) {

  override fun startRemoteWork(): ListenableFuture<Result> {
    val heapDump = inputData.asEvent<HeapDump>()
    val result = SettableFuture.create<Result>()
    heapAnalyzerThreadHandler.post {
      // 分析堆快照
      val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = {
        result.isCancelled
      }) { progressEvent ->
        if (!result.isCancelled) {
          // 发送分析进度事件
          InternalLeakCanary.sendEvent(progressEvent)
        }
      }
      if (result.isCancelled) {
        SharkLog.d { "Remote heap analysis for ${heapDump.file} was canceled" }
      } else {
        // 发送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
        result.set(Result.success())
      }
    }
    return result
  }

  override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
    return applicationContext.heapAnalysisForegroundInfoAsync()
  }
}

2.5.2 WorkManager asynchronous analysis

object WorkManagerHeapAnalyzer : EventListener {

  // 判断是否包含WorkManager
  internal val validWorkManagerInClasspath by lazy {
    try {
      Class.forName("androidx.work.WorkManager")
      val dataBuilderClass = Class.forName("androidx.work.Data$Builder")
      dataBuilderClass.declaredMethods.any { it.name == "putByteArray" }.apply {
        if (!this) {
          SharkLog.d { "Could not find androidx.work.Data$Builder.putByteArray, WorkManager should be at least 2.1.0." }
        }
      }
    } catch (ignored: Throwable) {
      false
    }
  }

  // setExpedited() requires WorkManager 2.7.0+
  private val workManagerSupportsExpeditedRequests by lazy {
    try {
      Class.forName("androidx.work.OutOfQuotaPolicy")
      true
    } catch (ignored: Throwable) {
      false
    }
  }

  internal fun OneTimeWorkRequest.Builder.addExpeditedFlag() = apply {
    if (workManagerSupportsExpeditedRequests) {
      setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    }
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply {
        setInputData(event.asWorkerInputData())
        addExpeditedFlag()
      }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val application = InternalLeakCanary.application
      WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
    }
  }
}

internal class HeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) :
  Worker(appContext, workerParams) {
  override fun doWork(): Result {
    // 分析堆快照
    val doneEvent =
      AndroidDebugHeapAnalyzer.runAnalysisBlocking(inputData.asEvent()) { event ->
        // 发送分析进度事件
        InternalLeakCanary.sendEvent(event)
      }
    // 发送分析完成事件
    InternalLeakCanary.sendEvent(doneEvent)
    return Result.success()
  }
 ...
}

2.5.3 Common asynchronous thread analysis BackgroundThreadHeapAnalyzer

object BackgroundThreadHeapAnalyzer : EventListener {

  internal val heapAnalyzerThreadHandler by lazy {
    val handlerThread = HandlerThread("HeapAnalyzer")
    handlerThread.start()
    Handler(handlerThread.looper)
  }

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      heapAnalyzerThreadHandler.post {
        // 分析堆快照
        val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
          // 发送分析进度事件
          InternalLeakCanary.sendEvent(event)
        }
        // 发送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
      }
    }
  }
}

  • AndroidDebugHeapAnalyzer.runAnalysisBlockingIt can be seen that among the three methods, LeakCanary analyzes the heap snapshot through the sub-thread or sub-process execution method, and sends callback events during the analysis process and after the analysis is completed.
  • The heap snapshot analysis is ultimately completed by the HeapAnalizer in Shark. The analysis will not be carried out here. The core process is:
  • 1. Search for leaked objects in the heap snapshot. The default is to search for KeyedWeakReference type objects;
  • 2. Analyze the shortest reference chain of the KeyedWeakReference object, group it according to the reference chain signature, and classify it according to Application Leaks and Library Leaks;
  • 3. Return the analysis completion event.

3. Summary

  1. Initialize using ContentProvider
  2. Register for monitoring of 5 Android leak scenarios
    • Activity uses application.registerActivityLifecycleCallbacks (ActivityLifecycleCallbacks callback) to monitor the global Activity life cycle
    • Fragment first Application.registerActivityLifecycleCallbacks(…)listens to the Activity.onCreate() event through the interface, and then FragmentManager.registerFragmentLifecycleCallbacks(…)listens to the life cycle of the Fragment through the interface.
    • ViewModel is Hookimplemented through ViewModelStore
    • Service is implemented through Hook mH.mCallbackcallbacks and monitoring messages.STOP_SERVICE
    • WindowManagerGlobalRootView is implemented internally by using the Curtains library to Hook the WMS service.
  3. After receiving the destruction callback, create a KeyedWeakReference based on the object to be recycled and associate it with the ReferenceQueue. Delay for 5 seconds to check whether the relevant object has been recycled. If not, start the service and dump heap to obtain the memory snapshot file .hprof.
  4. Parse the file through the Shark library .hprof, obtain the leaked object, and calculate the shortest path from the leaked object to GC roots.
  5. Merge multiple leak paths and output analysis results, and display the results to the visual interface

Guess you like

Origin blog.csdn.net/qq_25462179/article/details/132760099