0xA07 Android 10 源碼分析:Window 的類型 以及 三維視圖層級分析

引言

  • 這是 Android 10 源碼分析系列的第 7 篇
  • 分支:android-10.0.0_r14
  • 全文閱讀大概 10 分鐘

在以前的文章 0xA06 Android 10 源碼分析:WindowManager 視圖綁定以及體系結構 介紹了 Activity、Window、PhoneWindow、WindowManager 之間的關係,以及 Activity 和 Dialog 的視圖綁定過程,而這篇文章主要兩個目的:html

  1. 對上一篇文章 0xA06 Android 10 源碼分析:WindowManager 視圖綁定以及體系結構 作深刻的瞭解
  2. 爲後面的篇文章「如何在 Andorid 系統裏添加自定義 View」等等作好鋪墊

經過這篇文章你將學習到如下內容,將在文末總結部分會給出相應的答案java

  • Window 都有那些經常使用的參數?
  • Window 都那些類型?每一個類型的意思?以及做用?
  • Window 那些過期的 API 以及處理方案?
  • Window 視圖層級順序是如何肯定的?
  • Window 都那些 flag?每一個 flag 的意思?以及做用?
  • Window 的軟鍵盤模式?每一個模式的意思?以及如何使用?
  • Kotlin 小技巧?

在開始分析以前,咱們先來看一張圖,熟悉一下幾個基本概念,這些概念伴將隨着整篇文章android

Window 視圖層級順序

  • 咱們在手機上看到的界面是二維的,可是其實是一個三維,如上圖所示
  • Window:是一個抽象類,它做爲一個頂級視圖添加到 WindowManager 中,View 是依附於 Window 而存在的,對 View 進行管理
  • WindowManager:它是一個接口,繼承自接口 ViewManager,對 Window 進行管理
  • PhoneWindow:Window 惟一實現類,添加到 WindowManager 的根容器中
  • WindowManagerService:WindowManager 是 Window 的容器,管理着 Window,對 Window 進行添加和刪除,最終具體的工做都是由 WindowManagerService 來處理的,WindowManager 和 WindowManagerService 經過 Binder 來進行跨進程通訊,WindowManagerService 纔是 Window 的最終管理者

這篇文章重要知識點是 Window 視圖層級順序是如何肯定的,其餘內容都是一些概念的東西,能夠選擇性的閱讀,瞭解完基本概念以後,進入這篇文章的核心內容,咱們先來了解一下 Window 都有那些經常使用的參數git

Window 都有那些經常使用的參數

Window 的參數都被定義在 WindowManager 的靜態內部類 LayoutParams 中
frameworks/base/core/java/android/view/WindowManager#LayoutParams.javagithub

public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    // window 左上角的 x 座標
    public int x;
    
    // window 左上角的 y 座標
    public int y;
    
    // Window 的類型
    public int type;
    
    // Window 的 flag 用於控制 Window 的顯示
    public int flags;
    
    // window 軟鍵盤輸入區域的顯示模式
    public int softInputMode;
    
    // window 的透明度,取值爲0-1
    public float alpha = 1.0f;
    
    // window 在屏幕中的位置
    public int gravity;
    
    // window 的像素點格式,值定義在 PixelFormat 中
    public int format;
}
複製代碼

接下來咱們咱們主要來介紹一下 Window 的類型、Window 視圖層級順序、Window 的 flag、和 window 軟鍵盤模式面試

Window 都那些類型以及做用

Window 的類型大概能夠分爲三類: 應用程序 Window(Application Window)、子 Window(Sub Windwow)、系統 Window(System Window), Window 的類型經過 type 值來表示,每一個大類型又包含多個小類型,它們都定義在 WindowManager 的靜態內部類 LayoutParams
frameworks/base/core/java/android/view/WindowManager#LayoutParams.java算法

public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    public int type;
    
    // 應用程序 Window 的開始值
    public static final int FIRST_APPLICATION_WINDOW = 1;
    // 應用程序 Window 的結束值
    public static final int LAST_APPLICATION_WINDOW = 99;
    
    // 子 Window 類型的開始值
    public static final int FIRST_SUB_WINDOW = 1000;
    // 子 Window 類型的結束值
    public static final int LAST_SUB_WINDOW = 1999;
    
    // 系統 Window 類型的開始值
    public static final int FIRST_SYSTEM_WINDOW     = 2000;   
    // 系統 Window 類型的結束值
    public static final int LAST_SYSTEM_WINDOW      = 2999;
}
複製代碼
類型 備註
FIRST_APPLICATION_WINDOW 1 應用程序 Window 的開始值
LAST_APPLICATION_WINDOW 99 應用程序 Window 的結束值
FIRST_SUB_WINDOW 1000 子 Window 的開始值
LAST_SUB_WINDOW 1999 子 Window 的結束值
FIRST_SYSTEM_WINDOW 2000 系統 Window 的開始值
LAST_SYSTEM_WINDOW 2999 系統 Window 的結束值

小技巧:若是是層級在 2000(FIRST_SYSTEM_WINDOW)如下的是不須要申請彈窗權限的編程

  • 應用程序 Window(Application Window):它的區間範圍 [1,99],例如 Activity
    frameworks/base/core/java/android/view/WindowManager#LayoutParams.java
// 應用程序 Window 的開始值
public static final int FIRST_APPLICATION_WINDOW = 1;

// 應用程序 Window 的基礎值
public static final int TYPE_BASE_APPLICATION   = 1;

// 普通的應用程序
public static final int TYPE_APPLICATION        = 2;

// 特殊的應用程序窗口,當程序能夠顯示 Window 以前使用這個 Window 來顯示一些東西
public static final int TYPE_APPLICATION_STARTING = 3;

// TYPE_APPLICATION 的變體,在應用程序顯示以前,WindowManager 會等待這個 Window 繪製完畢
public static final int TYPE_DRAWN_APPLICATION = 4;

// 應用程序 Window 的結束值
public static final int LAST_APPLICATION_WINDOW = 99;
複製代碼
類型
備註
FIRST_APPLICATION_WINDOW 應用程序 Window 的開始值
TYPE_BASE_APPLICATION 應用程序 Window 的基礎值
TYPE_APPLICATION 普通的應用程序
TYPE_APPLICATION_STARTING 特殊的應用程序窗口,當程序能夠顯示 Window 以前使用這個
Window 來顯示一些東西
TYPE_DRAWN_APPLICATION TYPE_APPLICATION 的變體 在應用程序顯示以前,
WindowManager 會等待這個 Window 繪製完畢
LAST_APPLICATION_WINDOW 應用程序 Window 的結束值
  • 子 Window(Sub Windwow):它的區間範圍 [1000,1999],這些 Window 按照 Z-order 順序依附於父 Window 上(關於 Z-order 後文有介紹),而且他們的座標空間相對於父 Window 的,例如:PopupWindow
    frameworks/base/core/java/android/view/WindowManager#LayoutParams.java
// 子 Window 類型的開始值
public static final int FIRST_SUB_WINDOW = 1000;

// 應用程序 Window 頂部的面板。這些 Window 出如今其附加 Window 的頂部。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

// 用於顯示媒體(如視頻)的 Window。這些 Window 出如今其附加 Window 的後面。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

// 應用程序 Window 頂部的子面板。這些 Window 出如今其附加 Window 和任何Window的頂部
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

// 當前Window的佈局和頂級Window佈局相同時,不能做爲子代的容器
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

// 用顯示媒體 Window 覆蓋頂部的 Window, 這是系統隱藏的 API
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

// 子面板在應用程序Window的頂部,這些Window顯示在其附加Window的頂部, 這是系統隱藏的 API
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

// 子 Window 類型的結束值
public static final int LAST_SUB_WINDOW = 1999;
複製代碼
類型
備註
FIRST_SUB_WINDOW 子 Window 的開始值
TYPE_APPLICATION_PANEL 應用程序 Window 頂部的面板,這些 Window 出如今其附加 Window 的頂部
TYPE_APPLICATION_MEDIA 用於顯示媒體(如視頻)的 Window,這些 Window 出如今其附加 Window 的後面
TYPE_APPLICATION_SUB_PANEL 應用程序 Window 頂部的子面板,這些 Window 出如今其附加 Window 和任何Window的頂部
TYPE_APPLICATION_ATTACHED_DIALOG 當前Window的佈局和頂級Window佈局相同時,不能做爲子代的容器
TYPE_APPLICATION_MEDIA_OVERLAY 用顯示媒體 Window 覆蓋頂部的 Window, 這是系統隱藏的 API
TYPE_APPLICATION_ABOVE_SUB_PANEL 子面板在應用程序Window的頂部,這些Window顯示在其附加Window的頂部, 這是系統隱藏的 API
LAST_SUB_WINDOW 子 Window 的結束值
  • 系統 Window(System Window): 它區間範圍 [2000,2999],例如:Toast,輸入法窗口,系統音量條窗口,系統錯誤窗口
    frameworks/base/core/java/android/view/WindowManager#LayoutParams.java
// 系統Window類型的開始值
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;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

// 已經從系統中被移除,可使用 TYPE_KEYGUARD_DIALOG 代替
public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;

// 系統對話框窗口
public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;

// 鎖屏時顯示的對話框
public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;

@Deprecated
// API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;

// 輸入法窗口,位於普通 UI 之上,應用程序可從新佈局以避免被此窗口覆蓋
public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;

// 輸入法對話框,顯示於當前輸入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;

// 牆紙
public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;

// 狀態欄的滑動面板
public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;

// 應用程序疊加窗口顯示在全部窗口之上
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;

// 系統Window類型的結束值
public static final int LAST_SYSTEM_WINDOW      = 2999;
複製代碼
類型
備註
FIRST_SYSTEM_WINDOW 系統 Window 類型的開始值
TYPE_STATUS_BAR 系統狀態欄,只能有一個狀態欄,它被放置在屏幕的頂部,全部其餘窗口都向下移動
TYPE_SEARCH_BAR 系統搜索窗口,只能有一個搜索欄,它被放置在屏幕的頂部
TYPE_PHONE API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_SYSTEM_ALERT API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_KEYGUARD 已經從系統中被移除,可使用 TYPE_KEYGUARD_DIALOG 代替
TYPE_TOAST API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_SYSTEM_OVERLAY API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_PRIORITY_PHONE API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_SYSTEM_ERROR API 已通過時,用 TYPE_APPLICATION_OVERLAY 代替
TYPE_APPLICATION_OVERLAY 應用程序疊加窗口顯示在全部窗口之上
TYPE_SYSTEM_DIALOG 系統對話框窗口
TYPE_KEYGUARD_DIALOG 鎖屏時顯示的對話框
TYPE_INPUT_METHOD 輸入法窗口,位於普通 UI 之上,應用程序可從新佈局以避免被此窗口覆蓋
TYPE_INPUT_METHOD_DIALOG 輸入法對話框,顯示於當前輸入法窗口之上
TYPE_WALLPAPER 牆紙
TYPE_STATUS_BAR_PANEL 狀態欄的滑動面板
LAST_SYSTEM_WINDOW 系統 Window 類型的結束值

須要注意的是:數組

  1. TYPE_PHONE、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_SYSTEM_OVERLAY、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ERROR 這些 type 在 API 26 中均已通過時,使用 TYPE_APPLICATION_OVERLAY 代替,須要申請 Manifest.permission.SYSTEM_ALERT_WINDOW 權限性能優化

  2. TYPE_KEYGUARD 已經被從系統中移除,可使用 TYPE_KEYGUARD_DIALOG 來代替

Window 視圖層級順序

咱們在手機上看的是二維的,可是其實是三維的顯示,以下圖所示

640

在文章開頭介紹了參數類型包含了 Window 的 x 軸座標、Window 的 y 軸座標, 既然是一個三維座標系,那麼 z 軸座標在哪裏? 接下來就是咱們要分析的很是重要的一個類 WindowManagerService,當添加 Window 的時候已經肯定好了 Window 的層級,顯示的時候纔會根據當前的層級肯定 Window 應該在哪一層顯示

WindowManager 是 Window 的容器,管理着 Window,對 Window 進行添加和刪除,具體的工做都是由 WMS 來處理的,WindowManager 和 WMS 經過 Binder 來進行跨進程通訊,WMS 纔是 Window 的最終管理者,我先來看一下 WMS 的 addWindow 方法
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState) {
        
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
            appOp[0], seq, attrs, viewVisibility, session.mUid,
            session.mCanAddInternalSystemWindow);
            ......
            
            win.mToken.addWindow(win);
            ......
            
            win.getParent().assignChildLayers();
            ......
     
}
複製代碼
  • WindowState 計算當前 Window 層級
  • win.mToken.addWindow 這個方法將當前的 win 放入 WindowList 中,WindowList 是一個 ArrayList
  • displayContent.assignWindowLayers 方法 計算 z-order 值, z-order 值越大越靠前,就越靠近用戶

Window 視圖層級順序 用 Z-order 來表示,Z-order 對應着 WindowManager.LayoutParams 的 type 值,Z-order 能夠理解爲 Android 視圖的層級概念,值越大越靠前,就越靠近用戶。

WindowState 就是 windowManager 中的窗口,一個 WindowState 表示一個 window

那麼 Z-order 的值的計算邏輯在 WindowState 類中,WindowState 構造的時候初始化當前的 mBaseLayer 和 mSubLayer,這兩個參數應該是決定 z-order 的兩個因素
frameworks/base/services/core/java/com/android/server/wm/WindowState.java

static final int TYPE_LAYER_MULTIPLIER = 10000;
static final int TYPE_LAYER_OFFSET = 1000;
    
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
        WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
        int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
        PowerManagerWrapper powerManagerWrapper) {
      
        // 判斷該是否在子 Window 的類型範圍內[1000,1999]
        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {

            // 調用 getWindowLayerLw 方法返回值在[1,33]之間,根據不一樣類型的 Window 在屏幕上進行排序
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;

            // mSubLayer 子窗口的順序
            // 調用 getSubWindowLayerFromTypeLw 方法返回值在[-2.3]之間 ,返回子 Window 相對於父 Window 的位置
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
            ......
            
        } else {
           
            mBaseLayer = mPolicy.getWindowLayerLw(this)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = 0;
            ......
            
        }  
}
複製代碼
  • mBaseLayer 是基礎序,對應的區間範圍 [1,33]
  • mSubLayer 相同分組下的子 Window 的序,對應的區間範圍 [-2.3]
  • 判斷該是否在子 Window 的類型範圍內[1000,1999]
  • 若是是子 Window,調用 getWindowLayerLw 方法,計算 mBaseLayer 的值,返回一個用來對 Window 進行排序的任意整數,調用 getSubWindowLayerFromTypeLw 方法,計算 mSubLayer 的值,返回子 Window 相對於父 Window 的位置
  • 若是不是子 Window,調用 getWindowLayerLw 方法,計算 mBaseLayer 的值,返回一個用來對 Window 進行排序的任意整數,mSubLayer 值爲 0

計算 mBaseLayer 的值

調用 WindowManagerPolicy 的 getWindowLayerLw 方法,計算 mBaseLayer 的值
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java

int APPLICATION_LAYER = 2;
int APPLICATION_MEDIA_SUBLAYER = -2;
int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
int APPLICATION_PANEL_SUBLAYER = 1;
int APPLICATION_SUB_PANEL_SUBLAYER = 2;
int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
 
/**
* 根據不一樣類型的 Window 在屏幕上進行排序
* 返回一個用來對窗口進行排序的任意整數,數字越小,表示的值越小
*/   
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
    // 判斷是否在應用程序 Window 類型的取值範圍內 [1,99]
    if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
        return APPLICATION_LAYER;
    }


    switch (type) {
        case TYPE_WALLPAPER: // 壁紙,經過 window manager 刪除它
            return  1;
        case TYPE_PHONE: // 電話
            return  3;
        case TYPE_SEARCH_BAR: // 搜索欄
            return  6;
        case TYPE_SYSTEM_DIALOG: // 系統的 dialog
            return  7;
        case TYPE_TOAST: // 系統 toast
            return  8;
        case TYPE_INPUT_METHOD: // 輸入法
            return  15;
        case TYPE_STATUS_BAR: // 狀態欄
            return  17;
        case TYPE_KEYGUARD_DIALOG: //鎖屏
            return  20;
        ......
        
        case TYPE_POINTER:
            // the (mouse) pointer layer
            return  33;
        default:
            return APPLICATION_LAYER;
    }
}
複製代碼

根據不一樣類型的 Window 在屏幕上進行排序,返回一個用來對 Window 進行排序的任意整數,數字越小,表示的值越小,經過如下公式來計算它的基礎序 ,基礎序越大,Z-order 值越大越靠前,就越靠近用戶,咱們以 Activity 爲例:

Activity 屬於應用層 Window,它的取值範圍在 [1,99] 內,調用 getWindowLayerLw 方法返回 APPLICATION_LAYER,APPLICATION_LAYER 值爲 2,經過下面方法進行計算

static final int TYPE_LAYER_MULTIPLIER = 10000;
static final int TYPE_LAYER_OFFSET = 1000;

mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
複製代碼

那麼最終 Activity 的 mBaseLayer 值是 21000

計算 mSubLayer 的值

調用 getSubWindowLayerFromTypeLw 方法 ,傳入 WindowManager.LayoutParams 的實例 a 的 type 值,計算 mSubLayer 的值
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java

int APPLICATION_LAYER = 2;
int APPLICATION_MEDIA_SUBLAYER = -2;
int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
int APPLICATION_PANEL_SUBLAYER = 1;
int APPLICATION_SUB_PANEL_SUBLAYER = 2;
int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;

/**
* 計算 Window 相對於父 Window 的位置
* 返回 一個整數,正值在前面,表示在父 Window 上面,負值在後面,表示在父 Window 的下面
*/
default int getSubWindowLayerFromTypeLw(int type) {
    switch (type) {
        case TYPE_APPLICATION_PANEL: // 1000
        case TYPE_APPLICATION_ATTACHED_DIALOG: // 1003
            return APPLICATION_PANEL_SUBLAYER; // return 1
        case TYPE_APPLICATION_MEDIA:// 1001
            return APPLICATION_MEDIA_SUBLAYER;// return -2
        case TYPE_APPLICATION_MEDIA_OVERLAY:
            return APPLICATION_MEDIA_OVERLAY_SUBLAYER; // return -1
        case TYPE_APPLICATION_SUB_PANEL:// 1002
            return APPLICATION_SUB_PANEL_SUBLAYER;// return 2
        case TYPE_APPLICATION_ABOVE_SUB_PANEL:
            return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;// return 3
    }
    return 0;
}
複製代碼

計算子 Window 相對於父 Window 的位置,返回一個整數,正值表示在父 Window 上面,負值表示在父 Window 的下面

Window 的 flag

Window 的 flag 用於控制 Window 的顯示,它們的值也是定義在 WindowManager 的內部類 LayoutParams 中
frameworks/base/core/java/android/view/WindowManager#LayoutParams.java

// 當 Window 可見時容許鎖屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;

// Window 後面的內容都變暗
public static final int FLAG_DIM_BEHIND        = 0x00000002;

@Deprecated
// API 已通過時,Window 後面的內容都變模糊
public static final int FLAG_BLUR_BEHIND        = 0x00000004;

// Window 不能得到輸入焦點,即不接受任何按鍵或按鈕事件,例如該 Window 上 有 EditView,點擊 EditView 是 不會彈出軟鍵盤的
// Window 範圍外的事件依舊爲原窗口處理;例如點擊該窗口外的view,依然會有響應。另外只要設置了此Flag,都將會啓用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;

// 設置了該 Flag,將 Window 以外的按鍵事件發送給後面的 Window 處理, 而本身只會處理 Window 區域內的觸摸事件
// Window 以外的 view 也是能夠響應 touch 事件。
public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;

// 設置了該Flag,表示該 Window 將不會接受任何 touch 事件,例如點擊該 Window 不會有響應,只會傳給下面有聚焦的窗口。
public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;

// 只要 Window 可見時屏幕就會一直亮着
public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;

// 容許 Window 佔滿整個屏幕
public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;

// 容許 Window 超過屏幕以外
public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;

// 全屏顯示,隱藏全部的 Window 裝飾,好比在遊戲、播放器中的全屏顯示
public static final int FLAG_FULLSCREEN      = 0x00000400;

// 表示比FLAG_FULLSCREEN低一級,會顯示狀態欄
public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;

// 當用戶的臉貼近屏幕時(好比打電話),不會去響應此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;

// 則當按鍵動做發生在 Window 以外時,將接收到一個MotionEvent.ACTION_OUTSIDE事件。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;

@Deprecated
// 窗口能夠在鎖屏的 Window 之上顯示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

// 表示負責繪製系統欄背景。若是設置,系統欄將以透明背景繪製,
// 此 Window 中的相應區域將填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的顏色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

// 表示要求系統壁紙顯示在該 Window 後面,Window 表面必須是半透明的,才能真正看到它背後的壁紙
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
複製代碼
flag
備註
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 當 Window 可見時容許鎖屏
FLAG_DIM_BEHIND Window 後面的內容都變暗
FLAG_BLUR_BEHIND API 已通過時,Window 後面的內容都變模糊
FLAG_NOT_FOCUSABLE Window 不能得到輸入焦點,即不接受任何按鍵或按鈕事件,例如該 Window 上 有 EditView,點擊 EditView 是 不會彈出軟鍵盤的,Window 範圍外的事件依舊爲原窗口處理;例如點擊該窗口外的view,依然會有響應。另外只要設置了此Flag,都將會啓用FLAG_NOT_TOUCH_MODAL
FLAG_NOT_TOUCH_MODAL 設置了該 Flag,將 Window 以外的按鍵事件發送給後面的 Window 處理, 而本身只會處理 Window 區域內的觸摸事件,Window 以外的 view 也是能夠響應 touch 事件
FLAG_NOT_TOUCHABLE 設置了該Flag,表示該 Window 將不會接受任何 touch 事件,例如點擊該 Window 不會有響應,只會傳給下面有聚焦的窗口
FLAG_KEEP_SCREEN_ON 只要 Window 可見時屏幕就會一直亮着
FLAG_LAYOUT_IN_SCREEN 容許 Window 佔滿整個屏幕
FLAG_LAYOUT_NO_LIMITS 容許 Window 超過屏幕以外
FLAG_FULLSCREEN 全屏顯示,隱藏全部的 Window 裝飾,好比在遊戲、播放器中的全屏顯示
FLAG_FORCE_NOT_FULLSCREEN 表示比FLAG_FULLSCREEN低一級,會顯示狀態欄
FLAG_IGNORE_CHEEK_PRESSES 當用戶的臉貼近屏幕時(好比打電話),不會去響應此事件
FLAG_WATCH_OUTSIDE_TOUCH 則當按鍵動做發生在 Window 以外時,將接收到一個MotionEvent.ACTION_OUTSIDE事件
FLAG_SHOW_WHEN_LOCKED 已通過時,窗口能夠在鎖屏的 Window 之上顯示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 表示負責繪製系統欄背景。若是設置,系統欄將以透明背景繪製,
此 Window 中的相應區域將填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的顏色
FLAG_SHOW_WALLPAPER 表示要求系統壁紙顯示在該 Window 後面,Window 表面必須是半透明的,才能真正看到它背後的壁紙

window 軟鍵盤模式

表示 window 軟鍵盤輸入區域的顯示模式,常見的狀況 Window 的軟鍵盤打開會佔據整個屏幕,遮擋了後面的視圖,例如看直播的時候底部有個輸入框點擊的時候,輸入框隨着鍵盤一塊兒上來,而有的時候,但願鍵盤覆蓋在全部的 View 之上,界面保持不動等等

軟鍵盤模式(SoftInputMode) 值,與 AndroidManifest 中 Activity 的屬性 android:windowSoftInputMode 是對應的,所以能夠在 AndroidManifest 文件中爲 Activity 設置android:windowSoftInputMode

<activity android:windowSoftInputMode="adjustNothing" />
複製代碼

也能夠在 Java 代碼中爲 Window 設置 SoftInputMode

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
複製代碼

SoftInputMode 經常使用的有如下幾個值

// 不會改變軟鍵盤的狀態
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

// 當用戶進入該窗口時,隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_HIDDEN = 2;

// 當窗口獲取焦點時,隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;

// 當用戶進入窗口時,顯示軟鍵盤
public static final int SOFT_INPUT_STATE_VISIBLE = 4;

// 當窗口獲取焦點時,顯示軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

// window會調整大小以適應軟鍵盤窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

// 沒有指定狀態,系統會選擇一個合適的狀態或依賴於主題的設置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;

// 當軟鍵盤彈出時,窗口會調整大小,例如點擊一個EditView,整個layout都將平移可見且處於軟件盤的上方
// 一樣的該模式不能與SOFT_INPUT_ADJUST_PAN結合使用;
// 若是窗口的佈局參數標誌包含FLAG_FULLSCREEN,則將忽略這個值,窗口不會調整大小,但會保持全屏。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

// 當軟鍵盤彈出時,窗口不須要調整大小, 要確保輸入焦點是可見的,
// 例若有兩個EditView的輸入框,一個爲Ev1,一個爲Ev2,當你點擊Ev1想要輸入數據時,當前的Ev1的輸入框會移到軟鍵盤上方
// 該模式不能與SOFT_INPUT_ADJUST_RESIZE結合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;

// 將不會調整大小,直接覆蓋在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
複製代碼
model
備註
SOFT_INPUT_STATE_UNCHANGED 不會改變軟鍵盤的狀態
SOFT_INPUT_STATE_VISIBLE 當用戶進入窗口時,顯示軟鍵盤
SOFT_INPUT_STATE_HIDDEN 當用戶進入該窗口時,隱藏軟鍵盤
SOFT_INPUT_STATE_ALWAYS_HIDDEN 當窗口獲取焦點時,隱藏軟鍵盤
SOFT_INPUT_STATE_ALWAYS_VISIBLE 當窗口獲取焦點時,顯示軟鍵盤
SOFT_INPUT_MASK_ADJUST window 會調整大小以適應軟鍵盤窗口
SOFT_INPUT_ADJUST_UNSPECIFIED 沒有指定狀態,系統會選擇一個合適的狀態或依賴於主題的設置
SOFT_INPUT_ADJUST_RESIZE 1. 當軟鍵盤彈出時,窗口會調整大小,例如點擊一個EditView,整個layout都將平移可見且處於軟件盤的上方
2. 一樣的該模式不能與SOFT_INPUT_ADJUST_PAN結合使用
3. 若是窗口的佈局參數標誌包含FLAG_FULLSCREEN,則將忽略這個值,窗口不會調整大小,但會保持全屏
SOFT_INPUT_ADJUST_PAN 1. 當軟鍵盤彈出時,窗口不須要調整大小, 要確保輸入焦點是可見的
2. 例若有兩個EditView的輸入框,一個爲Ev1,一個爲Ev2,當你點擊Ev1想要輸入數據時,當前的Ev1的輸入框會移到軟鍵盤上方
3. 該模式不能與SOFT_INPUT_ADJUST_RESIZE結合使用
SOFT_INPUT_ADJUST_NOTHING 將不會調整大小,直接覆蓋在window上

Kotlin 小技巧

利用 plus (+) 和 plus (-) 對 Map 集合作運算,以下所示:

fun main() {
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)

    // plus (+)
    println(numbersMap + Pair("four", 4)) // {one=1, two=2, three=3, four=4}
    println(numbersMap + Pair("one", 10)) // {one=10, two=2, three=3}
    println(numbersMap + Pair("five", 5) + Pair("one", 11)) // {one=11, two=2, three=3, five=5}

    // plus (-)
    println(numbersMap - "one") // {two=2, three=3}
    println(numbersMap - listOf("two", "four")) // {one=1, three=3}
}
複製代碼

總結

到這裏就結束了,這篇文章主要介紹了 Window 的類型大概能夠分爲三類: 應用程序 Window(Application Window)、子 Window(Sub Windwow)、系統 Window(System Window)。 分別介紹了 Window 的類型、Window 視圖層級順序、Window 的 flag、和 window 軟鍵盤模式爲後面的內容作鋪墊

Window 都有那些經常使用的參數?

參數 備註
x window 左上角的 x 座標
y window 左上角的 y 座標
type Window 的類型
flag Window 的 flag 用於控制 Window 的顯示
softInputMode window 軟鍵盤輸入區域的顯示模式
alpha Window 的透明度,取值爲0-1
gravity Window 在屏幕中的位置
alpha Window 的透明度,取值爲0-1
format Window 的像素點格式,值定義在 PixelFormat 中

Window 都有那些類型?

應用程序 Window(Application Window)、子 Window(Sub Windwow)、系統 Window(System Window),子 Window 依附於父 Window 上,而且他們的座標空間相對於父 Window 的,每一個大類型又包含多個小類型,每一個類型在上文的表格中已經列出來了,

Window 那些過期的 API 以及處理方案?

  1. TYPE_PHONE、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_SYSTEM_OVERLAY、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ERROR 這些 type 在 API 26 中均已通過時,使用 TYPE_APPLICATION_OVERLAY 代替,須要申請 Manifest.permission.SYSTEM_ALERT_WINDOW 權限

  2. TYPE_KEYGUARD 已經被從系統中移除,可使用 TYPE_KEYGUARD_DIALOG 來代替

Window 視圖層級順序是如何肯定的?

Window 的參數 x、y,分別表示 Window 左上角的 x 座標,Window 左上角的 y 座標,Window 視圖層級順序 用 Z-order 來表示,Z-order 對應着 WindowManager.LayoutParams 的 type 值,Z-order 能夠理解爲 Android 視圖的層級概念,值越大越靠前,就越靠近用戶。而 mBaseLayer 和 mSubLayer 決定 z-order 的兩個因素

Window 都那些 flag?

Window 的 flag 用於控制 Window 的顯示,flag 的參數以下所示:

flag
備註
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 當 Window 可見時容許鎖屏
FLAG_DIM_BEHIND Window 後面的內容都變暗
FLAG_BLUR_BEHIND API 已通過時,Window 後面的內容都變模糊
FLAG_NOT_FOCUSABLE Window 不能得到輸入焦點,即不接受任何按鍵或按鈕事件,例如該 Window 上 有 EditView,點擊 EditView 是 不會彈出軟鍵盤的,Window 範圍外的事件依舊爲原窗口處理;例如點擊該窗口外的view,依然會有響應。另外只要設置了此Flag,都將會啓用FLAG_NOT_TOUCH_MODAL
FLAG_NOT_TOUCH_MODAL 設置了該 Flag,將 Window 以外的按鍵事件發送給後面的 Window 處理, 而本身只會處理 Window 區域內的觸摸事件,Window 以外的 view 也是能夠響應 touch 事件
FLAG_NOT_TOUCHABLE 設置了該Flag,表示該 Window 將不會接受任何 touch 事件,例如點擊該 Window 不會有響應,只會傳給下面有聚焦的窗口
FLAG_KEEP_SCREEN_ON 只要 Window 可見時屏幕就會一直亮着
FLAG_LAYOUT_IN_SCREEN 容許 Window 佔滿整個屏幕
FLAG_LAYOUT_NO_LIMITS 容許 Window 超過屏幕以外
FLAG_FULLSCREEN 全屏顯示,隱藏全部的 Window 裝飾,好比在遊戲、播放器中的全屏顯示
FLAG_FORCE_NOT_FULLSCREEN 表示比FLAG_FULLSCREEN低一級,會顯示狀態欄
FLAG_IGNORE_CHEEK_PRESSES 當用戶的臉貼近屏幕時(好比打電話),不會去響應此事件
FLAG_WATCH_OUTSIDE_TOUCH 則當按鍵動做發生在 Window 以外時,將接收到一個MotionEvent.ACTION_OUTSIDE事件
FLAG_SHOW_WHEN_LOCKED 已通過時,窗口能夠在鎖屏的 Window 之上顯示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 表示負責繪製系統欄背景。若是設置,系統欄將以透明背景繪製,
此 Window 中的相應區域將填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的顏色
FLAG_SHOW_WALLPAPER 表示要求系統壁紙顯示在該 Window 後面,Window 表面必須是半透明的,才能真正看到它背後的壁紙

Window 軟鍵盤模式?

Window 的軟鍵盤模式表示 Window 軟鍵盤輸入區域的顯示模式

model
備註
SOFT_INPUT_STATE_UNCHANGED 不會改變軟鍵盤的狀態
SOFT_INPUT_STATE_VISIBLE 當用戶進入窗口時,顯示軟鍵盤
SOFT_INPUT_STATE_HIDDEN 當用戶進入該窗口時,隱藏軟鍵盤
SOFT_INPUT_STATE_ALWAYS_HIDDEN 當窗口獲取焦點時,隱藏軟鍵盤
SOFT_INPUT_STATE_ALWAYS_VISIBLE 當窗口獲取焦點時,顯示軟鍵盤
SOFT_INPUT_MASK_ADJUST window 會調整大小以適應軟鍵盤窗口
SOFT_INPUT_ADJUST_UNSPECIFIED 沒有指定狀態,系統會選擇一個合適的狀態或依賴於主題的設置
SOFT_INPUT_ADJUST_RESIZE 1. 當軟鍵盤彈出時,窗口會調整大小,例如點擊一個EditView,整個layout都將平移可見且處於軟件盤的上方
2. 一樣的該模式不能與SOFT_INPUT_ADJUST_PAN結合使用
3. 若是窗口的佈局參數標誌包含FLAG_FULLSCREEN,則將忽略這個值,窗口不會調整大小,但會保持全屏
SOFT_INPUT_ADJUST_PAN 1. 當軟鍵盤彈出時,窗口不須要調整大小, 要確保輸入焦點是可見的
2. 例若有兩個EditView的輸入框,一個爲Ev1,一個爲Ev2,當你點擊Ev1想要輸入數據時,當前的Ev1的輸入框會移到軟鍵盤上方
3. 該模式不能與SOFT_INPUT_ADJUST_RESIZE結合使用
SOFT_INPUT_ADJUST_NOTHING 將不會調整大小,直接覆蓋在window上

參考文獻

結語

致力於分享一系列 Android 系統源碼、逆向分析、算法、翻譯、Jetpack 源碼相關的文章,若是你同我同樣喜歡研究 Android 源碼,能夠關注我,若是你喜歡這篇文章歡迎 star,一塊兒來學習,期待與你一塊兒成長

文章列表

算法

因爲 LeetCode 的題庫龐大,每一個分類都能篩選出數百道題,因爲每一個人的精力有限,不可能刷完全部題目,所以我按照經典類型題目去分類、和題目的難易程度去排序

  • 數據結構: 數組、棧、隊列、字符串、鏈表、樹……
  • 算法: 查找算法、搜索算法、位運算、排序、數學、……

每道題目都會用 Java 和 kotlin 去實現,而且每道題目都有解題思路,若是你同我同樣喜歡算法、LeetCode,能夠關注我 GitHub 上的 LeetCode 題解:Leetcode-Solutions-with-Java-And-Kotlin,一塊兒來學習,期待與你一塊兒成長

Android 10 源碼系列

正在寫一系列的 Android 10 源碼分析的文章,瞭解系統源碼,不只有助於分析問題,在面試過程當中,對咱們也是很是有幫助的,若是你同我同樣喜歡研究 Android 源碼,能夠關注我 GitHub 上的 Android10-Source-Analysis,文章都會同步到這個倉庫

Android 應用系列

工具系列

逆向系列

相關文章
相關標籤/搜索