天天看點

Android WindowManagerService機制分析:視窗的顯示層級

        WindowManagerService(以下簡稱WMS)是Android Framework中一個重要的系統服務,用來管理系統中視窗(Window)的行為。Window是一個抽象的概念,它是一個矩形區域,用來繪制UI界面,響應使用者的輸入事件。Android系統的界面,從Framework層的角度來看,就是由一個一個視窗組合而成的。

        在WMS中一個重要的功能就是對目前視窗的顯示順序進行排序。但是視窗隻是一個抽象的概念,WMS中所做的事情實際上是根據各種條件計算視窗的顯示層級,然後将這個表示層級的數值傳給SurfaceFlinger,SurfaceFlinger根據這個層級資訊去進行渲染。本文主要對Android 7.0版本中WMS關于計算視窗的顯示層級的相關機制進行簡單地分析。

一. 視窗的主序和次序

        在WMS中,WindowState.java類用于描述視窗的狀态。每一個視窗都對應着一個WindowState執行個體。其中有兩個成員變量用于描述視窗的層級:         mBaseLayer:視窗的主序,根據目前視窗類型定義了一個層級。mBaseLayer越大,視窗及其子視窗的顯示順序越靠前。         mSubLayer:視窗的子序,如果目前視窗是子視窗,決定了該視窗相對于其兄弟視窗(同一父視窗下的其它子視窗)以及父視窗的順序。mSubLayer越大,該視窗相對于其兄弟視窗就越靠前。         這兩個變量在WindowState中是final的,也就是說在構造函數中被初始化了以後,就不能再改變了。上面兩個值都是根據視窗類型計算出來的:(frameworks/base/services/core/java/com/android/server/wm/WindowState.java)

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
       WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
       int viewVisibility, final DisplayContent displayContent) {
    ......
    // 首先根據視窗類型判斷這是不是一個子視窗
    if ((mAttrs.type >= FIRST_SUB_WINDOW &&
            mAttrs.type <= LAST_SUB_WINDOW)) {
        // The multiplier here is to reserve space for multiple
        // windows in the same type layer.
        // 計算主序和子序,注意目前視窗是一個子視窗,其主序應該與其父視窗一緻,是以傳入的是其父視窗(attachWindow)的視窗類型
        mBaseLayer = mPolicy.windowTypeToLayerLw(
                attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
                + WindowManagerService.TYPE_LAYER_OFFSET;
        mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
        ......
    } else {
        // The multiplier here is to reserve space for multiple
        // windows in the same type layer.
        // 目前不是一個子視窗,非子視窗的mSubLayer都為0
        mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
                * WindowManagerService.TYPE_LAYER_MULTIPLIER
                + WindowManagerService.TYPE_LAYER_OFFSET;
        mSubLayer = 0;
        ......
    }
    ......
}
           

        視窗的主序和次序都是通過WMS的政策類(PhoneWindowManager.java)擷取的,其中的邏輯很簡單,就是根據視窗的type傳回一個2 ~ 31的數。根據上面的初始化過程我們可以看到,mBaseLayer的值會根據政策類傳回的值再乘以10000并加上1000計算而成。根據源碼中的注釋:The multiplier here is to reserve space for multiple windows in the same type layer. 我們可以推測,這樣計算的目的是為了區分同一類型的不同視窗的層級。也就是說,主序僅僅是按照視窗類型劃分了一個顯示順序,但是同一類型的視窗不一定隻有一個(計算同類型視窗的顯示層級還需要其它的條件),不同類型視窗的主序相差10000是為了給這些同類型視窗的顯示層級預留的空間。常見的視窗類型的主序如下:

視窗類型 主序
type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW(應用視窗) 21000
TYPE_WALLPAPER(牆紙) 21000
TYPE_PHONE(電話) 31000
TYPE_TOAST(toast) 81000
TYPE_INPUT_METHOD(輸入法) 101000
TYPE_STATUS_BAR(狀态欄) 161000
TYPE_KEYGUARD_DIALOG(鎖屏) 181000

        後面我們會分析同類型的視窗的顯示層級的計算規則,其核心邏輯就是根據主序不停地添加偏移量(5),是以需要預留10000的空間,防止某一視窗的層級超越了比它主序高的類型的視窗(但理論上也隻能保證2000個同類型視窗層級正确,雖然不太可能會有2000個同類型視窗同時存在的情況)。         視窗的子序就沒有這麼複雜了,WMS政策類會根據視窗類型傳回一個-2 ~ 3的數值。父視窗子序永遠為0,小于0的子視窗在父視窗下面,大于0的在父視窗上面。常見的子序如下:

子視窗類型 子序
TYPE_APPLICATION_MEDIA(SurfaceView) -2
TYPE_APPLICATION_PANEL 1
TYPE_APPLICATION_SUB_PANEL 2

二. 确定新視窗的位置

        每當新視窗被添加到WMS時,WMS都會将新建立的WindowState對象添加到其所屬的DisplayContent中的WindowState清單中。這個清單記錄了在目前螢幕下(DisplayContent.java在WMS中抽象地描述了螢幕的相關資訊,預設的就是我們所使用的手機顯示屏)WindowState的顯示順序,這個順序很大程度的上影響了每一個視窗的最終顯示層級,但并不是全部決定條件。下面我們簡單看一下新視窗被添加後,在DisplayContent中的位置是如何确定的。首先我們對整體流程有一個抽象地認識:

Android WindowManagerService機制分析:視窗的顯示層級

        下面根據源代碼對上述流程進行分析:         首先是添加視窗的流程,每當有新視窗被添加時,會調用WMS的addWindow方法:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        InputChannel outInputChannel) {
    ......
    // 經過一些條件的檢查,視窗的WindowState對象被建立,根據attrs.type決定其mBaseLayer以及mSubLayer的值
    WindowState win = new WindowState(this, session, client, token,
            attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
    ......
    // 這裡會對輸入法視窗做特殊的處理,這裡略去,通用的情況是調用此方法對新視窗位置進行插入
    addWindowToListInOrderLocked(win, true);
}
           

        addWindowToListInOrderLocked方法對新視窗進行插入的操作,其流程如上面流程圖所示。下面對照代碼分析一下具體流程: (frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)

private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
    if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win +
            " Callers=" + Debug.getCallers(4));
    // 如果mAttachedWindow是空,則表示該視窗不是一個子視窗
    if (win.mAttachedWindow == null) {
        final WindowToken token = win.mToken;
        // 注意這個變量,它表示該WindowState對象在所屬同一WindowToken的所有WindowState中的位置
        int tokenWindowsPos = 0;
        if (token.appWindowToken != null) {
            // appWindowToken不為空,意味着這是一個activity視窗
            tokenWindowsPos = addAppWindowToListLocked(win);
        } else {
            addFreeWindowToListLocked(win);
        }
        if (addToToken) {
            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token);
            // token.windows就是描述所屬該token下的所有WindowState對象
            // 比如一個activity彈出了一個AlertDialog視窗,這兩個視窗的AppWindowToken是一個
            token.windows.add(tokenWindowsPos, win);
        }
    } else {
        // 這是一個子視窗
        addAttachedWindowToListLocked(win, addToToken);
    }
 
    final AppWindowToken appToken = win.mAppToken;
    if (appToken != null) {
        if (addToToken) {
            appToken.addWindow(win);
        }
    }
}
 
 
// 首先是addAppWindowToListLocked方法,該方法對activity視窗進行了插入
private int addAppWindowToListLocked(final WindowState win) {
    final DisplayContent displayContent = win.getDisplayContent();
    if (displayContent == null) {
        // It doesn't matter this display is going away.
        return 0;
    }
    final IWindow client = win.mClient;
    final WindowToken token = win.mToken;
 
    final WindowList windows = displayContent.getWindowList();
    // 擷取同一DisplayContent中,所屬該WindowToken的所有WindowState
    WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
    int tokenWindowsPos = 0;
    // 如果存在相同的WindowToken的WindowState,則通過addAppWindowToTokenListLocked方法對其進行插入
    // 比如這種情景就會走到這個case:從一個activity中彈出一個popup window
    // 此時這個新的彈窗和activity的視窗屬于同一WindowToken
    if (!tokenWindowList.isEmpty()) {
        // 這個方法下面會分析
        return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList);
    }
 
    // No windows from this token on this display
    if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window " + client.asBinder()
            + " (token=" + token + ")");
    // Figure out where the window should go, based on the
    // order of applications.
    // 這個是要插入位置(原來)的WindowState
    WindowState pos = null;
 
    // 周遊該DisplayContent中所有的Task
    // 注意,流程走到這裡意味這這個視窗是一個activity視窗,是以在此之前,
    // AMS會通過addAppToken方法把其AppWindowToken添加到對應的task中去
    // 此時正常的狀态為:Task中有對應的AppWindowToken,
    // 但是DisplayContent中的WindowList裡面沒有該WindowState
    // 另外說明一點,通過addAppToken添加進來的AppWindowToken會記錄在mTokenMaps中,
    // 在addWindow的過程中,該AppWindowToken會通過mTokenMaps擷取,
    // 并指派給WindowState的mToken成員(AppWindowToken是WindowToken的子類)
    final ArrayList<Task> tasks = displayContent.getTasks();
    int taskNdx;
    int tokenNdx = -1;
    for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
        AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
        for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
            final AppWindowToken t = tokens.get(tokenNdx);
            // 自頂向下周遊,正常情況下第一個就應該是目标的AppWindowToken,
            // 此時會直接跳出循環,pos指向空
            if (t == token) {
                --tokenNdx;
                if (tokenNdx < 0) {
                    // 如果tokenNdx小于0,意味着已經到該task的最後一個了,
                    // 再插入的位置應該是下一個task的頂部
                    --taskNdx;
                    if (taskNdx >= 0) {
                        tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
                    }
                }
                break;
            }
 
            // We haven't reached the token yet; if this token
            // is not going to the bottom and has windows on this display, we can
            // use it as an anchor for when we do reach the token.
            // 如果第一個不是,那麼pos就指向目前周遊到AppWindowToken所屬的最下面一個
            // 的WindowState(目前activity中最後一個window)
            // 這樣做的目的是這樣,activity的WindowState的位置實際上要和activity的位置一緻
            // 如果下一輪疊代找到了對應的AppWindowToken,那麼它應該被插入在它上面的AppWindowToken
            // 的最後一個視窗的下面pos就指向了這個位置目前的WindowState,
            // 後面新的WindowState就要被插入到這個位置
            tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
            if (!t.sendingToBottom && tokenWindowList.size() > 0) {
                pos = tokenWindowList.get(0);
            }
        }
        if (tokenNdx >= 0) {
            // early exit
            break;
        }
    }
 
    // We now know the index into the apps.  If we found
    // an app window above, that gives us the position; else
    // we need to look some more.
    // 不為空的話則表示要插入的WindowState的AppWindowToken不在頂部
    // 需要插入到它上面一個AppWindowToken所屬的最下面的一個WindowState的後面
    if (pos != null) {
        // Move behind any windows attached to this one.
        WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
        if (atoken != null) {
            tokenWindowList =
                    getTokenWindowsOnDisplay(atoken, displayContent);
            final int NC = tokenWindowList.size();
            if (NC > 0) {
                WindowState bottom = tokenWindowList.get(0);
                if (bottom.mSubLayer < 0) {
                    // 確定pos指向的是上面AppWindowToken的最後一個WindowState
                    pos = bottom;
                }
            }
        }
        // 插入到DisplayContent所記錄順序的WindowList中
        placeWindowBefore(pos, win);
        // 傳回該WindowState在其WindowToken中所有WindowState的位置
        return tokenWindowsPos;
    }
 
    // Continue looking down until we find the first
    // token that has windows on this display.
    // 流程到這裡,意味這要插入的WindowState所對應的activity是在top task的頂部(大部分情況是這種)
    for ( ; taskNdx >= 0; --taskNdx) {
        AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
        for ( ; tokenNdx >= 0; --tokenNdx) {
            // 這裡就是要看,到底要插入到誰的上面
            // 理論上是插入在目前task的頂部WindowState的上面,但是有可能此時的activity是首次啟動,
            // task裡面沒有WindowState
            // 這種情況下的狀态為:Task裡僅僅有準備添加的這個WindowState的AppWindowToken,
            // 但是該AppWindowToken沒有屬于它的WindowState
            //(注意新Window還未添加進去,此方法的傳回值是新WindowState在AppWindowToken中的位置,
            // 然後才會添加進去),是以此時pos仍然為空
            // 但是當Task中有其他的AppWindowToken(app内部啟動新的activity,并且不加NEW_TASK的flag)時,
            // pos指向的是該Task内最上面的WindowState
            final AppWindowToken t = tokens.get(tokenNdx);
            tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
            final int NW = tokenWindowList.size();
            if (NW > 0) {
                pos = tokenWindowList.get(NW-1);
                break;
            }
        }
        if (tokenNdx >= 0) {
            // found
            break;
        }
    }
 
    // 如果pos不為空,則指向的是該Task最上面的WindowState,新的WindowState即将插入到它上面
    if (pos != null) {
        // Move in front of any windows attached to this
        // one.
        WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
        if (atoken != null) {
            final int NC = atoken.windows.size();
            if (NC > 0) {
                WindowState top = atoken.windows.get(NC-1);
                if (top.mSubLayer >= 0) {
                    // 這裡同上,確定pos是頂部的WindowState
                    pos = top;
                }
            }
        }
        placeWindowAfter(pos, win);
        return tokenWindowsPos;
    }
 
    // Just search for the start of this layer.
    // 走到這裡,意味着該WindowState是新啟動的一個activity的第一個視窗(新task的第一個WindowState)
    // 由于沒有可參考的activity視窗,是以需要通過mBaseLayer去插入
    // 很明顯,它将被插入到所有應用視窗的頂部
    final int myLayer = win.mBaseLayer;
    int i;
    for (i = windows.size() - 1; i >= 0; --i) {
        WindowState w = windows.get(i);
        // Dock divider shares the base layer with application windows, but we want to always
        // keep it above the application windows. The sharing of the base layer is intended
        // for window animations, which need to be above the dock divider for the duration
        // of the animation.
        // 分屏的DOCK_DIVIDER(分屏模式下中間那個黑杆,mBaseLayer也是21000,但是要保證它在所有應用視窗的上面)
        if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
            break;
        }
    }
    if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
            "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
                    + windows.size());
    windows.add(i + 1, win);
    mWindowsChanged = true;
    return tokenWindowsPos;
}
 
 
// 如果新添加的WindowState所對應的AppWindowToken已經有其它的WindowState存在了,
// 則需要通過addAppWindowToTokenListLocked方法插入
// 這裡可以這樣了解,要啟動的視窗所屬的activity之前已經存在了,
// activity的所有視窗(即我們上面一直說的所屬同一AppWindowToken的WindowState)
// 順序必然是連續的,是以此時新啟動的WindowState必定要插入在這其中的某一個位置
// 注意,流程到這裡是,已經判斷過了該WindowState不是一個子視窗,是以不能用mSubLayer判斷位置
private int addAppWindowToTokenListLocked(WindowState win, WindowToken token,
        WindowList windows, WindowList tokenWindowList) {
    int tokenWindowsPos;
    // If this application has existing windows, we
    // simply place the new window on top of them... but
    // keep the starting window on top.
    if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
        // 如果新視窗是TYPE_BASE_APPLICATION類型,則需要插入在該AppWindowToken所有視窗的最底部
        // Base windows go behind everything else.
        WindowState lowestWindow = tokenWindowList.get(0);
        placeWindowBefore(lowestWindow, win);
        tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
    } else {
        AppWindowToken atoken = win.mAppToken;
        final int windowListPos = tokenWindowList.size();
        WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
        if (atoken != null && lastWindow == atoken.startingWindow) {
            // 如果存在starting window,則插入到starting window的下面
            placeWindowBefore(lastWindow, win);
            tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
        } else {
            // 否則,插入到該AppWindowToken所有視窗的最頂部
            int newIdx = findIdxBasedOnAppTokens(win);
            //there is a window above this one associated with the same
            //apptoken note that the window could be a floating window
            //that was created later or a window at the top of the list of
            //windows associated with this token.
            if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
                    "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
                            + windows.size());
            windows.add(newIdx + 1, win);
            if (newIdx < 0) {
                // No window from token found on win's display.
                tokenWindowsPos = 0;
            } else {
                tokenWindowsPos = indexOfWinInWindowList(
                        windows.get(newIdx), token.windows) + 1;
            }
            mWindowsChanged = true;
        }
    }
    return tokenWindowsPos;
}
           

        上面分析了應用視窗(token.appWindowToken != null)的情況,如果一個視窗不屬于一個activity,則需要通過addFreeWindowToListLocked方法對其進行插入。這個插入的依據就是視窗的mBaseLayer,除了牆紙和輸入法視窗有一些特殊處理外,通用的插入原則就死插入到第一個mBaseLayer小于等于它的視窗的上面。最後是對子視窗進行插入的操作,通過addAttachedWindowToListLocked插入,主要是根據mSubLayer進行排序:父視窗的mBaseLayer為0,其它為兄弟視窗,大于0的在父視窗上面,越大越靠上;小于0的在父視窗下面,越小越靠下。依據這個邏輯進行插入,具體的代碼邏輯比較簡單,不在此處贅述。         簡單地總結一下上面分析的排序規則:         1. 非應用視窗一句mBaseLayer插入,越高越靠前(牆紙和輸入法有特殊處理)         2. 應用視窗依據activity的位置插入,應該被插入在其activity所在task的頂部(通常情況)或者該activity上面的activity的最後一個視窗的下面(分屏模式會遇到這種情況)         3. 子視窗依據mSubLayer插入 最終插入的地方有兩個:DisplayContent所持有的記錄該螢幕下所有視窗順序的WindowList,以及新視窗的WindowToken所記錄的所有屬于它的WindowState清單(WindowList)。

三. 應用視窗的重排序

        應用視窗的重排序是一個被頻繁觸發的機制,因為系統中應用視窗的位置會頻繁的變化,這個機制(rebuildAppWindowListLocked被調用)確定了應用視窗順序的正确性。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)

private void rebuildAppWindowListLocked(final DisplayContent displayContent) {
    final WindowList windows = displayContent.getWindowList();
    int NW = windows.size();
    int i;
    // 記錄了自底向上第一個應用視窗下面的第一個視窗的位置
    int lastBelow = -1;
    int numRemoved = 0;
 
    if (mRebuildTmp.length < NW) {
        mRebuildTmp = new WindowState[NW+10];
    }
 
    // First remove all existing app windows.
    // 周遊所有的WindowState,把應用視窗全部移除
    i=0;
    while (i < NW) {
        WindowState w = windows.get(i);
        if (w.mAppToken != null) {
            WindowState win = windows.remove(i);
            win.mRebuilding = true;
            mRebuildTmp[numRemoved] = win;
            mWindowsChanged = true;
            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win);
            NW--;
            numRemoved++;
            continue;
        } else if (lastBelow == i-1) {
            // 根據PhoneWindowManager裡面windowTypeToLayerLw方法我們可以知道,
            // 應用視窗的主序是最低的
            // 僅有牆紙視窗有可能在應用視窗下面
            if (w.mAttrs.type == TYPE_WALLPAPER) {
                lastBelow = i;
            }
        }
        i++;
    }
 
    // Keep whatever windows were below the app windows still below,
    // by skipping them.
    lastBelow++;
    i = lastBelow;
 
    // First add all of the exiting app tokens...  these are no longer
    // in the main app list, but still have windows shown.  We put them
    // in the back because now that the animation is over we no longer
    // will care about them.
    // 首先把準備退出的視窗重新添加進來,這個mExitingAppTokens在removeAppToken時被記錄
    // 也就是說,即将退出的視窗都往下面放
    final ArrayList<TaskStack> stacks = displayContent.getStacks();
    final int numStacks = stacks.size();
    for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
        AppTokenList exitingAppTokens = stacks.get(stackNdx).mExitingAppTokens;
        int NT = exitingAppTokens.size();
        for (int j = 0; j < NT; j++) {
            i = reAddAppWindowsLocked(displayContent, i, exitingAppTokens.get(j));
        }
    }
 
    // And add in the still active app tokens in Z order.
    // 接着把非退出的視窗按照Z order重新添加回去,實際上有就是根據activity的順序添加
    for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
        final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
        final int numTasks = tasks.size();
        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
            final int numTokens = tokens.size();
            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                final AppWindowToken wtoken = tokens.get(tokenNdx);
                if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) {
                    continue;
                }
                i = reAddAppWindowsLocked(displayContent, i, wtoken);
            }
        }
    }
 
    // 輸出一些日志資訊
    ......
}
           

        上述方法抽象出來主要的行為是:

        1. 移除所有應用視窗

        2. 把即将退出的視窗先插入回去

        3. 把非退出的視窗按照Z order(應用視窗就是根據activity的順序)插入到即将退出的視窗的上面

        真正插入的方法是reAddAppWindowsLocked:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)

private final int reAddAppWindowsLocked(final DisplayContent displayContent, int index,
                                        WindowToken token) {
    final int NW = token.windows.size();
    // 由于每次傳入的都是一個WindowToken,是以這個方法需要把該WindowToken下的所有視窗都插入回去
    for (int i=0; i<NW; i++) {
        // 這裡擷取到屬于該WindowToken的所有WindowState
        // 注意,子視窗是不在裡面的,是以在接下來的過程中還會通過reAddWindowLocked方法
        // 對每一個視窗的子視窗進行插入的操作
        final WindowState win = token.windows.get(i);
        final DisplayContent winDisplayContent = win.getDisplayContent();
        if (winDisplayContent == displayContent || winDisplayContent == null) {
            win.mDisplayContent = displayContent;
            index = reAddWindowLocked(index, win);
        }
    }
    return index;
}
 
 
// 該方法将傳入的WindowState以及它所有子視窗都插入回去,插入的依據仍然是mSubLayer
// 傳進來的參數win代表着父視窗
private final int reAddWindowLocked(int index, WindowState win) {
    final WindowList windows = win.getWindowList();
    // Adding child windows relies on mChildWindows being ordered by mSubLayer.
    final int NCW = win.mChildWindows.size();
    boolean winAdded = false;
    for (int j=0; j<NCW; j++) {
        WindowState cwin = win.mChildWindows.get(j);
        // 自底向上周遊,如果第一個視窗的mSubLayer就大于等于0,那麼就意味着父視窗就在最下面
        if (!winAdded && cwin.mSubLayer >= 0) {
            // 此時cwin就是父視窗,也就是傳進來的參數win
            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding child window at "
                    + index + ": " + cwin);
            win.mRebuilding = false;
            // 把父視窗插入回去
            windows.add(index, win);
            index++;
            winAdded = true;
        }
        // 按原來的順序繼續插入回去
        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at "
                + index + ": " + cwin);
        cwin.mRebuilding = false;
        windows.add(index, cwin);
        index++;
    }
    // winAdded表示父視窗是否被插入回去,如果為false,
    // 則要把父視窗插入到其它子視窗的上面(理論上不應該出現這種情況)
    if (!winAdded) {
        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at "
                + index + ": " + win);
        win.mRebuilding = false;
        windows.add(index, win);
        index++;
    }
    mWindowsChanged = true;
    return index;
}
           

        在調整一些特殊視窗的位置時也會觸發此機制,因為某些特殊視窗有可能夾在兩個應用視窗之間(輸入法視窗),一旦影響到了應用視窗的順序,就需要通過此機制更新應用視窗的順序。

四. 确定視窗的顯示層級

        DisplayContent中的WindowList的順序并不是視窗最終的顯示順序,真正的顯示層級還需要根據一些其它因素進行計算。為每一個視窗賦予顯示層級的方法為WindowLayersController.java中的assignLayersLocked方法。在addWindow時,視窗的順序在DisplayContent中确定了以後就會通過此方法為新視窗賦予顯示層級。這個方法也會被頻繁地調用,上面我們也提到,最終的顯示層級除了跟視窗的順序有關,還跟一些其它因素有關,比如視窗動畫的參數。是以其顯示層級并不是确定後一直不變的,動畫行為也會通過assignLayersLocked更新視窗顯示層級。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)

// 這裡傳入的就是DisplayContent的WindowList
final void assignLayersLocked(WindowList windows) {
    if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
            new RuntimeException("here").fillInStackTrace());
 
    clear();
    int curBaseLayer = 0;
    int curLayer = 0;
    boolean anyLayerChanged = false;
    for (int i = 0, windowCount = windows.size(); i < windowCount; i++) {
        final WindowState w = windows.get(i);
        boolean layerChanged = false;
 
        int oldLayer = w.mLayer;
        // 自底向上周遊,第一個視窗curLayer和mBaseLayer相等
        // 每往上疊代一次,周遊到的視窗的curLayer就添加數值為5的偏移量
        if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) {
            curLayer += WINDOW_LAYER_MULTIPLIER;
        } else {
            curBaseLayer = curLayer = w.mBaseLayer;
        }
        // 更新該視窗的mAnimLayer,也就是動畫層級(可以了解為動畫顯示時,該視窗的層級)
        assignAnimLayer(w, curLayer);
 
        // TODO: Preserved old behavior of code here but not sure comparing
        // oldLayer to mAnimLayer and mLayer makes sense...though the
        // worst case would be unintentionalp layer reassignment.
        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
            layerChanged = true;
            anyLayerChanged = true;
        }
 
        // 記錄目前應用視窗的最高顯示層級
        if (w.mAppToken != null) {
            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
                    w.mWinAnimator.mAnimLayer);
        }
        // 記錄一些特殊視窗,比如分屏相關的視窗,它們的顯示層級有特殊要求
        collectSpecialWindows(w);
 
        // 和Dim Layer有關,視窗顯示層級發生變化時,需要對Dim Layer進行排程
        if (layerChanged) {
            w.scheduleAnimationIfDimming();
        }
    }
 
    // 周遊結束,調整特殊視窗的層級
    adjustSpecialWindows();
 
    //TODO (multidisplay): Magnification is supported only for the default display.
    if (mService.mAccessibilityController != null && anyLayerChanged
            && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
        mService.mAccessibilityController.onWindowLayersChangedLocked();
    }
 
    if (DEBUG_LAYERS) logDebugLayers(windows);
}
 
 
// 這是一個關鍵的方法,用于更新視窗動畫層級
private void assignAnimLayer(WindowState w, int layer) {
    // 傳入的layer就是剛才計算的curLayer,也就是基于mBaseLayer以偏移量5遞增的數值
    // 如果不受動畫參數影響(w.getAnimLayerAdjustment()),
    // 并且它不是特殊視窗(getSpecialWindowAnimLayerAdjustment(w),輸入法和牆紙)
    // 那麼視窗的顯示層級和視窗的順序是一緻的(相鄰視窗的mLayer差5)
    w.mLayer = layer;
    w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
                getSpecialWindowAnimLayerAdjustment(w);
    if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0
            && w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
        // 這個是用于過渡動畫的,因為某些特殊類型的過渡動畫會在動畫過程中建立一塊新的Surface用于配合動畫
        // 但是這個Surface沒有在WMS中以Window的形式展現,
        // 其顯示屬性都要有AppWindowAnimator自己控制(直接對Surface操作)
        // 這裡就是更新了這種Surface的層級屬性
        w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
    }
}
           

        上面提到了兩種特殊情況,會對視窗的顯示層級有調整。一種是特殊視窗(輸入法和牆紙,這裡不做介紹),另一種是動畫:

過渡動畫可以通過Animation.java中的setZAdjustment方法調整視窗在過渡動畫期間的層級:(frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java)

public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
        int stackClip) {
    ......
    // 這裡會獲得通過Animaion.java的setZAdjustment設定的zorder類型
    int zorder = anim.getZAdjustment();
    int adj = 0;
    // ZORDER_TOP則會+1000,ZORDER_BOTTOM則會-1000
    if (zorder == Animation.ZORDER_TOP) {
        adj = TYPE_LAYER_OFFSET;
    } else if (zorder == Animation.ZORDER_BOTTOM) {
        adj = -TYPE_LAYER_OFFSET;
    }
 
    if (animLayerAdjustment != adj) {
        // 上面getAnimLayerAdjustment獲得的就是這個值
        animLayerAdjustment = adj;
        // 這裡會通知一些特殊的地方,動畫層級改變:比如牆紙
        updateLayers();
    }
    ......
}
           

        接下來需要調整一些特殊視窗的層級:(frameworks/base/services/core/java/com/android/server/wm/WindowLayerController.java)

private void adjustSpecialWindows() {
    // 這個初始化的值表示了目前最高的應用視窗層級+5
    int layer = mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER;
    // For pinned and docked stack window, we want to make them above other windows also when
    // these windows are animating.
    // Docked Stack(分屏模式下的上半屏視窗)中的視窗需要現實在所有應用視窗之上,這裡層級被重新調整了
    while (!mDockedWindows.isEmpty()) {
        // assignAndIncreaseLayerIfNeeded方法會通過assignAnimLayer更新該window的mLayer以及mAnimLayer
        // 更新完後,layer會再次+5
        layer = assignAndIncreaseLayerIfNeeded(mDockedWindows.remove(), layer);
    }
 
    // 接下來更新分屏杆的視窗,它需要在所有應用視窗之上,是以需要先更新Docked Stack裡面的應用視窗
    // 在此方法中,越靠後更新的視窗,layer越高
    layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
 
    // 調整輸入法與分屏杆的層級關系
    if (mDockDivider != null && mDockDivider.isVisibleLw()) {
        while (!mInputMethodWindows.isEmpty()) {
            final WindowState w = mInputMethodWindows.remove();
            // Only ever move IME windows up, else we brake IME for windows above the divider.
            if (layer > w.mLayer) {
                layer = assignAndIncreaseLayerIfNeeded(w, layer);
            }
        }
    }
 
    // We know that we will be animating a relaunching window in the near future, which will
    // receive a z-order increase. We want the replaced window to immediately receive the same
    // treatment, e.g. to be above the dock divider.
    while (!mReplacingWindows.isEmpty()) {
        layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
    }
 
    // 這是一種特殊模式的視窗,類似于畫中畫模式
    while (!mPinnedWindows.isEmpty()) {
        layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
    }
}
           

        至此,WMS中層級資訊已經計算完畢,接下來會向SurfaceFlinger更新。上層傳遞視窗的顯示層級的數值實際上是mAnimLayer,也就是動畫層級。

        每當某個視窗的過渡動畫執行到最後一幀時,用于動畫過程中調整層級的偏移量(animLayerAdjustment)會被置0,對應的mAnimLayer也會重新計算。是以動畫層級的偏移量僅僅會在過渡動畫(普通的視窗動畫沒有這個動畫層級偏移量)過程中影響視窗層級。

繼續閱讀