理解 Window 和 WindowManager
Window 和 WindowManager
Window 表示一个窗口的概念,是一个抽象类,具体实现是 PhoneWindow,它对 View 进行管理,位于 WindowManagerService 中。
可通过 WindowManager 来创建 Window,WindowManager 是一个接口类,继承自接口 ViewManager,它是用来管理 Window 的,WindowManager 的实现类为 WindowManagerImpl。如果想要对 Window(View)进行添加、更新和删除操作就可以使用 WindowManager,WindowManager 会将具体的工作交由 WMS 来处理。(关于 WMS,它的主要功能是管理 Window。)
WindowManager 和 WindowManagerService 的交互是一个 IPC 过程,通过 Binder 来进行跨进程通信,WMS 作为系统服务有很多 API 是不会暴露给 WindowManager 的,这一点与 ActivityManager 和 AMS 的关系有些类似。
Window 包含了 View 并对 View 进行管理,Window 用虚线来表示是因为 Window 是一个抽象概念,用来描述一个窗口,并不是真实存在的,Window 的实体其实也是 View,WindowManager 用来管理 Window,而 WindowManager 所提供的功能最终会由 WMS 进行处理。
Android 中所有的视图都是通过 Window 来呈现的。Window 分三类,并且是分层的。
WindowManager 的关联类
ViewManager:
ViewManager 中定义了 3 个方法,分别用来添加、更新和删除 View。ViewManager.java
1 | public interface ViewManager |
WindowManager 也继承了这些方法,而这些方法传入的参数都是 View 类型,说明 Window 是以 View 的形式存在的。WindowManager 在继承 ViewManager 的同时,又加入很多功能,包括 Window 的类型和层级相关的常量、内部类以及一些方法,其中有两个方法是根据 Window 的特性加入的:WindowManager.java
1 | // 能够得知这个 WindowManager 实例将 Window 添加到哪个屏幕上了,换句话说,就是得到 WindowManager 所管理的屏幕(Display)。 |
PhoneWindow:
在 Activity 启动过程中会调用 ActivityThread 的 performLaunchActivity 方法,performLaunchActivity 方法中又会调用 Activity 的 attach 方法,PhoneWindow 就是在这个方法中创建的。Activity.java
1 | final void attach(Context context, ActivityThread aThread, |
接下来查看 Window 中的 setWindowManager 方法。Window.java
1 | public void setWindowManager(WindowManager wm, IBinder appToken, String appName, |
接下来查看 WindowManagerImpl 的 addView 方法。WindowManagerImpl.java
1 |
|
可以看出,WindowManagerImpl 虽然是 WindowManager 的实现类,但是没有实现什么功能,而是将功能实现委托给了 WindowManagerGlobal,这里用到的是桥接模式。
接下来查看 WindowManagerImpl 是如何定义 WindowManagerGlobal 的。WindowManagerImpl.java
1 | public final class WindowManagerImpl implements WindowManager { |
PhoneWindow 继承自 Window,Window 通过 setWindowManager 方法与 WindowManager 发生关联。WindowManager 继承自接口 ViewManager,WindowManagerImpl 是 WindowManager 接口的实现类,但是具体的功能都会委托给 WindowManagerGlobal 来实现。
Window 的属性
对于 Window、WindowManager、WMS 之间的关系,WMS 是 Window 的最终管理者,Window 好比是员工,WMS 是老板,为了方便老板管理员工则需要定义一些 “协议”,这些 “协议” 就是 Window 的属性,它们被定义在 WindowManager 的内部类 LayoutParams 中,了解 Window 的属性能够更好地理解 WMS 的内部原理。Window 的属性有很多种,与应用开发最密切的的有 3 种,它们分别是 Type(Window 的类型)、Flag(Window 的标志)和 SoftInputMode(软键盘相关模式)。
Window 的类型和显示次序
Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog 等。总的来说 Window 分为三大类型,分别是 Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每个大类型中又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中。WindowManager.java
应用程序窗口:
Activity 就是一个典型的应用程序窗口,应用程序窗口包含的 Type 值类型如下:
1 | // 此 Type 表示应用程序窗口类型初始值。 |
子窗口:
它不能独立存在,需要附着在其它窗口才可以,比如 PopupWindow 就是子窗口。子窗口的类型定义如下:
1 | // 子窗口的类型初始值 |
系统窗口:
Toast、输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口。系统窗口的类型定义如下:
1 | // 系统窗口类型初始值 |
窗口显示次序:
当一个进程向 WMS 申请一个窗口时,WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,上面已知 Window 三大类型的 Type 值范围,在一般情况下,Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当然窗口显示次序的逻辑不会这么简单,情况会比较多,举个常见的情况;当多个窗口的 Type 值都是 TYPE_APPLICATION,这时 WMS 会结合各种情况给出最终的 Z-Oder。
Window 的标志
Window 的标志也就是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,常用的如下:
设置 Window 的 Flag 有 3 种方法:
通过 Window 的 addFlags 方法。
1
2
3Window mWindow = getWindow();
// addFlags 方法内部会调用 setFlags 方法。
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);通过 Window 的 setFlags 方法。
1
2Window mWindow = getWindow();
mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);给 LayoutParams 设置 Flas,并通过 WindowManager 的 addView 方法进行添加。
1
2
3
4
5WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams();
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
WindowManager mWindowManager = getSystemService(Context,WINDOW_SERVICE);
TextView textView = new TextView(this);
mWindowManager.addView(textView,mWindowLayoutParams);
软键盘相关模式
窗口和窗口的叠加是十分常见的场景,但如果其中的窗口是软键盘,可能会出现一些问题,比如典型的用户登录界面,默认的情况弹出的软键盘窗口可能会盖住输入框下方的按钮。为了使得软键盘窗口能够按照期望来显式,WindowManager 的静态内部类 LayoutParams 中定义了软键盘相关模式,常用的如下:
这里的 SoftInputMode 与 AndroidManifet 中 Activity 的属性 android:windowSoftInputMode 是对应的,并且,还可以在 Java 代码中为 Window 设置 SoftInputMode:getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
Window 的操作
对于 Window 的操作,最终都是交由 WMS 来进行处理。窗口的操作分为两大部分,WindowManager 处理部分和 WMS 处理部分。Window 三大类型的窗口添加过程会有所不同,但是对于 WMS 处理部分,添加的过程基本一致。
接下来主要分析 Window 操作的 WindowManager 处理部分。
系统窗口的添加过程
系统窗口的添加过程也会根据不同的系统窗口有所区别,接下来以系统窗口 StatusBar 为例,它是 SystemUI 的重要组成部分,具体就是指系统状态栏,用于显示时间、电量和信号等信息。查看 StatusBar 的 addStatusBarWindow 方法,此方法负责为 StatusBar 添加 Window。StatusBar.java
1 | private void addStatusBarWindow() { |
StatusBarWindowManager 的 add 方法如下。StatusBarWindowManager.java
1 | public void add(View statusBarView, int barHeight) { |
WindowManager 的 addView 方法定义在 WindowManager 的父类接口 ViewManager 中,在 WindowManagerImpl 中实现。WindowManagerImpl.java
1 | /** |
调用了 WindowManagerGlobal 的 addView 方法。WindowManagerGlobal.java
首先要了解 WindowManagerGlobal 中维护的和 Window 操作相关的 3 个列表,在窗口的添加、更新和删除过程中都会涉及这 3 个列表,它们分别是 View 列表(ArrayList<View> mViews
)、布局参数列表(ArrayList<WindowManager.LayoutParams> mParams
)和 ViewRootImpl 列表(ArrayList<ViewRootImpl> mRoots
)。
1 | public void addView(View view, ViewGroup.LayoutParams params, |
ViewRootImpl 身负了很多职责,主要有以下几点:
- View 树的根并管理 View 树。
- 触发 View 的测量、布局和绘制。
- 输入事件的中转站。
- 管理 Surface。
- 负责与 WMS 进行进程间通信。
接下来查看 ViewRootImpl 的 setView 方法。ViewRootImpl.java
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
可以看出,本地进程的 ViewRootImpl 要想和 WMS 进行通信需要经过 Session,而 Session 为何包含在 WMS 中呢,查看 Session 的 addToDisplay 方法。Session.java
1 |
|
剩下的工作由 WMS 来处理,在 WMS 中会为这个添加的窗口分配 Surface,并确定窗口显示次序,可见负责显示界面的是画布 Surface,而不是窗口本身。WMS 会将它所管理的 Surface 交由 SurfaceFlinger 处理,SurfaceFlinger 会将这些 Surface 混合并绘制到屏幕上。
Activity 的添加过程
无论是哪种窗口,它的添加过程在 WMS 处理部分中是基本类似的,只不过会在权限和窗口显示次序等方面会有些不同。但是在 WindowManager 处理部分会有所不同,这里以最典型的应用程序窗口 Activity 为例,Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThread,ActivityThread 管理着当前应用程序进程的线程,这在 Activity 的启动过程中运用的很明显,当界面要与用户进行交互时,会调用 ActivityThread 的 handleResumeActivity 方法。ActivityThread.java
1 | final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { |
Window 的更新过程
与 Window 的添加过程类似。需要调用 ViewManager 的 updateViewLayout 方法,updateViewLayout 在 WindowManagerImpl 中实现,WindowManagerImpl 的 updateViewLayout 方法会调用 WindowManagerGlobal 的 updateViewLayout 方法。WindowManagerGlobal.java
1 | public void updateViewLayout(View view, ViewGroup.LayoutParams params) { |
ViewRootImpl 的 setLayoutParams 方法在最后会调用 ViewRootImpl 的 scheduleTraversals 方法。ViewRootImpl.java
1 | void scheduleTraversals() { |
1 | final class TraversalRunnable implements Runnable { |
performTraversals 方法使得 ViewTree 开始 View 的工作流程。
1 | private void performTraversals() { |
在 ViewRootImpl 的 performTraversals 方法中分别调用了 performMeasure、performLayout 和 performDraw 方法,它们的内部又会调用 View 的 measure、layout 和 draw 方法,从而完成了 View 的工作流程。
在 performTraversals 方法中更新了 Window 视图,又执行 Window 中的 View 的工作流程,这样就完成了 Window 的更新。
Window 的内部机制
Window 是一个抽象的概念,每个 Window 都对应着一个 View 和 一个 ViewRootImpl,Window 和 View 是通过 ViewRootImpl 来建立联系的,因此 Window 并不是实际存在的,而是以 View 的形式存在。
Window 的添加过程
通过 WindowManager(一个接口,真正实现是 WindowManagerImpl 类) 的 addView 实现。
WindowManagerImpl 的工作模式是典型的桥接模式,将所有操作委托给 WindowManagerGlobal(以工厂的形式向外提供自己的实例) 来实现。
WindowManagerGlobal 的 addView() 中:
- 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数。
- 创建 ViewRootImpl 并将 View 添加到列表中。
- 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程。
Window 的删除过程
也是先通过 WindowManagerImpl(removeView()) 后,再进一步 通过 WindowManagerGlobal 来实现的。
WindowManagerGlobal 的 removeView() 中:
- 先通过 findViewLocked 来建立数组遍历查找待删除的 View 的索引,然后调用 removeViewLocked 做进一步删除。
- removeViewLocked 通过 ViewRootImpl 的 die 方法来完成删除操作。
- die 发送一个请求删除的消息后就立刻返回了(异步删除)。
- ViewRootImpl 中 Handler 会处理此消息并调用 doDie 方法。
- doDie() 内部会调用 dispatchDetachedFromWindow(),由它完成真正删除 View 的逻辑。
Window 的更新过程
WindowManagerGlobal 的 updateViewLayout():
- 更新 View 的 LayoutParams 并替换掉 老的 LayoutParams。
- 更新 ViewRootImpl 中的 LayoutParams。
Window 的创建过程
View 是 Android 中的视图的呈现方式,它必须附着在 Window 这个抽象的概念上面,因此有视图的地方就有 Window。
Activity 的 Window 创建过程
Activity 的启动过程最终由 ActivityThread 中的 performLaunchActivity() 来完成,这个方法内部会通过类加载器创建 Activity 的实例对象,并调用其 attach() 为其关联运行过程中所依赖的一系列上下文环境变量。
在 Activity 的 attach() 里,系统会创建 Activity 所属的 Window 对象并为其设置回调接口,Window 对象的创建是通过 PolicyManager(策略类,其真正实现是 Policy 类)的 makeNewWindow 方法实现的。由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态改变时就会回调 Activity 的方法。
Activity 的视图是怎么附属在 Window 上的:
Activity 的视图由 setContentView() 提供,此方法中可看出,Activity 将具体实现交给了 Window(具体实现是 PhoneWindow)处理,PhoneWindow 的 setContentView() 大致遵循如下几个步骤:
- 如果没有 DecorView,那么就创建它
- 将 View 添加到 DecorView 的 mContentParent 中
- 回调 Activity 的 onContentChanged() 通知 Activity 视图已经发生改变
Dialog 的 Window 创建过程
过程与 Activity 类似:
- 创建 Window
- 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
- 将 DecorView 添加到 Window 中并显示
Toast 的 Window 创建过程
由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。
Toast 内部有两类 IPC 过程:
- Toast 访问 NotificationManagerService
- NotificationManagerService 回调 Toast 里的 TN 接口
Toast 属于系统 Window,其内部视图由两种方式指定,它们都对应 Toast 的一个 View 类型的内部成员 mNextView:
- 系统默认样式
- 通过 setView() 指定一个自定义 View
备注
参考资料:
Android 开发艺术探索