本文主题
关于WindowManager这个复杂的系统,本文会基于Android9.0源码,把其中的关键代码截取出来进行分析,并通过问答的形式来进行叙述,最终回答以下几个问题:
- WindowManager是什么?它的作用是什么?
- Window和WindowManager如何关联?
- Window、WindowManager和WindowManagerService三者有什么关系?
- Window有哪些类型?
- Window在Activity启动过程中的作用?
- Window如何处理View的添加、移除和更新?
WindowManager是什么
The interface that apps use to talk to the window manager.
Each window manager instance is bound to a particular Display. To obtain a WindowManager for a different display, use Context#createDisplayContext to obtain a Context for that display, then use Context.getSystemService(Context.WINDOW_SERVICE) to get the WindowManager.
The simplest way to show a window on another display is to create a Presentation. The presentation will automatically obtain a WindowManager and Context for that display.
用通俗一点的话来讲就是:
WindowManager是一个实现了ViewManager的接口,至于它是干嘛用的,官方文档并没有详细说明,从名字上我们可以知道它和显示有关,用于管理Window,具体作用还是直接看源码吧。
如何获取WindowManager
正如官方文档所说,我们可以直接通过 Context.getSystemService(Context.WINDOW_SERVICE) 来获取WindowManager
继承ViewManager接口
WindowManager继承ViewManager接口,而ViewManager接口很简单,只有三个方法
1 | public interface ViewManager |
定义了许多Flags
在WindowManager.LayoutParams里面有许多Flags,这些Flag的作用就是在创建Window的时候用于区分这个Window到底是什么类型的,关于Window类型的问题我们会在后面再详细说明。
下面列举了一小部分Flags
1 | public static final int TYPE_BASE_APPLICATION = 1; |
通过int类型的Type,我们可以区分不同的Window类型。
大家也可以直接跳到第四个问题查看:Window有哪些类型?
回答:WindowManager是什么
WindowManager负责的事情其实并不多,主要完成一些配置工作(定义Window类型的Flags,以及LayoutParams静态内部类),具体的跨进程通信还是要看WindowManagerService,而对View的操作则是通过WindowManagerGlobal来进行。
Window和WindowManager如何关联
说了这么多,分析了一通WindowManager这个类的源码,我们只知道它是一个接口,但是还不知道它具体是怎么使用的。别急,接下来就轮到Window登场了。
关联的关键:Window.java
我们在Window.java找到以下代码:
1 | /** |
这两个setWindowManager()方法分别在Activity和Dialog中被调用了。说明在Activity创建,Toast显示的过程中都需要用到WindowManager。
注意:mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
这一行代码就是Window和WindowManager产生化学反应的关键!
首先需要看看WindowManagerImpl是什么
WM的实现类:WindowManagerImpl.java
WindowManagerImpl.java
1 | public WindowManagerImpl createLocalWindowManager(Window parentWindow) { |
其实Window.java里面调用的这个方法,就是创建一个Impl对象。
在WindowManagerImpl中,我们会看到有个成员变量
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
这个WindowManagerGlobal 正是用于对View进行操作的实际对象。
无论是addView(), updateView(), removeView(),都是通过这个对象进行操作的。
1 |
|
关于WindowManagerGlobal 怎么处理View的逻辑,我们在后面会继续解析。这里还是先回到Window和WindowManager这两者是如何关联这个问题上来。
如果大家有细心留意的话就能看到,WindowManagerImpl的构造方法里面有一个parentWindow 的参数,这个参数的类型是Window,也就是说,当我们在Activity或者Dialog调用setWindowManager()的时候,就会把当前Window作为参数传递过来,在创建WindowManager的同时把这两者给关联起来。
回答:Window和WindowManager如何关联
在创建Activity或者Dialog的时候会调用Window.setWindowManager()方法,然后把当前Window作为参数传递到WindowManagerImpl的createLocalWindowManager()方法中,在创建WindowManager对象的时候把Window关联起来。
Window、WindowManager和WindowManagerService三者有什么关系
通过前面的分析,我们知道Window和WindowManager是如何关联起来的,然鹅到目前为止,我们还是不知道在Framework层的Window是如何同Native层的WindowManagerService进行通信的,让我们继续看源码。
上面说到,具体对View的操作实际是WindowManagerGlobal这个类来做的,那我们看看addView()方法里做了什么:
1 | public void addView(View view, ViewGroup.LayoutParams params, |
这里主要做了几件事:
- 构建ViewRootImpl对象
- 设置参数
- 添加到list中
- 把View显示出来
我们一个个来看,首先是构建ViewRootImpl对象
1 | public ViewRootImpl(Context context, Display display) { |
这里初始化了许多的成员变量,其中有一个是mWindowSession
,我们进去看看
1 |
|
看一下WindowManager是如何获取WMS对象的:
1 |
|
通过ServiceManager.getService(“window”)获取到WMS,然后再转为IWindowManager,那么在getService()方法中做了什么呢?
1 | public static IBinder getService(String name) { |
可以看到getService()方法返回的是IBinder对象。到这里我们就真正拿到了WMS了,也知道了Framework层与Native底层其实都是通过Binder机制进行通信。
这里比较绕,我们按照流程来捋一遍:首先在ViewRootImpl构建过程中,我们需要初始化IWindowSession对象,因此在getWindowSession() -> getWindowManagerService() -> getService()中从缓存列表(HashMap)获取WMS,然后通过asInterface函数转为WindowManager对象,最后通过openSession()与WMS建立会话,也就是在Framework层和Native层之间建立了连接。
回答:Window,WM和WMS有什么关系
经过前面三个问题的分析,我们应该有比较清晰的脉络了,对于这三者,Window和WM有关联(通过Activity和Dialog,忘记了可以回头看),WM和WMS有关联(通过WM的实现类WindowManagerImp的小弟WindowManagerGlobal)
因此,在Activity或者Dialog创建的时候,其实这三者就已经创建并且相互关联起来了。
我们还没说View到底是怎么显示出来的,这个问题留到最后一步再来解决。
Window有哪些类型
我们现在来填第一个问题时候埋下的坑,关于Window有哪些类型,其实就三种
- System Window
- Sub Window
- Application Window
System Window(系统窗口)
常见的例如Toast,输入法,系统弹出框等等,这部分窗口我们是没有权限创建的。
还记得上面我们列举了一小部分的Flags吗?System Window的type范围是2000以上,下面列举一部分
WindowManager.java:
1 |
|
Sub Window(子窗口)
所谓子窗口,则是指这个窗口还要有一个父窗口,例如PopupWindow
Sub Window的范围是是1000~1999,由于Sub Window比较少,我就全部列出来了
WindowManager.java
1 | // 子窗口 |
Application Window(应用程序窗口)
常见的例如Activity,由于比较少,我也全部列举出来了:
WindowManager.java
1 | // 开始应用程序窗口 |
回答:Window有哪些类型
三种,分别是系统窗口,子窗口,应用程序窗口,根据Type大小,系统窗口>子窗口>应用窗口,因此系统窗口在最上层,优先级最高。
WindowManager在Activity启动过程中的作用
Activity的attach()方法
关于Activity启动过程我们先忽略,只了解与Window/WindowManager相关的源码
在Activity.attach() 方法中,我们找到了WindowManager的身影
1 |
|
在attach()方法中,Activity会创建一个Window,然后setWindowManager() 我们在上面已经分析过了,其实就是创建了一个WindowManagerImpl类,并把Window和WM关联了起来。
接下来在Activity的onCreate()方法中,我们会调用setContentView()方法:
AppCompatActivity.java
1 |
|
继续进入AppCompatDelegate.java,这是一个抽象类,没有实现具体方法,我们直接看他的子类AppCompatDelegateImpl.java
1 |
|
这里提供了三个setContentView的重载方法,我们只看第二个,也就是传入layoutId的这个方法:
ensureSubDecor();
1 | private void ensureSubDecor() { |
createSubDecor()
1 | private ViewGroup createSubDecor() { |
这个方法主要做了两个事情
- 获取Window,保证mWindow对象不为空
- 获取到Window对象后再调用setContentView()方法
我们先看第一步:
1 | private void ensureWindow() { |
还记得在这个问题的开头,Activity的attach()方法中做了什么吗?
没错,我们初始化了一个PhoneWindow对象,并赋值为了mWindow!因此在这里拿到的Window对象自然也是PhoneWindow了!
因此第二步调用的setContentView()方法,也就是:PhoneWindow.setContentView()了。我们继续往下看
PhoneWindow的setContentView()方法
PhoneWindow.java
1 |
|
我们看一下installDecor()
做了什么
1 | private void installDecor() { |
具体的源码就不再仔细分析了,在这里系统主要做了:创建Decor,然后根据Window的Flag, Theme等配置创建一个布局,并且添加到Decor中。
至此,Activity已经成功的创建了WindowManager, 创建了Decor,创建了相应的布局,还差最后一步:把这个布局添加到Window中并显示出来。
让我们回到最初的起点,也就是ActivityThread,在这里我们能看到有个方法:handleResumeActivity()并找到其中一段
1 | // 设置activity为可见状态 |
在这里系统又做了三件事情:
- 获取WindowManager
- 把刚刚创建的Decor对象添加到WM中
- 设置Decor为可见状态
最终Activity才能正常显示在用户的面前。
回答:WindowManager在Activity启动过程中的作用
首先ActivityThread.attach()方法会创建Window和WindowManager
然后在onCreate()方法中会调用创建的PhoneWindow的setContentView()方法,接着在里面创建Decor以及一个根布局,创建完毕后把layoutResId加载到布局中,然后通知回调
最后调用ActivityThead.handleResumeActivity()方法,在这里把WindowManager和Decor关联起来,并且调用setVisibility让Decor可见,最终把UI呈现在用户面前。
WindowManager如何处理View的添加、移除和更新
这一个问题其实是上一个问题的延伸和扩展,虽然在上面我们说到了ActivityThread()调用handleResumeActivity()方法,然后把Decor通过addView()的方式加入到WindowManager中,但是我们有没有想过,这个addView()的过程有涉及到哪些模块呢?
正是因为这一过程比较复杂,因此也值得单独提出来探究。
addView() 过程
还记得WindowManager.addView() 实际调用的是哪个类的方法吗?忘了的请回去重新看一遍第二问~
WindowManagerGlobal.java
1 | public void addView(View view, ViewGroup.LayoutParams params, |
RootViewImpl.setView()
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
RootViewImpl.setView()方法很复杂,其中我们需要关注的是两点:
- requestLayout()
- mWindowSession.addToDisplay()
我们先看一下requestLayout()方法:
1 | // 定义 TraversalRunnable |
往Handler里面发送一条CALLBACK_TRAVERSAL消息,这条消息的意思就是刷新界面。
最终会调用 doTraversal() 方法
1 | void doTraversal() { |
然后到 performTraversals() 方法, 这个方法非常复杂,整个方法加起来大概有800多行,主要工作就是
- 测量各个View的大小(performMeasure)
- 布局(performLayout)
- 绘制(performDraw)
最终把整个视图树展示出来
updateViewLayout() 过程
update过程比较简单,直接上源码:
1 | public void updateViewLayout(View view, ViewGroup.LayoutParams params) { |
- 首先获取要update的View的ViewRootImpl
- 把这个View的LayoutParam移除掉
- 重新添加LayoutParam
- 刷新根布局
removeView() 过程
1 |
|
- 首先通过removeView()方法来移除View,在这个方法里面调用了removeViewLocked()
- 在removeViewLocked()又调用了 root.die(immediate)
- 在die() 又调用了 doDie()
- 在doDie()中调用了 doRemoveView()
- 如果需要延迟,则再发送一条MSG_DIE,重新调用doDie()方法
doRemoveView()
1 | void doRemoveView(ViewRootImpl root) { |
这个方法和上面的update方法类似,从缓存列表中找到对应的view,然后移除掉。
总结
在这篇文章中,我们从Acitivty的创建过程说起,涉及到ActivityThread, Window, WindowManager, WindowManagerService, RootViewImpl, PhoneWindow, WindowManagerImpl, WindowManagerGlobal 这么多类的相关源码分析,希望大家看完之后能对WindowManager相关的知识点有所了解。
如果文章有不对的地方也欢迎大家批评指正,感谢阅读!