前段时间遇到一个问题:怎么获取对话框的视图层级?
对话框不属于当前的Activity所在的window里面。所以通过监听当前window的状态改变是无法实现的。于是准备挖掘一番。
首先看看Dialog的构造方法,发现的确是新建了一个PhoneWindow,并赋值给成员变量mWindow,同时将系统WindowManager赋值给mWindow,如下图源码片段:
然后,再看一下Dialog的show方法,会发现如果mDecor不为空时,是直接设置其可见性
如果mDecor不为空,则对mDevor赋值为window的DecorView实例
所以首次调用show方法的情形是这样的
发现会调用到WindowMannager的addView方法,说明此时才是将Dialog的视图添加到window里面,接着往下看,发现WindowMannager是一个接口,继承的ViewManager,addView方法也是Viewmanager的。于是需要找到WindowManager的实现类。我们先看Context.getSystemService方法。
查看Context源码,发现只是一个接口。而我们通常调用该方法时,Context对象一般是Activity或者Application,我们先看看Application的源码。
我们发现Application是集继承自ContextWrapper,从类名上可以看出ContextWrapper是对Context的包装类,而且在Application中也的确没有找到getSystemService的具体实现,自然是需要看看ContextWrapper
通过源码我们发现,ContextWrapper只是包装类,最终的实现全部是由mBase来实现,mBase的赋值主要是在构造器和attachBaseContext方法中,然后我们回去继续看Application的源码。
这里非常有意思,Applicaton的构造器是调用了参数为null的ContextWrapper的父类构造器,所以此时mBase是null,也就是只剩下在attachBaseContext中对mBase赋值了。。
整个Applicaton中只有此处调用了attachBaseContext,而且还是个隐藏Api.线索貌似到这里就断了~~(因为需要看Application的创建过程,不在本次议题中…),但是我们看到了一个新的类ContextImpl,不妨进去看看。
果然看到了我们想要的东西,当然是继续了呀。。
看描述,果然是一个强大的类。
此处就是ContextImpl中调用的方法。
SYSTEM_SERVICE_FETCHERS是一个static final类型的HashMap。在registerService的时候put一个对应serviceName的serviceFetcher。
而注册方法是一个private static的方法,也就是在类的静态代码段中调用的,这里面注册了一系列的系统服务~~我们找找WindowManager对应的服务。
找到了。原来WindowManager最终的实现是WindowManagerImpl.
(是不是源码从一路找过来比百度更有意思!)
然后我们回到最初的问题:WindowManager的addView方法:
又是调用到其他的地方去了。去mGlobal一探究竟。。
单例。。突破口貌似有了。继续看看WindowManagerGlobal的addView方法的具体实现
这里就简单了。。就是将view对象添加到mViews里。。
mViews是一个ArrayList。
到这里。。我们就可以尝试去解决我们前面提到的问题了。
先来看看代码怎么写。这里自定义了一个ArrayList,来实现对窗口的改变。当新窗口中有一个ID为test_text的TextView时,我们把其类容设置为hook
当然为了不影响系统本身的调用。。需要调用原始的ArrayList对应的方法。
下面是我们自定义Dialog的布局。重点是ID为test_text的TextView.
我们在Activtiy的onCreate方法中去Hook一下。。
hookWindowManagerGloabal具体代码:
然后我们试试弹出Dialog
最终效果是:
可以看到,Dialog中的TextView的内容已经改变了。。
事实上,WindowManagerGlobal还有视图移除、视图更新等方法。我们可以在应用初始化时,去监听对应的window变化。。从而实现全局的静默监听窗口的视图变化。
文章虽然是解决Window视图变化,但是探究了一次系统服务的注册逻辑。相信对这块不是很清楚的同学有些帮助。。。