一. 前言
之前有些过两篇分析LeakCanary如何监听各种对象的文章:
LeakCanary如何监听Fragment、Fragment View、ViewModel销毁时机?
LeakCanary如何监听Service、Root View销毁时机?
其实,在分析LeakCanary如何监听Root View(如Dialog、Toast窗口根View)的时候,并没有分析RootViewWatcher是怎么实现监听应用所有窗口根View的添加和移除的(当时我也不懂),只有下面孤零零的一段牛逼的监听代码:
override fun install() {
Curtains.onRootViewsChangedListeners += listener
}
override fun uninstall() {
Curtains.onRootViewsChangedListeners -= listener
}
这也算是留下了一个遗憾吧,毕竟学习到如何监听应用所有窗口Root View的添加和移除的这种技巧,不管是对源码的认知,还是对以后的项目功能的启发都一定会有帮助的。
现在就来解开谜底了,Curtains是大名鼎鼎的Square提供的一个window帮助库,而RootViewWatcher就是借助这个库监听Root View添加和移除,所以我们就带着上面的问题细细分析下这个库的实现机制了。
PS:分析的Curtains库依赖版本:
dependencies {
implementation 'com.squareup.curtains:curtains:1.2.4'
}
二. Curtains探究
Curtains.onRootViewsChangedListeners瞧一瞧
这里我们直接以上面的Curtains如何帮助RootViewWatcher监听Root View添加作为切入点分析:
继续往下看:
其中rootViewsSpy是RootViewsSpy类的实例对象,同时还是Curtains内的一个属性。
可以看到,最终会将listener添加到RootViewsSpy的listeners集合中。
这里顺便说个知识点,+=是集合定义的运算符重载扩展函数实现的,相当于实现了add()操作。
接下来的目标就要寻找这个listeners集合中的元素什么时候被取出并执行的,接下来,我们先看下创建RootViewsSpy对象时发生了哪些操作。
RootViewsSpy的创建初始化
上面有说,rootViewsSpy是Curtains的一个属性,并且还是通过懒加载创建的,懒加载代码块中调用RootViewsSpy.install()完成最终的创建,我们看下这个方法:
该方法创建RootViewSpy同时,还调用了非常关键的方法WindowManagerSpy.swapWindowManagerGlobalMViews{}:
这个方法从表面上看,就是将该方法内部的mViews添加到delegatingViewList中,其中这个mViews就是收集的应用全局所有的Root View对象,至于怎么获取的我们下面会进行分析的,这里我们看下delegatingViewList的操作:
delegatingViewList重写了集合的add()方法,这个方法会遍历上面的RootViewsSpy.listeners对象,最终执行了我们添加的listener对象,从而最终实现了对于Root View的attach和detach的内存泄漏监听。
所以,WindowManagerSpy.swapWindowManagerGlobalMViews{}就是我们本篇文章讲解的核心,接下来我们看下它是如何收集应用所有的Root View的。
WindowManagerSpy.swapWindowManagerGlobalMViews{}探究
关键代码mViewsField[windowManagerInstance] as ArrayList<View>,通过这个最终拿到了应用所有的Root View,并且很明显是通过反射获取的,其中mViewsField属性对应windowManagerInstance的一个字段。
接下来我们看下最关键的mViewsField是个啥。
mViewsField是个啥?
mViewsField这个代表这windowManagerClass类中一个叫mViews的字段,windowManagerClass这个是个啥类:
到这里,终于破案了,windowManagerClass就是WindowManagerGlobal对象,这个对象相信对于View渲染流程比较熟悉的人应该不会默认,这里我们简单说下这个类的作用:
界面渲染流程是从Activity的onResume开始的,其中在onResume界面会调用WindowManager.addView()方法完成窗口的添加,而这个方法经过WindowManagerImpl(WindowManager是接口,这个是其实现类)最终会调用到WindowManagerGlobal.addView(View)方法;
在这个方法中会将界面要渲染显示的根View添加到WindowManagerGlobal.mViews对应的集合中,所以只要我们能拿到WindowManagerGlobal.mViews ,自然就可以拿到应用所有的Root View 。
像常见的Dialog、Toast等显示的界面最终也是会调用WindowManager.addView()方法走到上面的这个流程中的。
其中,WindowManagerGlobal对象实例可以通过其提供的getInstance()静态方法获取:
对应在WindowManagerSpy中获取该实例对象的方法为:
private val windowManagerInstance by lazy(NONE) {
windowManagerClass?.let { windowManagerClass ->
val methodName = if (SDK_INT > 16) {
"getInstance"
} else {
"getDefault"
}
windowManagerClass.getMethod(methodName).invoke(null)
}
}
三. 总结
从上面的流程中,我们可以得到这个机制实现的一个大致流程:
-
由于所有窗口的添加都会经过
WindowManagerGlobal.addView(View)的方法,所以自然可以拿到窗口的根View并保存到WindowManagerGlobal.mViews中,所以我们通过反射获取mViews字段实例; -
接着我们自定义一个
ArrayList,监听集合的add()和removeAt()方法,并通过反射将其赋值给mViews; -
最终通过监听的
add()方法调用外部传入的OnRootViewsChangedListener集合实例,也就是我们文章一开始提到的listener,最终完成了对于Root View的attach和detach监听:
四. 后续
其实上面的这个用法只是curtains提供的一个小功能而已,对于这个curtains的其他功能探究后续有空会再一一进行分析。
本文正在参加「金石计划」