一. 前语
之前有些过两篇剖析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的其他功用探究后续有空会再一一进行剖析。
本文正在参加「金石计划」











