一 概述
之前我们分析了 Window、WindowManager 和 WMS 之间的关系,WMS 是 Window 的最终管理者,Window 好比是员工,WMS 是老板,为了方便老板管理员工则需要定义一些 “协议”,这些“协议”就是 Window 的属性,被定义在 WindowManager 的内部类 LayoutParams 中,了解 Window 的属性能够更好的理解 WMS 的内部原理。
Window 的属性有很多种,与应用开发最密切的有三种,它们分别是 Type (Window 的类型)、Flag (Window 的标志) 和 SoftInputMode(软键盘相关模式),下面分别介绍这三种 Window 的属性。
二 Window类型
Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog 等等。总来来说分为三大类分别是:Application Window(应用程序窗口)、Sub Windwow(子窗口)、System Window(系统窗口),每个大类又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中,接下来我们分别对这三大类进行讲解。
常见的 Window 类型如下:
完整的 Window 类型及解释, 可以参考源码 WindowManager。
当一个进程向 WMS 申请一个窗口时,WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟的用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,我们知道应用程序窗口的 Type 值范围为 1 到 99,子窗口 1000 到 1999 ,系统窗口 2000 到 2999,一般情况下,Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当然窗口显示次序的逻辑不会这么简单,情况会比较多,举个常见的情况:当多个窗口的 Type 值都是 TYPE_APPLICATION,这时 WMS 会结合各种情况给出最终的 Z-Oder,我们接下来分析下这个 Z-Oder 的次序是怎么确定的。
2.1 Z-Order的确定
在 Client 端,WindowManager.LayoutParams 的 x & y & type 用来确定 Window 的三维坐标位置。
public static class WindowManager.LayoutParams extends
ViewGroup.LayoutParams implements Parcelable {
public int x;// x坐标
public int y;// y坐标
public int type;// 类型(WMS根据type确定Z-Order)
......
}
在 WMS 端,WindowState 表示一个窗口实例,其中的属性 mBaseLayer 和 mSubLayer 用来确定 Z-Order。
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
// 计算主序时乘以10000, 目的是为同类型的多个窗口预留空间和Z-Order调整
static final int TYPE_LAYER_MULTIPLIER = 10000;
// 计算主序时加1000, 目的是确定相同主序的子窗口相对于父窗口的上下位置
static final int TYPE_LAYER_OFFSET = 1000;
// 表示主序
final int mBaseLayer;
// 表示子序
final int mSubLayer;
WindowState(WindowManagerService service, Session s, ......) {
......
// 若当前窗口类型为子窗口
if ((mAttrs.type >= FIRST_SUB_WINDOW &&
mAttrs.type <= LAST_SUB_WINDOW)) {
// 计算主序, 主序与父窗口一致,主序大的窗口位于主序小的窗口上面
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow) *
TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
// 计算子序
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
......
} else { // 若当前窗口类型不是子窗口
// 计算主序
mBaseLayer = mPolicy.getWindowLayerLw(this) *
TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
// 子序为0
mSubLayer = 0;
......
}
......
}
2.1.1 确定主序
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
default int getWindowLayerLw(WindowState win) {
return getWindowLayerFromTypeLw(win.getBaseType(),
win.canAddInternalSystemWindow());
}
default int getWindowLayerFromTypeLw(int type) {
if (isSystemAlertWindowType(type)) {
throw new IllegalArgumentException("......");
}
return getWindowLayerFromTypeLw(type, false);
}
default int getWindowLayerFromTypeLw(int type,
boolean canAddInternalSystemWindow) {
if (type >= FIRST_APPLICATION_WINDOW && type <=
LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER; // 2
}
switch (type) {
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the
// window manager may move it.
return 1;
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
case TYPE_DOCK_DIVIDER:
case TYPE_QS_DIALOG:
return APPLICATION_LAYER;
case TYPE_PHONE:
return 3;
case TYPE_SEARCH_BAR:
case TYPE_VOICE_INTERACTION_STARTING:
return 4;
case TYPE_VOICE_INTERACTION:
// voice interaction layer is almost
// immediately above apps.
return 5;
case TYPE_INPUT_CONSUMER:
return 6;
case TYPE_SYSTEM_DIALOG:
return 7;
case TYPE_TOAST:
// toasts and the plugged-in battery thing
return 8;
case TYPE_PRIORITY_PHONE:
// SIM errors and unlock. Not sure if this
// really should be in a high layer.
return 9;
case TYPE_SYSTEM_ALERT:
// like the ANR / app crashed dialogs
// Type is deprecated for non-system apps.
// For system apps, this type should be
// in a higher layer than TYPE_APPLICATION_OVERLAY.
return canAddInternalSystemWindow ? 13 : 10;
case TYPE_APPLICATION_OVERLAY:
return 12;
case TYPE_DREAM:
// used for Dreams (screensavers with TYPE_DREAM windows)
return 14;
case TYPE_INPUT_METHOD:
// on-screen keyboards and other such input
// method user interfaces go here.
return 15;
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input
// method user interfaces go here.
return 16;
case TYPE_STATUS_BAR:
return 17;
case TYPE_STATUS_BAR_PANEL:
return 18;
case TYPE_STATUS_BAR_SUB_PANEL:
return 19;
case TYPE_KEYGUARD_DIALOG:
return 20;
case TYPE_VOLUME_OVERLAY:
// the on-screen volume indicator and
// controller shown when the user
// changes the device volume
return 21;
case TYPE_SYSTEM_OVERLAY:
// the on-screen volume indicator and
// controller shown when the user
// changes the device volume
return canAddInternalSystemWindow ? 22 : 11;
case TYPE_NAVIGATION_BAR:
// the navigation bar, if available
// shows atop most things
return 23;
case TYPE_NAVIGATION_BAR_PANEL:
// some panels (e.g. search) need to
// show on top of the navigation bar
return 24;
case TYPE_SCREENSHOT:
// screenshot selection layer shouldn't go
// above system error, but it should cover
// navigation bars at the very least.
return 25;
case TYPE_SYSTEM_ERROR:
// system-level error dialogs
return canAddInternalSystemWindow ? 26 : 10;
case TYPE_MAGNIFICATION_OVERLAY:
// used to highlight the magnified portion of a display
return 27;
case TYPE_DISPLAY_OVERLAY:
// used to simulate secondary display devices
return 28;
case TYPE_DRAG:
// the drag layer: input for drag-and-drop is
// associated with this window,
// which sits above all other focusable windows
return 29;
case TYPE_ACCESSIBILITY_OVERLAY:
// overlay put by accessibility services to
// intercept user interaction
return 30;
case TYPE_SECURE_SYSTEM_OVERLAY:
return 31;
case TYPE_BOOT_PROGRESS:
return 32;
case TYPE_POINTER:
// the (mouse) pointer layer
return 33;
default:
return APPLICATION_LAYER;
}
}
从主序的确定过程可知:
- 从大的分类上来说,系统窗口 > 子窗口 > 应用窗口,整体上 type 和主序存在正相关。
- 但是在单个分类里,单个窗口的 type 大小和主序大小不存在正相关或负相关。
- 主序越大,窗口 Z-Order 越靠上面。
2.1.2 确定子序
default int getSubWindowLayerFromTypeLw(int type) {
switch (type) {
case TYPE_APPLICATION_PANEL:
case TYPE_APPLICATION_ATTACHED_DIALOG:
return APPLICATION_PANEL_SUBLAYER; // 1
case TYPE_APPLICATION_MEDIA:
return APPLICATION_MEDIA_SUBLAYER; // -2
case TYPE_APPLICATION_MEDIA_OVERLAY:
return APPLICATION_MEDIA_OVERLAY_SUBLAYER; // -1
case TYPE_APPLICATION_SUB_PANEL:
return APPLICATION_SUB_PANEL_SUBLAYER; // 2
case TYPE_APPLICATION_ABOVE_SUB_PANEL:
return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER; // 3
}
return 0;
}
窗口子序,用来描述子窗口相对于父窗口的位置:
- 子序 >0 表示位于父窗口上面,子序 <0 表示位于父窗口下面,子序 = 0 表示不是子窗口。
- 子序越大,子窗口相对于父窗口越靠上面。
从子序的确定过程可知:
- 子窗口的 type 大小和子序大小不存在正相关或负相关。
- 视频相关的子窗口类型 ( TYPE_APPLICATION_MEDIA 和 TYPE_APPLICATION_MEDIA_OVERLAY ) 位于父窗口的下面
- 其他类型子窗口位于父窗口上面
确定了主序和子序,之后的过程后面再分析,此处不细纠。
三 Window标志
Window 的标志也就是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,这里我们给出几个比较常用。
Flag | 描述 |
---|---|
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | 只要窗口可见,就允许在开启状态的屏幕上锁屏 |
FLAG_NOT_FOCUSABLE | 窗口不能获得输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置 |
FLAG_NOT_TOUCHABLE | 窗口不接收任何触摸事件 |
FLAG_NOT_TOUCH_MODAL | 在该窗口区域外的触摸事件传递给其他的Window,而自己只会处理窗口区域内的触摸事件 |
FLAG_KEEP_SCREEN_ON | 只要窗口可见,屏幕就会一直亮着 |
FLAG_LAYOUT_NO_LIMITS | 允许窗口超过屏幕之外 |
FLAG_FULLSCREEN | 隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示 |
FLAG_SHOW_WHEN_LOCKED | 窗口可以在锁屏的窗口之上显示 |
FLAG_IGNORE_CHEEK_PRESSES | 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 |
FLAG_TURN_SCREEN_ON | 窗口显示时将屏幕点亮 |
设置 Window 的 Flag 有三种方法,第一种是通过 Window 的 addFlags 方法:
Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
第二种通过 Window 的 setFlags 方法:
Window mWindow = getWindow();
mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
,WindowManager.LayoutParams.FLAG_FULLSCREEN);
其实 Window 的 addFlags 方法内部会调用 setFlags 方法,因此这两种方法区别不大。
第三种则是给 LayoutParams 设置 Flag,并通过 WindowManager 的 addView 方法进行添加,如下所示:
WindowManager.LayoutParams mWindowLayoutParams =
new WindowManager.LayoutParams();
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
WindowManager mWindowManager =
(WindowManager) getSystemService(Context.WINDOW_SERVICE);
TextView mTextView = new TextView(this);
mWindowManager.addView(mTextView,mWindowLayoutParams);
四 软键盘相关模式
窗口和窗口的叠加是非常常见的场景,但如果其中的窗口是软键盘窗口,可能就会出现一些问题,比如典型的用户登录界面,默认的情况弹出的软键盘窗口可能会盖住输入框下方的按钮,这样用户体验会非常糟糕。
为了使得软键盘窗口能够按照期望来显示,WindowManager 的静态内部类 LayoutParams 中定义了软键盘相关模式,这里给出常用的几个:
SoftInputMode | 描述 |
---|---|
SOFT_INPUT_STATE_UNSPECIFIED | 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置 |
SOFT_INPUT_STATE_UNCHANGED | 不会改变软键盘状态 |
SOFT_INPUT_STATE_HIDDEN | 当用户进入该窗口时,软键盘默认隐藏 |
SOFT_INPUT_STATE_ALWAYS_HIDDEN | 当窗口获取焦点时,软键盘总是被隐藏 |
SOFT_INPUT_ADJUST_RESIZE | 当软键盘弹出时,窗口会调整大小 |
SOFT_INPUT_ADJUST_PAN | 当软键盘弹出时,窗口不需要调整大小,要确保输入焦点是可见的 |
从上面给出的 SoftInputMode,可以发现,它们与 AndroidManifest 中 Activity 的属性 android:windowSoftInputMode 是对应的。因此,除了在 AndroidMainfest 中为 Activity 设置 android:windowSoftInputMode 以外还可以在 Java 代码中为 Window 设置 SoftInputMode:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
五 常见Window
App 开发中常见的涉及 Window 的地方:Activity / Dialog / PopupWindow / Toast / 输入法。
通过查看相关源码,得出如下结论:
Activity:创建 PhoneWindow,类型为 TYPE_BASE_APPLICATION
Dialog:创建 PhoneWindow,未指定类型
PopupWindow:未创建 Window,类型为 TYPE_APPLICATION_PANEL
Toast:类型为 TYPE_TOAST
输入法(IMM):类型为 TYPE_INPUT_METHOD