Android解析WindowManagerService(二)WMS的重要成員和Window的新增過程

劉望舒發表於2019-02-25

相關文章
Android系統啟動系列
Android深入四大元件系列
Android應用程式啟動過程系列
Android解析WindowManager系列

前言

在本系列的上一篇文章中,我們學習了WMS的誕生,WMS被建立後,它的重要的成員有哪些?Window新增過程的WMS部分做了什麼呢?這篇文章會給你解答。

1.WMS的重要成員

所謂WMS的重要成員是指WMS中的重要的成員變數,如下所示。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    final WindowManagerPolicy mPolicy;
    final IActivityManager mActivityManager;
    final ActivityManagerInternal mAmInternal;
    final AppOpsManager mAppOps;
    final DisplaySettings mDisplaySettings;
    ...
    final ArraySet<Session> mSessions = new ArraySet<>();
    final WindowHashMap mWindowMap = new WindowHashMap();
    final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
    final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
    final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
    final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
    WindowState[] mPendingRemoveTmp = new WindowState[20];
    final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
    final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
    ...
    final H mH = new H();
    ...
    final WindowAnimator mAnimator;
    ...
     final InputManagerService mInputManager複製程式碼

這裡列出了WMS的部分成員變數,下面分別對它們進行簡單的介紹。

mPolicy:WindowManagerPolicy
WindowManagerPolicy(WMP)型別的變數。WindowManagerPolicy是視窗管理策略的介面類,用來定義一個視窗策略所要遵循的通用規範,並提供了WindowManager所有的特定的UI行為。它的具體實現類為PhoneWindowManager,這個實現類在WMS建立時被建立。WMP允許定製視窗層級和特殊視窗型別以及關鍵的排程和佈局。

mSessions:ArraySet
ArraySet型別的變數,元素型別為Session。在Android解析WindowManager(三)Window的新增過程這篇文章中我提到過Session,它主要用於程式間通訊,其他的應用程式程式想要和WMS程式進行通訊就需要經過Session,並且每個應用程式程式都會對應一個Session,WMS儲存這些Session用來記錄所有向WMS提出視窗管理服務的客戶端。
mWindowMap:WindowHashMap
WindowHashMap型別的變數,WindowHashMap繼承了HashMap,它限制了HashMap的key值的型別為IBinder,value值的型別為WindowState。WindowState用於儲存視窗的資訊,在WMS中它用來描述一個視窗。綜上得出結論,mWindowMap就是用來儲存WMS中各種視窗的集合。

mFinishedStarting:ArrayList
ArrayList型別的變數,元素型別為AppWindowToken,它是WindowToken的子類。要想理解mFinishedStarting的含義,需要先了解WindowToken是什麼。WindowToken主要有兩個作用:

  • 可以理解為視窗令牌,當應用程式想要向WMS申請新建立一個視窗,則需要向WMS出示有效的WindowToken。AppWindowToken作為WindowToken的子類,主要用來描述應用程式的WindowToken結構,
    應用程式中每個Activity都對應一個AppWindowToken。
  • WindowToken會將相同元件(比如Acitivity)的視窗(WindowState)集合在一起,方便管理。

mFinishedStarting就是用於儲存已經完成啟動的應用程式視窗(比如Acitivity)的AppWindowToken的列表。
除了mFinishedStarting,還有類似的mFinishedEarlyAnim和mWindowReplacementTimeouts,其中mFinishedEarlyAnim儲存了已經完成視窗繪製並且不需要展示任何已儲存surface的應用程式視窗的AppWindowToken。mWindowReplacementTimeout儲存了等待更換的應用程式視窗的AppWindowToken,如果更換不及時,舊視窗就需要被處理。

mResizingWindows:ArrayList
ArrayList型別的變數,元素型別為WindowState。
mResizingWindows是用來儲存正在調整大小的視窗的列表。與mResizingWindows類似的還有mPendingRemove、mDestroySurface和mDestroyPreservedSurface等等。其中mPendingRemove是在記憶體耗盡時設定的,裡面存有需要強制刪除的視窗。mDestroySurface裡面存有需要被Destroy的Surface。mDestroyPreservedSurface裡面存有視窗需要儲存的等待銷燬的Surface,為什麼視窗要儲存這些Surface?這是因為當視窗經歷Surface變化時,視窗需要一直保持舊Surface,直到新Surface的第一幀繪製完成。

mAnimator:WindowAnimator
WindowAnimator型別的變數,用於管理視窗的動畫以及特效動畫。

mH:H
H型別的變數,系統的Handler類,用於將任務加入到主執行緒的訊息佇列中,這樣程式碼邏輯就會在主執行緒中執行。

mInputManager:InputManagerService
InputManagerService型別的變數,輸入系統的管理者。InputManagerService(IMS)會對觸控事件進行處理,它會尋找一個最合適的視窗來處理觸控反饋資訊,WMS是視窗的管理者,因此,WMS“理所應當”的成為了輸入系統的中轉站,WMS包含了IMS的引用不足為怪。

2.Window的新增過程(WMS部分)

我們知道Window的操作分為兩大部分,一部分是WindowManager處理部分,另一部分是WMS處理部分,如下所示。


Android解析WindowManager(三)Window的新增過程這篇文章中,我講解了Window的新增過程的WindowManager處理部分,這一篇文章我們接著來學習Window的新增過程的WMS部分。
無論是系統視窗還是Activity,它們的Window的新增過程都會呼叫WMS的addWindow方法,由於這個方法程式碼邏輯比較多,這裡分為3個部分來閱讀。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

addWindow方法part1

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {

        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);//1
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            ...
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {//3
                parentWindow = windowForClientLocked(null, attrs.token, false);//4
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
           ...
}
...
}複製程式碼

WMS的addWindow返回的是addWindow的各種狀態,比如新增Window成功,無效的display等等,這些狀態被定義在WindowManagerGlobal中。
註釋1處根據Window的屬性,呼叫WMP的checkAddPermission方法來檢查許可權,具體的實現在PhoneWindowManager的checkAddPermission方法中,如果沒有許可權則不會執行後續的程式碼邏輯。註釋2處通過displayId來獲得視窗要新增到哪個DisplayContent上,如果沒有找到DisplayContent,則返回WindowManagerGlobal.ADD_INVALID_DISPLAY這一狀態,其中DisplayContent用來描述一塊螢幕。註釋3處,type代表一個視窗的型別,它的數值介於FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間(1000~1999),這個數值定義在WindowManager中,說明這個視窗是一個子視窗,不瞭解視窗型別取值範圍的請閱讀Android解析WindowManager(二)Window的屬性這篇文章。註釋4處,attrs.token是IBinder型別的物件,windowForClientLocked方法內部會根據attrs.token作為key值從mWindowMap中得到該子視窗的父視窗。接著對父視窗進行判斷,如果父視窗為null或者type的取值範圍不正確則會返回錯誤的狀態。

addWindow方法part2

   ...
            AppWindowToken atoken = null;
            final boolean hasParent = parentWindow != null;
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);//1
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;//2
            boolean addToastWindowRequiresToken = false;

            if (token == null) {
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                ...
                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);//3
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {//4
                atoken = token.asAppWindowToken();//5
                if (atoken == null) {
                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
            } else if (rootType == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            }
      ...複製程式碼

註釋1處通過displayContent的getWindowToken方法來得到WindowToken。註釋2處,如果有父視窗就將父視窗的type值賦值給rootType,如果沒有將當前視窗的type值賦值給rootType。接下來如果WindowToken為null,則根據rootType或者type的值進行區分判斷,如果rootType值等於TYPE_INPUT_METHOD、TYPE_WALLPAPER等值時,則返回狀態值WindowManagerGlobal.ADD_BAD_APP_TOKEN,說明rootType值等於TYPE_INPUT_METHOD、TYPE_WALLPAPER等值時是不允許WindowToken為null的。通過多次的條件判斷篩選,最後會在註釋3處隱式建立WindowToken,這說明當我們新增視窗時是可以不向WMS提供WindowToken的,前提是rootType和type的值不為前面條件判斷篩選的值。WindowToken隱式和顯式的建立肯定是要加以區分的,註釋3處的第4個引數為false就代表這個WindowToken是隱式建立的。接下來的程式碼邏輯就是WindowToken不為null的情況,根據rootType和type的值進行判斷,比如在註釋4處判斷如果視窗為應用程式視窗,在註釋5處會將WindowToken轉換為專門針對應用程式視窗的AppWindowToken,然後根據AppWindowToken的值進行後續的判斷。

addWindow方法part3

   ...
  final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);//1
            if (win.mDeathRecipient == null) {//2
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

            if (win.getDisplayContent() == null) {//3
                Slog.w(TAG_WM, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            mPolicy.adjustWindowParamsLw(win.mAttrs);//4
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
            res = mPolicy.prepareAddWindowLw(win, attrs);//5
            ...
            win.attach();
            mWindowMap.put(client.asBinder(), win);//6
            if (win.mAppOp != AppOpsManager.OP_NONE) {
                int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                        win.getOwningPackage());
                if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                        (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                    win.setAppOpVisibilityLw(false);
                }
            }

            final AppWindowToken aToken = token.asAppWindowToken();
            if (type == TYPE_APPLICATION_STARTING && aToken != null) {
                aToken.startingWindow = win;
                if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                        + " startingWindow=" + win);
            }

            boolean imMayMove = true;
            win.mToken.addWindow(win);//7
             if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                setInputMethodWindowLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
                imMayMove = false;
            } else {
                if (type == TYPE_WALLPAPER) {
                    displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }
         ...複製程式碼

在註釋1處建立了WindowState,它存有視窗的所有的狀態資訊,在WMS中它代表一個視窗。從WindowState傳入的引數,可以發現WindowState中包含了WMS、Session、WindowToken、父類的WindowState、LayoutParams等資訊。緊接著在註釋2和3處分別判斷請求新增視窗的客戶端是否已經死亡、視窗的DisplayContent是否為null,如果是則不會再執行下面的程式碼邏輯。註釋4處呼叫了WMP的adjustWindowParamsLw方法,該方法的實現在PhoneWindowManager中,會根據視窗的type對視窗的LayoutParams的一些成員變數進行修改。註釋5處呼叫WMP的prepareAddWindowLw方法,用於準備將視窗新增到系統中。
註釋6處將WindowState新增到mWindowMap中。註釋7處將WindowState新增到該WindowState對應的WindowToken中(實際是儲存在WindowToken的父類WindowContainer中),這樣WindowToken就包含了相同元件的WindowState。

addWindow方法總結

addWindow方法分了3個部分來進行講解,主要就是做了下面4件事:

  1. 對所要新增的視窗進行檢查,如果視窗不滿足一些條件,就不會再執行下面的程式碼邏輯。
  2. WindowToken相關的處理,比如有的視窗型別需要提供WindowToken,沒有提供的話就不會執行下面的程式碼邏輯,有的視窗型別則需要由WMS隱式建立WindowToken。
  3. WindowState的建立和相關處理,將WindowToken和WindowState相關聯。
  4. 建立和配置DisplayContent,完成視窗新增到系統前的準備工作。

結語

在本篇文章中我們首先學習了WMS的重要成員,瞭解這些成員有利於對WMS的進一步分析。接下來我們又學習了Window的新增過程的WMS部分,將addWindow方法分為了3個部分來進行講解,從addWindow方法我們得知WMS有3個重要的類分別是WindowToken、WindowState和DisplayContent,關於它們會在本系列後續的文章中進行介紹。

參考資料
《深入理解Android卷III》

相關文章