.hd-box .hd-fr

你知道 Android 是如何管理复杂的 Window 层级的?

2022-10-17 16:13TechMerger(Jingle Zhang)38评

App 开发者的不知有没有发现,StatusBar 一直是盖在 App 上面,不管是修改颜色,或者是写悬浮框,都无法盖住 StatusBar。

framework 开发,会出现一些定制,如盖住 StatusBar,不了解的可能用错,出现一些不必要的 bug,官方文档也没有列出 Window 层级的规则。

所以希望通过下文给大家分享,Android 是如何制定显示层级规则的。

大概说下 Window 在 Android 中的概念

其实也可以好理解,和经常使用 Windows 操作系统一样,打开一个应用,出现界面,我们可以理解出现了一个窗口,所以 Window ≠ View。

一个 Activity 可以理解 对应一个 Window,理解源码的同学知道:ViewRootImpl 是对应一个 Window。

怎么看 Window 呢?

adb shell dumpsys window抽取了几个典型的Window如下:Window #2 Window{911875c u0 NavigationBar0}://导航栏  ty=NAVIGATION_BAR  isOnScreen=true  isVisible=trueWindow #4 Window{bf1a956 u0 StatusBar}://状态栏  ty=STATUS_BAR  isOnScreen=true  isVisible=trueWindow #11 Window{d377ae1 u0 InputMethod}://输入法,不显示  ty=INPUT_METHOD  isOnScreen=false  isVisible=falseWindow #12 Window{e190206 u0 com.android.settings/com.android.settings.Settings}://打开 App activity  ty=BASE_LICATION  isOnScreen=true  isVisible=trueWindow #16 Window{abcabb9 u0 com.android.systemui.ImageWallpaper}://壁纸  ty=WALLPAPER  isOnScreen=false  isVisible=false

一般手机都会存在以上 Window,层级顺序从高 -> 低。

显示 PopWindow

 Window #11 Window{513f711 u0 PopupWindow:3e4bfb}:   ty=LICATION_SUB_PANEL   isOnScreen=true   sVisible=true

显示 Dialog

Window #11 Window{a08f90b }:  ty=LICATION  isOnScreen=true  isVisible=true

不难看出,Window 层级与 ty 有关系的,ty 是 type 的简写。

Window 的分类

Application Window:应用程序窗口

type 取值范围 [1,99]

/** * Start of window types that represent normal lication windows. */public static final int FIRST_LICATION_WINDOW = 1;// activity 会使用 此 typepublic static final int TYPE_BASE_LICATION   = 1;// dialog 会使用 此 typepublic static final int TYPE_LICATION        = 2;// 冷启动会显示的 Window,真正启动页面显示之前的画面public static final int TYPE_LICATION_STARTING = 3;// 没玩过public static final int TYPE_DRAWN_LICATION = 4;/** * End of types of lication windows. */public static final int LAST_LICATION_WINDOW = 99;

Sub Window:子窗口

子窗口:顾名思义,对应有主窗口。子窗口需要附在主窗口上,如 PopWindow

type 取值范围 [1000,1999]

/** * Start of types of sub-windows.  The {@link #token} of these windows * must be set to the window they are attached to.  These types of * windows are kept next to their attached window in Z-order, and their * coordinate space is relative to their attached window. */public static final int FIRST_SUB_WINDOW = 1000;public static final int TYPE_LICATION_PANEL = FIRST_SUB_WINDOW;public static final int TYPE_LICATION_MEDIA = FIRST_SUB_WINDOW + 1;public static final int TYPE_LICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;public static final int TYPE_LICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;public static final int TYPE_LICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;public static final int TYPE_LICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;/** * End of types of sub-windows. */public static final int LAST_SUB_WINDOW = 1999;

System Window :系统窗口

type 取值范围 [2000,2999]

如 Toast,ANR 窗口,输入法,StatusBar,NavigationBar 等。

/** * Start of system-specific window types.  These are not normally * created by lications. */public static final int FIRST_SYSTEM_WINDOW     = 2000;public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;/** * End of types of system windows. */public static final int LAST_SYSTEM_WINDOW      = 2999;

之前好像看过文章说 type 值越大,层级越高,这个观点是错的

具体层级是下面逻辑代码,返回值越大,层级越高,最终在屏幕上显示时就越靠近用户。

frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java/** * Returns the layer assignment for the window type. Allows you to control how different * kinds of windows are ordered on-screen. * * @param type The type of window being assigned. * @param canAddInternalSystemWindow If the owner window associated with the type we are *        evaluating can add internal system windows. I.e they have *        {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window *        types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)} *        can be assigned layers greater than the layer for *        {@link android.view.WindowManager.LayoutParams#TYPE_LICATION_OVERLAY} Else, their *        layers would be lesser. * @param roundedCornerOverlay {#code true} to indicate that the owner window is rounded corner *                             overlay. * @return int An arbitrary integer used to order windows, with lower numbers below higher ones. */default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,        boolean roundedCornerOverlay) {    // Always put the rounded corner layer to the top most.    if (roundedCornerOverlay && canAddInternalSystemWindow) {        return getMaxWindowLayer();    }    if (type >= FIRST_LICATION_WINDOW && type <= LAST_LICATION_WINDOW) {        return LICATION_LAYER;// LICATION_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:        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 s.            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 /  crashed dialogs            // Type is deprecated for non-system s. For system s, this type should be            // in a higher layer than TYPE_LICATION_OVERLAY.            return  canAddInternalSystemWindow ? 13 : 10;        case TYPE_LICATION_OVERLAY:            return  12;        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_ADDITIONAL:            return  18;        case TYPE_NOTIFICATION_SHADE:            return  19;        case TYPE_STATUS_BAR_SUB_PANEL:            return  20;        case TYPE_KEYGUARD_DIALOG:            return  21;        case TYPE_VOLUME_OVERLAY:            // the on-screen volume indicator and controller shown when the user            // changes the device volume            return  22;        case TYPE_SYSTEM_OVERLAY:            // the on-screen volume indicator and controller shown when the user            // changes the device volume            return  canAddInternalSystemWindow ? 23 : 11;        case TYPE_NAVIGATION_BAR:            // the navigation bar, if available, shows atop most things            return  24;        case TYPE_NAVIGATION_BAR_PANEL:            // some panels (e.g. search) need to show on top of the navigation bar            return  25;        case TYPE_SCREENSHOT:            // screenshot selection layer shouldn't go above system error, but it should cover            // navigation bars at the very least.            return  26;        case TYPE_SYSTEM_ERROR:            // system-level error dialogs            return  canAddInternalSystemWindow ? 27 : 10;        case TYPE_MAGNIFICATION_OVERLAY:            // used to highlight the magnified portion of a display            return  28;        case TYPE_DISPLAY_OVERLAY:            // used to simulate secondary display devices            return  29;        case TYPE_DRAG:            // the drag layer: input for drag-and-drop is associated with this window,            // which sits above all other focusable windows            return  30;        case TYPE_ACCESSIBILITY_OVERLAY:            // overlay put by accessibility services to intercept user interaction            return  31;        case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:            return 32;        case TYPE_SECURE_SYSTEM_OVERLAY:            return  33;        case TYPE_BOOT_PROGRESS:            return  34;        case TYPE_POINTER:            // the (mouse) pointer layer            return  35;        default:            Slog.e("WindowManager", "Unknown window type: " + type);            return 3;    }}

以上方法,返回 layer,type -> layer,以上代码可以得到如下信息。

layer 取值范围 【1,36】

App 对应 APPLICATION_LAYER,值为 2,仅比 TYPE_WALLPAPER 大

Window 层级具体是怎么计算的呢?

从 System Window 中 基本已经找到答案。本章节具体说下实现细节:

mBaseLayer & mSubLayer

用来计算层级的两个参数

mSubLayer:用来计算子窗口的层级,默认值为 0

mBaseLayer:用来计算主窗口的层级。

frameworks/base/services/core/java/com/android/server/wm/WindowState.javaif mAttrs.type = FIRST_SUB_WINDOW && mAttrs.type = LAST_SUB_WINDOW {    mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)            * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;// layer * 10000 + 1000    mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);} else {    mBaseLayer = mPolicy.getWindowLayerLw(this)            * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;// layer * 10000 + 1000    mSubLayer = 0;}

WindowState 中 mBaseLayer,mSubLayer

mBaseLayer:主窗口的 type 对应 value ,计算如下

如 Activity,type 是 TYPE_BASE_APPLICATION ,getWindowLayerLw 计算返回 APPLICATION_LAYER(2),mBaseLayer = 2 * 10000 + 1000

TYPE_LAYER_MULTIPLIER:为什么要 * 10000,将阈值扩大 10000 倍,系统中可能存在相同类型的窗口有很多。

TYPE_LAYER_OFFSET:为了移动同一层级的一组窗口

以上两个常量具体怎么使用,没有研究,该值不影响本文的分析。

mSubLayer:计算规则如下,取值范围 [-2,3],TYPE_APPLICATION_ATTACHED_DIALOG 值为 1,APPLICATION_MEDIA_SUBLAYER 值为 -2,看到这里就可以想到子窗口是可以在主窗口下方,主窗口如果可以看到子窗口,必须透明。

frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.javadefault int getSubWindowLayerFromTypeLw(int type) {    switch (type) {        case TYPE_LICATION_PANEL:        case TYPE_LICATION_ATTACHED_DIALOG:            return LICATION_PANEL_SUBLAYER;// 1        case TYPE_LICATION_MEDIA:            return LICATION_MEDIA_SUBLAYER;// -2        case TYPE_LICATION_MEDIA_OVERLAY:            return LICATION_MEDIA_OVERLAY_SUBLAYER;// -1        case TYPE_LICATION_SUB_PANEL:            return LICATION_SUB_PANEL_SUBLAYER; // 2        case TYPE_LICATION_ABOVE_SUB_PANEL:            return LICATION_ABOVE_SUB_PANEL_SUBLAYER;// 3    }    Slog.e("WindowManager", "Unknown sub-window type: " + type);    return 0;}

Sub Window 排序

frameworks/base/services/core/java/com/android/server/wm/WindowState.java/** * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms * of z-order and 1 otherwise. */private static final Comparator<WindowState> sWindowSubLayerComparator =        new Comparator<WindowState>() {            @Override            public int compare(WindowState w1, WindowState w2) {                final int layer1 = w1.mSubLayer;                final int layer2 = w2.mSubLayer;                if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {                    // We insert the child window into the list ordered by                    // the sub-layer.  For same sub-layers, the negative one                    // should go below others; the positive one should go                    // above others.                    return -1;                }                return 1;            };        };

根据上文 mSubLayer 的值排序,如果是新插入的 window ,如果 sublayer 相等且为负值,放在下方,如果 sublayer 相等且为正值,放在上方。

主 Window 排序

frameworks/base/services/core/java/com/android/server/wm/WindowToken.java/** * Compares two child window of this token and returns -1 if the first is lesser than the * second in terms of z-order and 1 otherwise. */private final Comparator<WindowState> mWindowComparator =        (WindowState newWindow, WindowState existingWindow) -> {    final WindowToken token = WindowToken.this;    if (newWindow.mToken != token) {        throw new IllegalArgumentException("newWindow=" + newWindow                + " is not a child of token=" + token);    }    if (existingWindow.mToken != token) {        throw new IllegalArgumentException("existingWindow=" + existingWindow                + " is not a child of token=" + token);    }    return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;};protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,            WindowState existingWindow) {        // New window is considered greater if it has a higher or equal base layer.        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;}

与 Sub Window 排序类似,按照 mBaseLayer 大小排序,如果是新插入的,且相等,放在上方。

总结

主 window 排序图示

子 window 排序图示

本文来自微信公众号:TechMerger (ID:ELC-XTLS-QSW),作者:Jingle Zhang

广告声明:文内含有的对外跳转链接(包括不限于超链接、二维码、口令等形式),用于传递更多信息,节省甄选时间,结果仅供参考,IT之家所有文章均包含本声明。

下载IT之家APP,分享赚金币换豪礼
大家都在买广告
热门评论
查看更多评论