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之家所有文章均包含本聲明。