Android窗口管理4 Window属性

一 概述

之前我们分析了 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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值