LeakCanary 2.0(二)源码分析及总结

799 阅读15分钟

LeakCanary 系列文章:

LeakCanary 2.0(一)工作原理及使用详解

LeakCanary 2.0(二)源码分析及总结

一 LeakCanary 简介

LeakCanary 是一款 Android 平台上进行内存泄漏检测的工具,其简介及使用方法可参考 LeakCanary 2.0 工作原理及使用详解 。本文主要从源码角度来分析其工作流程。

二 源码分析

LeakCanary 工作流程大致可分为以下 6 个阶段:

  1. 初始化: 初始化 LeakCanary 内部分析引擎
  2. 注册垃圾对象的监听: 在 Android Framework 中注册监听器,感知五种 Android 内存泄漏场景中产生垃圾对象的时机
  3. 监控内存泄漏: 为垃圾对象关联弱引用对象,若一段时间后引用对象没有按预期进入引用队列,则认为对象发生内存泄漏
  4. Java Heap Dump: 当泄漏对象计数达到阈值时,会触发 Java Heap Dump 并生成.hprof 文件存储到文件系统中
  5. 分析堆快照: 使用 Shark 分析.hprof文件
  6. 输出分析报告: 分析工作完成后,会在 Logcat 打印分析结果,也会发送一条系统通知消息

2.1 初始化

旧版本的 LeakCanary 需要在 Application 中调用相关初始化 API,而在 LeakCanary 2.0 利用了 ContentProvider 的启动机制来间接调用初始化 API, 实现了无侵入的 LeakCanary 初始化。

在项目工程 leakcanary-object-watcher-android 的 AndroidManifext.xml文件中,注册了一个继承自 ContentProvider 的 MainProcessAppWatcherInstaller。应用启动时,会先调用注册的 ContentProvider 的 onCreate 完成初始化

AndroidManifext.xml

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

在 MainProcessAppWatcherInstaller 类的 onCreate 方法中,实际是 AppWatcher.manualInstall(application) 完成了 LeakCanary 的初始化。 MainProcessAppWatcherInstaller.kt

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

AppWatcher.kt

@JvmOverloads
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  // 确保在主线程,否则抛出 UnsupportedOperationException 异常
  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"
  }
  this.retainedDelayMillis = retainedDelayMillis
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // 初始化 InternalLeakCanary 内部引擎
  LeakCanaryDelegate.loadLeakCanary(application)
  // 注册 5 种 Android 泄漏场景的监控 Hook 点
  watchersToInstall.forEach {
    it.install()
  }
  // Only install after we're fully done with init.
  installCause = RuntimeException("manualInstall() first called here")
}

LeakCanary 的初始化工程可以概括为 2 项内容:

  • 初始化 LeakCanary 内部分析引擎;
  • 在 Android Framework 上注册 5 种 Android 泄漏场景的监控。

2.2 注册 5 种 Android 泄漏场景的监控

在初始过程中,对应 5 种场景的内存泄露监控

AppWatcher.kt

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

Activity 回收监控

ActivityWatcher 类中 通过 Application#registerActivityLifecycleCallbacks(…) 接口监听 Activity#onDestroy 事件;

ActivityWatcher.kt

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

Fragment 与 Fragment View 回收监控:

FragmentAndViewModelWatcher 类中通过 Application#registerActivityLifecycleCallbacks(…) 接口监听 Fragment 的生命周期:

FragmentAndViewModelWatcher.kt

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

再来看 FragmentAndViewModelWatcher 生命回调的处理: FragmentAndViewModelWatcher.kt

private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) {
      for (watcher in fragmentDestroyWatchers) {
        //实际调用的是对应的 invoke 方法
        watcher(activity)
      }
    }
  }

FragmentAndViewModelWatcher.kt

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  // fragmentDestroyWatchers 列表,支持不同 Fragment 实例的检测;
  // 这里的 watcher 都继承自(Activity)->Unit 表示方法类型/函数类型,
  // 参数为 Activity,返回值为空;因为是方法类型所以需要重写 invoke 方法
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
  //Android O 后构建 AndroidOFragmentDestroyWatcher
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
  // 如果 Class.for(className)能找到 androidx.fragment.app.Fragment 和
  // leakcanary.internal.AndroidXFragmentDestroyWatcher 则添加 AndroidXFragmentDestroyWatcher 则添加
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  //如果 Class.for(className)能找到 android.support.v4.app.Fragment 和
  //leakcanary.internal.AndroidSupportFragmentDestroyWatcher 则添加 AndroidSupportFragmentDestroyWatcher
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}

以 AndroidX Fragment 为例,AndroidXFragmentDestroyWatcher 的 invoke 方法实现: AndroidXFragmentDestroyWatcher.kt

override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      //取得对应的 FragmentManager,注册生命周期回调
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      //添加了 ViewModelStoreOwner 为 Activity 的 ViewModelClearedWatcher 监测
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }

LeakCanary 在 onFragmentDestroyed 回调里面来处理检查 Fragment 是否正常被回收的检测逻辑。

AndroidXFragmentDestroyWatcher.kt

override fun onFragmentDestroyed(
  fm: FragmentManager,
  fragment: Fragment
) {
  reachabilityWatcher.expectWeaklyReachable(
    fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
  )
}

LeakCanary 在 onFragmentViewDestroyed 回调里面来处理检查 Fragment 的 View 是否正常被回收的检测逻辑。

AndroidXFragmentDestroyWatcher.kt

override fun onFragmentViewDestroyed(
  fm: FragmentManager,
  fragment: Fragment
) {
  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)"
    )
  }

ViewModel 监控

由于 Android Framework 未提供设置 ViewModel#onClear() 全局监听的方法,所以 LeakCanary 是通过 Hook 的方式实现。即:在 Activity#onCreateFragment#onCreate 事件中实例化一个自定义 ViewModel,在进入 ViewModel#onClear() 方法时,通过反射获取当前作用域中所有的 ViewModel 对象交给 ObjectWatcher 监控。

ViewModelClearedWatcher.kt

// ViewModel 的子类
internal class ViewModelClearedWatcher(
    storeOwner: ViewModelStoreOwner,
    private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

    // 反射获取 ViewModelStore 中的 ViewModel 映射表,即可获取当前作用域所有 ViewModel 对象
    private val viewModelMap: Map<String, ViewModel>? = try {
        val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
        mMapField.isAccessible = true
        mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
    } catch (ignored: Exception) {
        null
    }

    override fun onCleared() {
        // 遍历当前作用域所有 ViewModel 对象
        viewModelMap?.values?.forEach { viewModel ->
            // reachabilityWatcher 即 ObjectWatcher
            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 {
                override fun <T : ViewModel?> create(modelClass: Class<T>): T =
                    ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
            })
            provider.get(ViewModelClearedWatcher::class.java)
        }
    }
}

RootView 监控

由于 Android Framework 未提供设置全局监听 RootView 从 WindowManager 中移除的方法,所以 LeakCanary 是通过 Hook 的方式实现的,这一块是通过 squareup 另一个开源库 curtains 实现的。RootView 监控这部分源码也比较复杂了,需要通过 2 步 Hook 来实现:

  • Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表,获取 RootView 新增和移除的时机;
  • 检查 View 对应的 Window 类型,如果是 Dialog 或 DreamService 等类型,则在注册 View#addOnAttachStateChangeListener() 监听,在其中的 onViewDetachedFromWindow() 回调中将 View 对象交给 ObjectWatcher 监控。

LeakCanary 源码摘要如下:

RootViewWatcher.kt

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

private val listener = OnRootViewAddedListener { rootView ->
    val trackDetached = when(rootView.windowType) {
    PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
            // Activity 类型已经在 ActivityWatcher 中监控了,不需要重复监控
            is Activity -> false
            is Dialog -> {
                // leak_canary_watcher_watch_dismissed_dialogs:Dialog 监控开关
                val resources = rootView.context.applicationContext.resources
                resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
            }
            // DreamService 屏保等
            else -> true
        }
    }
    POPUP_WINDOW -> false
    TOOLTIP, TOAST, UNKNOWN -> true
    }
    if (trackDetached) {
        // 2. 注册 View#addOnAttachStateChangeListener 监听
        rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
            val watchDetachedView = Runnable {
                // 3. 交给 ObjectWatcher 监控
                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)
            }
        })
    }
}

curtains 源码摘要如下:

RootViewsSpy.kt

private val delegatingViewList = object : ArrayList<View>() {
    // 重写 ArrayList#add 方法
    override fun add(element: View): Boolean {
        // 回调
        listeners.forEach { it.onRootViewsChanged(element, true) }
        return super.add(element)
    }

    // 重写 ArrayList#removeAt 方法
    override fun removeAt(index: Int): View {
        // 回调
        val removedView = super.removeAt(index)
        listeners.forEach { it.onRootViewsChanged(removedView, false) }
        return removedView
    }
}

companion object {
    fun install(): RootViewsSpy {
        return RootViewsSpy().apply {
            WindowManagerSpy.swapWindowManagerGlobalMViews { mViews /*原对象*/ ->
                // 新对象(lambda 表达式的末行就是返回值)
                delegatingViewList.apply { addAll(mViews) }
            }
        }
    }
}

WindowManageSpy.kt

// Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表
// swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象
fun swapWindowManagerGlobalMViews(swap: (ArrayList<View>) -> ArrayList<View>) {
    windowManagerInstance?.let { windowManagerInstance ->
        mViewsField?.let { mViewsField ->
            val mViews = mViewsField[windowManagerInstance] as ArrayList<View>
            mViewsField[windowManagerInstance] = swap(mViews)
        }
    }
}

Service 回收监听

由于 Android Framework 未提供设置 Service#onDestroy() 全局监听的方法,所以 LeakCanary 是通过 Hook 的方式实现的。

Service 监控这部分源码比较复杂,需要通过 2 步 Hook 来实现:

  • 1、Hook 主线程消息循环的 mH.mCallback 回调,监听其中的 STOP_SERVICE 消息,将即将 Destroy 的 Service 对象暂存起来(由于 ActivityThread.H 中没有 DESTROY_SERVICE 消息,所以不能直接监听到 onDestroy() 事件,需要第 2 步);
  • 2、使用动态代理 Hook AMS 与 App 通信的的 IActivityManager Binder 对象,代理其中的 serviceDoneExecuting() 方法,视为 Service#onDestroy() 的执行时机,拿到暂存的 Service 对象交给 ObjectWatcher 监控。 ServiceWatcher.kt
private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null

// 暂存即将 Destroy 的 Service
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()

override fun install() {
    // 1. Hook mH.mCallback
    swapActivityThreadHandlerCallback { mCallback /*原对象*/ ->
        // uninstallActivityThreadHandlerCallback:用于取消 Hook
        uninstallActivityThreadHandlerCallback = {
            swapActivityThreadHandlerCallback {
                mCallback
            }
        }
        // 新对象(lambda 表达式的末行就是返回值)
        Handler.Callback { msg ->
            // 1.1 Service#onStop() 事件
            if (msg.what == STOP_SERVICE) {
                val key = msg.obj as IBinder
                // 1.2 activityThreadServices:反射获取 ActivityThread mServices 映射表 <IBinder, CreateServiceData>
                activityThreadServices[key]?.let {
                    // 1.3 暂存即将 Destroy 的 Service
                    servicesToBeDestroyed[token] = WeakReference(service)
                }
            }
            // 1.4 继续执行 Framework 原有逻辑
            mCallback?.handleMessage(msg) ?: false
        }
    }
    // 2. Hook AMS IActivityManager
    swapActivityManager { activityManagerInterface, activityManagerInstance /*原对象*/ ->
        // uninstallActivityManager:用于取消 Hook
        uninstallActivityManager = {
            swapActivityManager { _, _ ->
                activityManagerInstance
            }
        }
        // 新对象(lambda 表达式的末行就是返回值)
        Proxy.newProxyInstance(activityManagerInterface.classLoader, arrayOf(activityManagerInterface)) { _, method, args ->
            // 2.1 代理 serviceDoneExecuting() 方法
            if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
                // 2.2 取出暂存的即将 Destroy 的 Service
                val token = args!![0] as IBinder
                if (servicesToBeDestroyed.containsKey(token)) {
                    servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
                        // 2.3 交给 ObjectWatcher 监控
                        serviceWeakReference.get()?.let { service ->
                            reachabilityWatcher.expectWeaklyReachable(service /*被监控对象*/, "${service::class.java.name} received Service#onDestroy() callback")
                        }
                    }
                }
            }
            // 2.4 继续执行 Framework 原有逻辑
            method.invoke(activityManagerInstance, *args)
        }
    }
}

override fun uninstall() {
    // 关闭 mH.mCallback 的 Hook
    uninstallActivityManager?.invoke()
    uninstallActivityThreadHandlerCallback?.invoke()
    uninstallActivityManager = null
    uninstallActivityThreadHandlerCallback = null
}

// 使用反射修改 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?
    // 将 swap 的返回值作为新对象,实现 Hook
    mCallbackField[mH] = swap(mCallback)
}

// 使用反射修改 AMS 与 App 通信的 IActivityManager Binder 对象
// swap 是一个 lambda 表达式,参数为 IActivityManager 的 Class 对象和接口原实现对象,返回值为注入的新对象
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.
    val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)

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

至此,LeakCanary 初始化完成,并且成功在 Android Framework 的各个位置安插监控,实现对 Activity 和 Service 等对象进入无用状态的监听。

2.3 监控内存泄漏

完成以上步骤后,会交给 ObjectWatcher 监控,它主要通过以下 3 步来判断对象是否泄漏:

  • 1. 为被监控对象 watchedObject 创建一个 KeyedWeakReference 弱引用,并存储到 <UUID, KeyedWeakReference> 的映射表中;
  • 2. postDelay 五秒后检查引用对象是否出现在引用队列中,出现在队列则说明被监控对象未发生泄漏。随后,移除映射表中未泄露的记录,更新泄漏的引用对象的 retainedUptimeMillis 字段以标记为泄漏;
  • 3. 通过回调 onObjectRetained 告知 LeakCanary 内部发生新的内存泄漏。

AppWatcher.kt

val objectWatcher = ObjectWatcher(
    // lambda 表达式获取当前系统时间
    clock = { SystemClock.uptimeMillis() },
    // lambda 表达式实现 Executor SAM 接口
    checkRetainedExecutor = {
        mainHandler.postDelayed(it, retainedDelayMillis)
    },
    // lambda 表达式获取监控开关
    isEnabled = { true }
)

ObjectWatcher.kt

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

    if (!isEnabled()) {
        // 监控开关
        return
    }

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

    // KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏
    private val queue = ReferenceQueue<Any>()

    // 1. 为 watchedObject 对象增加监控
    @Synchronized 
    override fun expectWeaklyReachable(
        watchedObject: Any,
        description: String
    ) {
        // 1.1 移除 watchedObjects 中未泄漏的引用对象
        removeWeaklyReachableObjects()
        // 1.2 新建一个 KeyedWeakReference 引用对象
        val key = UUID.randomUUID().toString()
        val watchUptimeMillis = clock.uptimeMillis()
        watchedObjects[key] = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
        // 2. 五秒后检查引用对象是否出现在引用队列中,否则判定发生泄漏
        // checkRetainedExecutor 相当于 postDelay 五秒后执行 moveToRetained() 方法
        checkRetainedExecutor.execute {
            moveToRetained(key)
        }
    }

    // 2. 五秒后检查引用对象是否出现在引用队列中,否则说明发生泄漏
    @Synchronized 
    private fun moveToRetained(key: String) {
        // 2.1 移除 watchedObjects 中未泄漏的引用对象
        removeWeaklyReachableObjects()
        // 2.2 依然存在的引用对象被判定发生泄漏
        val retainedRef = watchedObjects[key]
        if (retainedRef != null) {
            retainedRef.retainedUptimeMillis = clock.uptimeMillis()
            // 3. 回调通知 LeakCanary 内部处理
            onObjectRetainedListeners.forEach { it.onObjectRetained() }
        }
    }

    // 移除未泄漏对象对应的 KeyedWeakReference
    private fun removeWeaklyReachableObjects() {
        var ref: KeyedWeakReference?
        do {
            ref = queue.poll() as KeyedWeakReference?
            if (ref != null) {
                // KeyedWeakReference 出现在引用队列中,说明未发生泄漏
                watchedObjects.remove(ref.key)
            }
        } while (ref != null)
    }

    // 4. Heap Dump 后移除所有监控时间早于 heapDumpUptimeMillis 的引用对象
    @Synchronized 
    fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
        val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
        weakRefsToRemove.values.forEach { it.clear() }
        watchedObjects.keys.removeAll(weakRefsToRemove.keys)
    }

    // 获取是否有内存泄漏对象
    val hasRetainedObjects: Boolean
    @Synchronized get() {
        // 移除 watchedObjects 中未泄漏的引用对象
        removeWeaklyReachableObjects()
        return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
    }

    // 获取内存泄漏对象计数
    val retainedObjectCount: Int
    @Synchronized get() {
        // 移除 watchedObjects 中未泄漏的引用对象
        removeWeaklyReachableObjects()
        return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
    }
}

被监控对象 watchedObject 关联的弱引用对象:

KeyedWeakReference.kt

class KeyedWeakReference(
    // 被监控对象
    referent: Any,
    // 唯一 Key,根据此字段匹配映射表中的记录
    val key: String,
    // 描述信息
    val description: String,
    // 监控开始时间,即引用对象创建时间
    val watchUptimeMillis: Long,
    // 关联的引用队列
    referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(referent, referenceQueue) {
  
    // 记录实际对象 referent 被判定为泄漏对象的时间
    // -1L 表示非泄漏对象,或者还未判定完成
    @Volatile
    var retainedUptimeMillis = -1L

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

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

2.4 Dump heap 获取内存快照文件

ObjectWatcher 判定被监控对象发生泄漏后,会通过接口方法 OnObjectRetainedListener#onObjectRetained() 回调到 LeakCanary 内部的管理器 InternalLeakCanary 处理(在前文 AppWatcher 初始化中提到过)。LeakCanary 不会每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:

  • 1. 泄漏对象计数未达到阈值,或者进入后台时间未达到阈值;
  • 2. 计算距离上一次 HeapDump 未超过 60s。 源码摘要如下:

InternalLeakCanary.kt

// 从 ObjectWatcher 回调过来
override fun onObjectRetained() = scheduleRetainedObjectCheck()

private lateinit var heapDumpTrigger: HeapDumpTrigger

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

HeapDumpTrigger.kt

fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
    // 已简化:源码此处使用时间戳拦截,避免重复 postDelayed
    backgroundHandler.postDelayed({
        checkRetainedObjects()
    }, delayMillis)
}

private fun checkRetainedObjects() {
    val config = configProvider()

    // 泄漏对象计数
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    if (retainedReferenceCount > 0) {
        // 主动触发 GC,并等待 100 ms
        gcTrigger.runGc()
        // 重新获取泄漏对象计数
        retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    // 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值
    if (retainedKeysCount < retainedVisibleThreshold) {
        // App 位于前台或者刚刚进入后台
        if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
            // 发送通知提醒
            showRetainedCountNotification("App visible, waiting until %d retained objects")
            // 延迟 2 秒再检查
            scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
            return;
        }
    }

    // 拦截 2:计算距离上一次 HeapDump 未超过 60s
    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
        // 发送通知提醒
        showRetainedCountNotification("Last heap dump was less than a minute ago")
        // 延迟 (60 - elapsedSinceLastDumpMillis)s 再检查
        scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis)
        return
    }
	
    // 移除通知提醒
    dismissRetainedCountNotification()
    // 触发 HeapDump(此时,应用有可能在后台)
    dumpHeap(...)
}

// 真正开始执行 Heap Dump
private fun dumpHeap(...) {
    // 1. 获取文件存储提供器
    val directoryProvider = InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)

    // 2. 创建 .hprof File 文件
    val heapDumpFile = directoryProvider.newHeapDumpFile()

    // 3. 执行 Heap Dump
    // Heap Dump 开始时间戳
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    // heapDumper.dumpHeap:最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath) 
    configProvider().heapDumper.dumpHeap(heapDumpFile)

    // 4. 清除 ObjectWatcher 中过期的监控
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)

    // 5. 分析堆快照
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
}

2.5 分析堆快照

在前面的工作中,LeakCanary 已经成功生成 .hprof 堆快照文件,并且发送了一个 LeakCanary 内部事件 HeapDump。LeakCanary 的配置项中设置了多个事件消费者 EventListener,其中与 HeapDump 事件有关的是 when{} 代码块中三个消费者。不过,这三个消费者并不是并存的,而是会根据 App 当前的依赖项而选择最优的执行策略:

  • 1 - WorkerManager 多进程分析
  • 2 - WorkManager 异步分析
  • 3 - 异步线程分析(兜底策略) LeakCanary 配置项中的事件消费者:

LeakCanary.kt

data class Config(
    val eventListeners: List<EventListener> = listOf(
        LogcatEventListener,
        ToastEventListener,
        LazyForwardingEventListener {
            if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
        },
        when {
            // 策略 1 - WorkerManager 多进程分析
            RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->RemoteWorkManagerHeapAnalyzer
            // 策略 2 - WorkManager 异步分析
            WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
            // 策略 3 - 异步线程分析(兜底策略)
            else -> BackgroundThreadHeapAnalyzer
        }
    ),
    ...
)

策略 1 - WorkerManager 多进程分析: 判断是否可以类加载 RemoteLeakCanaryWorkerService,这个类位于前文提到的 com.squareup.leakcanary:leakcanary-android-process:2.9.1 依赖中。如果可以类加载成功则视为有依赖,使用 WorkerManager 多进程分析;

RemoteWorkManagerHeapAnalyzer.kt

object RemoteWorkManagerHeapAnalyzer : EventListener {

    // 通过类加载是否成功,判断是否存在依赖
    internal val remoteLeakCanaryServiceInClasspath by lazy {
        try {
            Class.forName("leakcanary.internal.RemoteLeakCanaryWorkerService")
            true
        } catch (ignored: Throwable) {
            false
        }
    }

    override fun onEvent(event: Event) {
        if (event is HeapDump) {
            // 创建并分发 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()
            WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
        }
    }
}

RemoteHeapAnalyzerWorker.kt

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 {
            // 1.1 分析堆快照
            val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = {
                result.isCancelled
            }) { progressEvent ->
                // 1.2 发送分析进度事件
                if (!result.isCancelled) {
                    InternalLeakCanary.sendEvent(progressEvent)
                }
            }
            // 1.3 发送分析完成事件
            InternalLeakCanary.sendEvent(doneEvent)
            result.set(Result.success())
        }
        return result
    }
}
  • 策略 2 - WorkManager 异步分析: 判断是否可以类加载 androidx.work.WorkManager,如果可以,则使用 WorkManager 异步分析;

WorkManagerHeapAnalyzer.kt

internal val validWorkManagerInClasspath by lazy {
    // 判断 WorkManager 依赖,代码略
}

override fun onEvent(event: Event) {
    if (event is HeapDump) {
        // 创建并分发 WorkManager 请求
        val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply {
            setInputData(event.asWorkerInputData())
            addExpeditedFlag()
        }.build()
        val application = InternalLeakCanary.application
        WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
    }
}

HeapAnalyzerWorker.kt

internal class HeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
    override fun doWork(): Result {
        // 2.1 分析堆快照
        val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(inputData.asEvent()) { event ->
            // 2.2 发送分析进度事件
            InternalLeakCanary.sendEvent(event)
        }
        // 2.3 发送分析完成事件
        InternalLeakCanary.sendEvent(doneEvent)
        return Result.success()
    }
}
  • 策略 3 - 异步线程分析(兜底策略): 如果以上策略未命中,则直接使用子线程兜底执行。

BackgroundThreadHeapAnalyzer.kt

object BackgroundThreadHeapAnalyzer : EventListener {

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

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

可以看到,不管采用那种执行策略,最终执行的逻辑都是一样的:

    1. 分析堆快照;
    1. 发送分析进度事件;
    1. 发送分析完成事件。

在前面的分析中,我们已经知道 LeakCanary 是通过子线程或者子进程执行 AndroidDebugHeapAnalyzer.runAnalysisBlocking 方法来分析堆快照的,并在分析过程中和分析完成后发送回调事件。现在我们来阅读 LeakCanary 的堆快照分析过程:

AndroidDebugHeapAnalyzer.kt

fun runAnalysisBlocking(
    heapDumped: HeapDump,
    isCanceled: () -> Boolean = { false },
    progressEventListener: (HeapAnalysisProgress) -> Unit
): HeapAnalysisDone<*> {
    ...
    // 1. .hprof 文件
    val heapDumpFile = heapDumped.file
    // 2. 分析堆快照
    val heapAnalysis = analyzeHeap(heapDumpFile, progressListener, isCanceled)
    val analysisDoneEvent = ScopedLeaksDb.writableDatabase(application) { db ->
    // 3. 将分析报告持久化到 DB
    val id = HeapAnalysisTable.insert(db, heapAnalysis)
    // 4. 发送分析完成事件(返回到上一级进行发送:InternalLeakCanary.sendEvent(doneEvent))
    val showIntent = LeakActivity.createSuccessIntent(application, id)
    val leakSignatures = fullHeapAnalysis.allLeaks.map { it.signature }.toSet()
    val leakSignatureStatuses = LeakTable.retrieveLeakReadStatuses(db, leakSignatures)
    val unreadLeakSignatures = leakSignatureStatuses.filter { (_, read) -> !read}.keys.toSet()
        HeapAnalysisSucceeded(heapDumped.uniqueId, fullHeapAnalysis, unreadLeakSignatures ,showIntent)
    }
    return analysisDoneEvent
}

开始进入 Shark 组件:

shark.HeapAnalyzer.kt

// analyze -> analyze -> FindLeakInput.analyzeGraph
private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
): HeapAnalysisSuccess {
    ...
    // 1. 在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象
    // leakingObjectFinder 默认是 KeyedWeakReferenceFinder
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    // 2. 分析泄漏对象的最短引用链,并按照应用链签名分类
    // applicationLeaks: Application Leaks
    // librbuildLeakTracesaryLeaks:Library Leaks
    // unreachableObjects:LeakCanary 无法分析出强引用链,可以提 Stack Overflow
    val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
    // 3. 返回分析完成事件
    return HeapAnalysisSuccess(...)
}

private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
    // PathFinder:引用链分析器
    val pathFinder = PathFinder(graph, listener, referenceReader, referenceMatchers)
    // pathFindingResults:完整引用链
    val pathFindingResults = pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
    // unreachableObjects:LeakCanary 无法分析出强引用链(相当于 LeakCanary 的 Bug)
    val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)
    // shortestPaths:最短引用链
    val shortestPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
    // inspectedObjectsByPath:标记信息
    val inspectedObjectsByPath = inspectObjects(shortestPaths)
    // retainedSizes:泄漏内存大小
    val retainedSizes = computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree)
    // 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类
    // applicationLeaks: Application Leaks
    // librbuildLeakTracesaryLeaks:Library Leaks
    val (applicationLeaks, librbuildLeakTracesaryLeaks) = buildLeakTraces(shortestPaths, inspectedObjectsByPath, retainedSizes)
    return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects)
}

可以看到,堆快照分析最终是交给 Shark 中的 HeapAnalizer 完成的,核心流程是:

  • 1、在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象;
  • 2、分析 KeyedWeakReference 对象的最短引用链,并按照引用链签名分组,按照 Application Leaks 和 Library Leaks 分类;
  • 3、返回分析完成事件。

着重看最复杂的第 2 步:

shark.HeapAnalyzer.kt

// 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类
private fun FindLeakInput.buildLeakTraces(
    shortestPaths: List<ShortestPath> /*最短引用链*/ ,
    inspectedObjectsByPath: List<List<InspectedObject>> /*标记信息*/ ,
    retainedSizes: Map<Long, Pair<Int, Int>>? /*泄漏内存大小*/
): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    // Application Leaks
    val applicationLeaksMap = mutableMapOf<String, MutableList<LeakTrace>>()
    // Library Leaks
    val libraryLeaksMap = mutableMapOf<String, Pair<LibraryLeakReferenceMatcher, MutableList<LeakTrace>>>()

    shortestPaths.forEachIndexed { pathIndex, shortestPath ->
        // 标记信息
        val inspectedObjects = inspectedObjectsByPath[pathIndex]
        // 实例化引用链上的每个对象快照(非怀疑对象的 leakingStatus 为 NOT_LEAKING)
        val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes)
        val referencePath = buildReferencePath(shortestPath, leakTraceObjects)
        // 分析报告
        val leakTrace = LeakTrace(
            gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot),
            referencePath = referencePath,
            leakingObject = leakTraceObjects.last()
        )
        val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher()
        if (firstLibraryLeakMatcher != null) {
            // Library Leaks
            val signature: String = firstLibraryLeakMatcher.pattern.toString().createSHA1Hash()
            libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() }.second += leakTrace
        } else {
            // Application Leaks
            applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace
        }
    }
    val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) ->
        // 实例化为 ApplicationLeak 类型
        ApplicationLeak(leakTraces)
    }
    val libraryLeaks = libraryLeaksMap.map { (_, pair) ->
        // 实例化为 LibraryLeak 类型
        val (matcher, leakTraces) = pair
        LibraryLeak(leakTraces, matcher.pattern, matcher.description)
    }
    return applicationLeaks to libraryLeaks
}
// 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类
private fun FindLeakInput.buildLeakTraces(
    shortestPaths: List<ShortestPath> /*最短引用链*/ ,
    inspectedObjectsByPath: List<List<InspectedObject>> /*标记信息*/ ,
    retainedSizes: Map<Long, Pair<Int, Int>>? /*泄漏内存大小*/
): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
    // Application Leaks
    val applicationLeaksMap = mutableMapOf<String, MutableList<LeakTrace>>()
    // Library Leaks
    val libraryLeaksMap = mutableMapOf<String, Pair<LibraryLeakReferenceMatcher, MutableList<LeakTrace>>>()

    shortestPaths.forEachIndexed { pathIndex, shortestPath ->
        // 标记信息
        val inspectedObjects = inspectedObjectsByPath[pathIndex]
        // 实例化引用链上的每个对象快照(非怀疑对象的 leakingStatus 为 NOT_LEAKING)
        val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes)
        val referencePath = buildReferencePath(shortestPath, leakTraceObjects)
        // 分析报告
        val leakTrace = LeakTrace(
            gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot),
            referencePath = referencePath,
            leakingObject = leakTraceObjects.last()
        )
        val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher()
        if (firstLibraryLeakMatcher != null) {
            // Library Leaks
            val signature: String = firstLibraryLeakMatcher.pattern.toString().createSHA1Hash()
            libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() }.second += leakTrace
        } else {
            // Application Leaks
            applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace
        }
    }
    val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) ->
        // 实例化为 ApplicationLeak 类型
        ApplicationLeak(leakTraces)
    }
    val libraryLeaks = libraryLeaksMap.map { (_, pair) ->
        // 实例化为 LibraryLeak 类型
        val (matcher, leakTraces) = pair
        LibraryLeak(leakTraces, matcher.pattern, matcher.description)
    }
    return applicationLeaks to libraryLeaks
}

2.6 输出分析报告

LeakCanary 会使用 ObjectInspector 对象检索器在引用链上的节点中标记必要的信息和状态,标记信息会显示在分析报告中,并且会影响报告中的提示。而引用链 LEAKING 节点以后到第一个 NOT_LEAKING 节点中间的节点,才会用 ~~~ 下划线标记为怀疑对象。

LeakCanary 通过 leakingObjectFinder 标记引用信息,leakingObjectFinder 默认是 AndroidObjectInspectors.appDefaults,也可以在配置项中自定义。

// inspectedObjectsByPath:筛选出非怀疑对象(分析报告中 ~~~ 标记的是怀疑对象)
val inspectedObjectsByPath = inspectObjects(shortestPaths)

看一下可视化报告中相关源码:

DisplayLeakAdapter.kt

...
val reachabilityString = when (leakingStatus) {
    UNKNOWN -> extra("UNKNOWN")
    NOT_LEAKING -> "NO" + extra(" (${leakingStatusReason})")
    LEAKING -> "YES" + extra(" (${leakingStatusReason})")
}
...

LeakTrace.kt

// 是否为怀疑对象
fun referencePathElementIsSuspect(index: Int): Boolean {
    return  when (referencePath[index].originObject.leakingStatus) {
        UNKNOWN -> true
        NOT_LEAKING -> index == referencePath.lastIndex || referencePath[index + 1].originObject.leakingStatus != NOT_LEAKING
        else -> false
    }
}

有两个位置处理了 HeapAnalysisSucceeded 事件:

  • Logcat:打印分析报告日志;
  • Notification: 发送分析成功系统通知消息。

LogcatEventListener.kt

 object LogcatEventListener : EventListener {
    ...
    SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(event.heapAnalysis.toString(), 120)}" }
    ...
}

NotificationEventListener.kt

object NotificationEventListener : EventListener {
    ...
    val flags = if (Build.VERSION.SDK_INT >= 23) {
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    } else {
        PendingIntent.FLAG_UPDATE_CURRENT
    }
    // 点击通知消息打开可视化分析报告
    val pendingIntent = PendingIntent.getActivity(appContext, 1,  event.showIntent, flags)
    showHeapAnalysisResultNotification(contentTitle,pendingIntent)
    ...
}

2.7 小结

最后来总结下 LeakCanary 内存泄漏分析过程:

    1. 初始化
    1. 注册 5 种 Android 泄漏场景的监控
    1. 收到销毁回调后,根据要回收对象创建 KeyedWeakReference 并关联 ReferenceQueue
    1. 延迟 5 秒检查相关对象是否被回收
    1. 如果未被回收则开启服务,dump heap 获取内存快照.hprof文件
    1. 通过 Shark 库解析.hprof 文件,获取泄漏对象,计算泄漏对象到 GC roots 的最短路径
    1. 合并多个泄漏路径并输出分析结果
    1. 将结果展示到可视化界面

三 参考文献

为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!