設(shè)置
  • 日夜間
    隨系統(tǒng)
    淺色
    深色
  • 主題色

你知道 Android 是如何管理復雜的 Window 層級的?

TechMerger 2022/10/17 16:13:36 責編:遠生

App 開發(fā)者的不知有沒有發(fā)現(xiàn),StatusBar 一直是蓋在 App 上面,不管是修改顏色,或者是寫懸浮框,都無法蓋住 StatusBar。

framework 開發(fā),會出現(xiàn)一些定制,如蓋住 StatusBar,不了解的可能用錯,出現(xiàn)一些不必要的 bug,官方文檔也沒有列出 Window 層級的規(guī)則。

所以希望通過下文給大家分享,Android 是如何制定顯示層級規(guī)則的。

大概說下 Window 在 Android 中的概念

其實也可以好理解,和經(jīng)常使用 Windows 操作系統(tǒng)一樣,打開一個應用,出現(xiàn)界面,我們可以理解出現(xiàn)了一個窗口,所以 Window ≠ View。

一個 Activity 可以理解 對應一個 Window,理解源碼的同學知道:ViewRootImpl 是對應一個 Window。

怎么看 Window 呢?

adb shell dumpsys window

抽取了幾個典型的Window如下:

Window #2 Window{911875c u0 NavigationBar0}://導航欄
  ty=NAVIGATION_BAR
  isOnScreen=true
  isVisible=true
Window #4 Window{bf1a956 u0 StatusBar}://狀態(tài)欄
  ty=STATUS_BAR
  isOnScreen=true
  isVisible=true
Window #11 Window{d377ae1 u0 InputMethod}://輸入法,不顯示
  ty=INPUT_METHOD
  isOnScreen=false
  isVisible=false
Window #12 Window{e190206 u0 com.android.settings/com.android.settings.Settings}://打開 App activity
  ty=BASE_LICATION
  isOnScreen=true
  isVisible=true
Window #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 有關(guān)系的,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 會使用 此 type
public static final int TYPE_BASE_LICATION   = 1;
// dialog 會使用 此 type
public 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 :系統(tǒng)窗口

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 中 基本已經(jīng)找到答案。本章節(jié)具體說下實現(xiàn)細節(jié):

mBaseLayer & mSubLayer

用來計算層級的兩個參數(shù)

mSubLayer:用來計算子窗口的層級,默認值為 0

mBaseLayer:用來計算主窗口的層級。

frameworks/base/services/core/java/com/android/server/wm/WindowState.java

if 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 倍,系統(tǒng)中可能存在相同類型的窗口有很多。

TYPE_LAYER_OFFSET:為了移動同一層級的一組窗口

以上兩個常量具體怎么使用,沒有研究,該值不影響本文的分析。

mSubLayer:計算規(guī)則如下,取值范圍 [-2,3],TYPE_APPLICATION_ATTACHED_DIALOG 值為 1,APPLICATION_MEDIA_SUBLAYER 值為 -2,看到這里就可以想到子窗口是可以在主窗口下方,主窗口如果可以看到子窗口,必須透明。

frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java

default 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;
            };
        };

根據(jù)上文 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 大小排序,如果是新插入的,且相等,放在上方。

總結(jié)

主 window 排序圖示

子 window 排序圖示

本文來自微信公眾號:TechMerger (ID:ELC-XTLS-QSW),作者:Jingle Zhang

廣告聲明:文內(nèi)含有的對外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。

軟媒旗下網(wǎng)站: IT之家 最會買 - 返利返現(xiàn)優(yōu)惠券 iPhone之家 Win7之家 Win10之家 Win11之家

軟媒旗下軟件: 軟媒手機APP應用 魔方 最會買 要知