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