Android WindowManagerService--04:Window的添加過程

本文轉載自:

本文基于Android 11.0源碼分析

前言

??窗口的添加,站在用戶角度看,是開啟一個新界面;站在開發者角度看,是通過API創建了一個新的Activity或窗口;站在系統實現角度看,則并非如此簡單,本篇文章的目的,就是弄明白,當添加一個窗口時,系統相關管理者做了哪些工作。

1.添加窗口API

??WindowManager是Android中提供給APP用來創建窗口的。在結構上,WindowManager繼承于ViewManager,從API看,只提供了三個接口用來給APP添加或移除window:

abstract void addView(View view, ViewGroup.LayoutParams params)
abstract void removeView(View view)
abstract void updateViewLayout(View view, ViewGroup.LayoutParams params)

上面方法中也可以知道,在創建窗口時,我們只需要考慮這個窗口要展示的View、以及窗口的屬性(如大小、類型,全部放置在WindowManager.LayoutParams中)。

??WindowManager.LayoutParams是ViewGroup.LayoutParams的子類,該類用來攜帶創建Window時所設置的所有屬性,如Window類型、屬性、格式、Flag等。

1.1 創建基本窗口

??創建一個Window,只需要如下幾步即可:

    private void addWindow() {
        // 1.獲取WindowManager對象
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        // 2.獲取Window.LayoutParams對象
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        // 3.定義Window要展示的View
        LinearLayout linearLayout = (LinearLayout) View.inflate(this,R.layout.window_layout,null);
        // 4.添加View
        wm.addView(linearLayout, layoutParams);
    }

以上示例第2步中,創建WindowManager.LayoutParams對象時使用了默認構造函數,除此之外,WindowManager API提供了7個構造方法用來創建WindowManager.LayoutParams:

//frameworks/base/core/java/android/view/WindowManager.java
WindowManager.LayoutParams()
WindowManager.LayoutParams(int _type)
WindowManager.LayoutParams(int _type, int _flags)
WindowManager.LayoutParams(int _type, int _flags, int _format)
WindowManager.LayoutParams(int w, int h, int _type, int _flags, int _format)
WindowManager.LayoutParams(int w, int h, int xpos, int ypos, int _type, int _flags, int _format)
WindowManager.LayoutParams(Parcel in)

這些參數表示:

  • _type: 窗口類型,用來計算層級,Android中定義了三大類、20幾小類Window類型:

    • Application Window:[FIRST_APPLICATION_WINDOW,LAST_APPLICATION_WINDOW],[1,99];

    • Sub Window: [FIRST_SUB_WINDOW, LAST_SUB_WINDOW],[1000,1999];

    • SystemWindows: [FIRST_SYSTEM_WINDOW, LAST_SYSTEM_WINDOW ], [2000,2999];

  • _flag: 窗口的標記,會根據攜帶的標記值設置不同的功能;

  • _format: Bitmap的格式,PixelFormat中定義的常量;

  • xpos/ypos: Window的x、y坐標。

??接下來就從WindowManager.addView()這個方法開始,看起Window是如何創建的。

2.Window的添加過程

??三大類窗口的添加過程會有所不同,這里以系統窗口StatusBar為例,StatusBar是SystemUI的重要組成部分,具體就是指系統狀態欄,用于顯示時間、電量和信號等信息。我們來查看StatusBar的實現類PhoneStatusBar的addStatusBarWindow方法,這個方法負責為StatusBar添加Window。下面我們從PhoneStatusBar.addStatusBarWindow()方法開始,對添加窗口過程進行分析。

2.1 PhoneStatusBar.addStatusBarWindow()

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
 private void addStatusBarWindow() {
    makeStatusBarView();//1.構建視圖
    mStatusBarWindowManager = new StatusBarWindowManager(mContext);
    mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
            mHeadsUpManager);
    mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());//2
}

注釋1處用于構建StatusBar的視圖。在注釋2處調用了StatusBarWindowManager的add方法,并將StatusBar的視圖(StatusBarWindowView)和StatusBar的傳進去。

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
public void add(View statusBarView, int barHeight) {
    mLp = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            barHeight,
            WindowManager.LayoutParams.TYPE_STATUS_BAR,//1.窗口類型
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
            PixelFormat.TRANSLUCENT);
    mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    mLp.gravity = Gravity.TOP;
    mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    mLp.setTitle("StatusBar");
    mLp.packageName = mContext.getPackageName();
    mStatusBarView = statusBarView;
    mBarHeight = barHeight;
    mWindowManager.addView(mStatusBarView, mLp);//2.添加窗口
    mLpChanged = new WindowManager.LayoutParams();
    mLpChanged.copyFrom(mLp);
}

首先通過創建LayoutParams來配置StatusBar視圖的屬性,包括Width、Height、Type、 Flag、Gravity、SoftInputMode等。 關鍵在注釋1處,設置了TYPE_STATUS_BAR,表示StatusBar視圖的窗口類型是狀態欄。在注釋2處調用了WindowManager的addView方法,addView方法定義在WindowManager的父類接口ViewManager中,而實現addView方法的則是WindowManagerImpl中。

2.2 WindowManagerImpl.addView()

??在framework層實現中,WindowMananger是一個接口,客戶端得到的WindowMananger對象實際上是WindowManangerImpl實例,因此客戶端執行WindowManager.addView()后,將會進入到WindowManagerImpl.addView()中:

//frameworks/base/core/java/android/view/WindowManagerImpl.java
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        // 如果不存在父Window且存在默認Token,會將mDefaultToken設置給params.token。一般都為null
        applyDefaultToken(params);
        // 進入WindowManagerGlobal
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

以上方法中,又會調用進入mGlobal#addView(),mGlobal是WindowManagerGlobal的實例,它通過單例方式提供給WindowMangerImpl:

// frameworks/base/core/java/android/view/WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerGlobal類相對于WindoweMangerImpl來說,它的操作不需要依賴任何的Context對象。這個單例也是相對于WindoweMangerImpl而言的,在Activity啟動時,系統會為每一個Activity或Window創建一個WindoweMangerImpl對象,但一個進程內,只有一個WindowManagerGlobal類實例。

2.3 WindowManagerGlobal.addView()

??WindowManagerGlobal.addView()如下:

//frameworks/base/core/java/android/view/WindowManagerGlobal.java
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            // 如果存在父window,會將params調整一致,存在父Window時,wparams.token就是在此處設置
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // 設置HARDWARE_ACCELERATED標記
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
                       ......
            // 創建一個ViewRootImpl,表示一個Viwe層級的根View
            root = new ViewRootImpl(view.getContext(), display);
            // 把params設置給View
            view.setLayoutParams(wparams);
            mViews.add(view); // 將View添加到mViews列表中
            mRoots.add(root); // 將ViewRootImpl添加到mRoots列表中
            mParams.add(wparams); // 將攜帶的LayoutParam添加到mParams列表中
            try {
                // 將View設置給RootViewImpl
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
            }
        }
    }

以上方法中,會創建ViewRootImpl對象,然后將view、ViewRootImpl、WindowManager.LayoutParmas對象分別添加到了對應列表中后,并執行ViewRootImpl#setView()方法。ViewRootImpl身負了很多職責:

  • View樹的根并管理View樹;

  • 觸發View的測量、布局和繪制;

  • 輸入事件的中轉站;

  • 管理Surface;

  • 負責與WMS進行進程間通信。

2.4 ViewRootImpl.setView()

//frameworks/base/core/java/android/view/ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
                ......
                // 注冊VSync信號監聽器,準備進行layout
                requestLayout();
                try {
                    // 通過WindowSession,將調用進入WMS中
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                } catch (RemoteException e) {
                }
            }
    }

在ViewRootImpl#setView()方法中,會設置這個view的許多屬性,然后執行mWindowSession#addToDisplayAsUser()方法。

??mWindowSession在創建ViewRootImpl實例時獲得,下面來看下mWindowSession的獲取過程。

2.5 獲取IWindowSession對象

??ViewRootImpl是每一個View的管理者,承載著客戶端和WMS服務端的交互重任(通過IWindowSession對象和IWindow對象),繼續來看ViewRoot的構造方法:

// frameworks/base/core/java/android/view/ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }

這里看到,通過WindowManagerGlobal#getWindowSession()方法,獲得了一個IWindowSession接口的對象。ViewRootImpl本身不具有跨進程通信的能力,當它需要向WMS發出交互請求時,就是通過IWindowSession實例來進行。

??接著看下WindowManagerGlobal#getWindowSession()方法:

// frameworks/base/core/java/android/view/WindowManagerGlobal.java
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // WMS遠端對象
                    IWindowManager windowManager = getWindowManagerService();
                    // 從WMS中獲取Session
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                ......
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                }
            }
            return sWindowSession;
        }
    }

在這里,通過WMS打開了一個Session:

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
    return new Session(this, callback);
}

WMS中則直接返回了一個Session類對象。

??由于ViewRootImpl中持有了來自于WMS中的IWindowSession實例,因此在當它需要和WMS進行交互時,將直接通過該實例進行。

3.進入sysmtem_server添加流程

??客戶端進程通過Session對象,最終通過Binder調用進入了WindowManagerService中,所以接下來的流程將在system_server中執行。

3.1 Session.addToDisplay()

進入Session.addToDisplay()后,將直接調用進入WMS中:

// frameworks/base/services/core/java/com/android/server/wm/Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
        Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
            outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
            outInsetsState);
}

3.2 WindowManagerService#addWindow()

??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,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
            int requestUserId) {
                ......
        // 獲取Window type
        final int type = attrs.type;

        synchronized (mGlobalLock) {
            ......
            ActivityRecord activity = null;
            // 對于type為SUB_WINDOW的窗口,判斷是否存在父窗口
            final boolean hasParent = parentWindow != null;
            // 根據attr.token獲取WindowToken對象
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);

            final int rootType = hasParent ? parentWindow.mAttrs.type : type;
            // 如果不存在WindowToken,創建新的WindowToken
            if (token == null) {
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
            // 如果是Application類型的窗口,則該WindowToken向下轉型為ActivityRecord
            } else if (rootType >= FIRST_APPLICATION_WINDOW
                    && rootType <= LAST_APPLICATION_WINDOW) {
                activity = token.asActivityRecord();
                        .........
            } 

            // 創建WindowState對象
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            // 調整win.mAttrs屬性
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
            displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
            // 創建InputChanel,用于input事件的傳遞和接收
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }

            // From now on, no exceptions or errors allowed!
            res = WindowManagerGlobal.ADD_OKAY;
            // 將Windowstate和IWindow添加到mWindowMap中
            mWindowMap.put(client.asBinder(), win);
            // 將WindowToken向下轉型
            final ActivityRecord tokenActivity = token.asActivityRecord();
            boolean imMayMove = true;
            // 將WindowState對象添加到WindowToken中,WindowToken將作為WindowState的父容器
            win.mToken.addWindow(win);
            // 窗口動畫對象 
            final WindowStateAnimator winAnimator = win.mWinAnimator;
            winAnimator.mEnterAnimationPending = true;
            winAnimator.mEnteringAnimation = true;
                        .....
            // 設置Inset
            outInsetsState.set(win.getInsetsState(), win.isClientLocal());
            // 將InputMonitor#mUpdateInputWindowsNeeded屬性設置為true,表示將更新Input內部
            displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();

            boolean focusChanged = false;
            //  如果能收到按鍵
            if (win.canReceiveKeys()) {
                // 更新焦點窗口
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }
            // 為該WindowState分配layer,但是此次并不會真正地為分配layer
            win.getParent().assignChildLayers();
            // 更新Input Window
            if (focusChanged) {
                displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                        false /*updateInputWindows*/);
            }
            displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);

            // 如果方向有更新,更新全局配置
            if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
                displayContent.sendNewConfiguration();
            }

            getInsetsSourceControls(win, outActiveControls);
        }
        return res;
    }

以上方法中,先進行了大量的驗證,如果驗證成功,則進入開始做進一步的處理。這里省略去了一些針對特殊窗口的處理,所做工作主要如下:

  • 根據傳入的attrs.token,從DisplayContent#mTokenMap<IBinder, WindowToken>中獲取WindowToken對象,如果獲取不到,則會創建新的WindowToken;

  • 創建WindowState對象;

  • 將WindowState對象添加到WindowToken中,WindowToken將成為WindowState對象的父容器;

  • 創建InputChannel,用于input事件派發;

  • 更新焦點窗口;

  • 更新InputWindow。

下面分別來看這幾步操作。

3.3 創建WindowToken對象

??WindowToken作為WindowState的父容器,負責管理一組Window。Window添加過程中,WindowToken的創建是視情況而定的,因為一個WindowToken管理著一組Window,如果在添加Window的過程中,已經存在合適的WindowToken,那么就不會創建,否則會創建WindowToken。

WindowToken的構造方法如下:

//frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
            boolean roundedCornerOverlay, boolean fromClientToken) {
        super(service);
        token = _token; // IBinder對象,作為一組窗口的實際token
        windowType = type; // 窗口類型
        mPersistOnEmpty = persistOnEmpty; // 是否是顯式添加的WidowToken
        mOwnerCanManageAppTokens = ownerCanManageAppTokens; // 是否具有MANAGE_APP_TOKENS權限
        mOwnerUid = ownerUid; // owner id
        mRoundedCornerOverlay = roundedCornerOverlay; // 是否覆蓋圓角
        mFromClientToken = fromClientToken;
        if (dc != null) {
            dc.addWindowToken(token, this); // 將該對象添加到DisplayContent中
        }
    }

以上方法中,在對一些屬性進行初始化后,將WindowToken和對應的IBinder對象傳遞給DisplayContent,并以<IBinder, WindowToken>的形式,保存在DisplayContent#mToken中,同時將Token放置在合適的容器中:

//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
void addWindowToken(IBinder binder, WindowToken token) {
    // 如果該WindowToken已經關聯有DisplayContent對象,則不能再關聯其他DisplayContent對象
    final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);

    // 將WindowToken添加到DisplayContent#mTokenMap中
    mTokenMap.put(binder, token);
    // 對于非Activity類型的窗口,會根據Window類型添加到對應三個container中
    if (token.asActivityRecord() == null) {
                // WindowToken和DisplayContent關聯
                token.mDisplayContent = this;

        switch (token.windowType) {
            // 將輸入法Window,放在mImeWindowsContainers中
            case TYPE_INPUT_METHOD:
            case TYPE_INPUT_METHOD_DIALOG:
                mImeWindowsContainers.addChild(token);
                break;
            case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
                mOverlayContainers.addChild(token);
                break;
            // 其他類型Window,放在mDisplayAreaPolicy
            default:
                mDisplayAreaPolicy.addWindow(token);
                break;
        }
    }
}

以上方法中,mImeWindowsContainers、mOverlayContainers和mDisplayAreaPolicy將作為WindowToken的父容器對其進行管理,其中:

  • mImeWindowsContainers:ImeContainer類對象,作為IME窗口的父容器,管理IME窗口;

  • mOverlayContainers:NonAppWindowContainers類對象,作為和應用不關聯的窗口的父容器,如狀態欄等;

  • mDisplayAreaPolicy: DisplayAreaPolicy類對象,它本身并非容器,但該對象作為策略會持有一個合適的容器,這個容器就是DisplayArea.Token, 來管理其他類型的窗口。

??這三個容器的類結構如下:

WMS08.png

此時,WindowToken對象創建完畢。

3.4 創建WindowState對象

??在得到WindowToken對象后,接下來將創建WindowState對象,一個WindowState代表了一個具體的Window,每添加一個Window,都會創建對應的WindowState對象。

??WindowState類的構造方法如下:

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, int showUserId,
            boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
        super(service); // 執行WindowContainer(wms)
        mSession = s; // Session對象
        mClient = c; // 客戶端的IWindow類型對象,該對象和客戶端進行交互
        mToken = token; // WindowToken對象
        mActivityRecord = mToken.asActivityRecord(); // 該token如果是ActivityRecord類型,則相當于向下轉型
        mAttrs.copyFrom(a); // 從傳入的attr對象復制給mAttrs對象
        mLastSurfaceInsets.set(mAttrs.surfaceInsets);
        mViewVisibility = viewVisibility; // 窗口可見性
        mPolicy = mWmService.mPolicy; // PhoneWindowManager對象
        mContext = mWmService.mContext;                        
        mSeq = seq;
        mPowerManagerWrapper = powerManagerWrapper;
        mForceSeamlesslyRotate = token.mRoundedCornerOverlay; // 圓角窗口的特殊標記,控制圓角的方向旋轉
        // 設置Binder die代理
        try {
            c.asBinder().linkToDeath(deathRecipient, 0);
        } catch (RemoteException e) {
        }
        mDeathRecipient = deathRecipient;
        // 對Sub Window類型窗口設置BaseLayer和SubLayer值
        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
            mIsChildWindow = true;
            mLayoutAttached = mAttrs.type !=
                    WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
            mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
                    || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
            mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
        } else {// 對非子窗口類型窗口設置BaseLayer和SubLayer值
                        ......
        }
        // 是否為"漂浮"類窗口,因為該倆窗口需要掛在其他窗口上顯示
        mIsFloatingLayer = mIsImWindow || mIsWallpaper;
        // 創建WindowStateAnimator對象
        mWinAnimator = new WindowStateAnimator(this);
        mWinAnimator.mAlpha = a.alpha;
        // 初始話寬高
        mRequestedWidth = 0;        
        mRequestedHeight = 0;
        mLastRequestedWidth = 0;
        mLastRequestedHeight = 0;
        mLayer = 0;
        // 創建InputWindowHandle對象
        mInputWindowHandle = new InputWindowHandle(
                mActivityRecord != null ? mActivityRecord.mInputApplicationHandle : null,
                    getDisplayId());
        // 如果是子窗口類型窗口,需要添加到父窗口中
        if (mIsChildWindow) {
            parentWindow.addChild(this, sWindowSubLayerComparator);
        }
        // 獲取WindowProcessController對象,管理進程
        mWpcForDisplayConfigChanges = (s.mPid == MY_PID || s.mPid < 0)
                ? null
                : service.mAtmService.getProcessController(s.mPid, s.mUid);
    }

在以上方法中,對WindState類中的許多屬性進行了初始化操作,其中還創建了兩個對象——WindowStateAnimator對象和InputWindowHandle對象。

  • WindowStateAnimator對象用來為WindowState對象進行動畫和Surface的轉變操作,繪制過程中,動畫的狀態和Surface狀態都會由該對象來進行記錄。

  • InputWindowHandle對象則用于input事件的分發,通過內部的IBinder類型的token,將InputChannel和WindowState進行關聯。這倆對象的創建相對較簡單,這里就略去了。

3.5 將WindowToken設置為WindoState的父容器

??在3.2 WindowManagerService#addWindow()小節中,會通過WindowToken#addWindow()方法,將WindowState對象添加到WindowToken中,使得WindowToken成為WindowState的父容器:

//3.2 WindowManagerService#addWindow()
win.mToken.addWindow(win);
// frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
    void addWindow(final WindowState win) {
        // 對于子窗口而言,由父窗口管理,不需要由WindowToken管理
        if (win.isChildWindow()) {
            return;
        }
        // 如果mSurfaceControl不存在創建createSurfaceControl對象
        if (mSurfaceControl == null) {
            createSurfaceControl(true /* force */);
        }
        // 添加到mChildren列表中,并設置WindowStack#mParent為該WindowToken
        if (!mChildren.contains(win)) {
            addChild(win, mWindowComparator);
            mWmService.mWindowsChanged = true;
        }
    }

以上方法中,通過addChild()方法,將WindowState對象添加到WindowToken#mChildren列表中,同時將WindowToken賦值給WindowState#mParent屬性。

3.6 創建InputChannel

??InputChannel用于傳輸input事件給應用,其內部通過socket的方式,將inputFlinger中的input事件讀取并傳遞給應用,在WindowState#的方式創建并注冊了InputChannel對象:

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
    void openInputChannel(InputChannel outInputChannel) {

        String name = getName();
        // 創建一個InputChannel對
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        // 向IMS中注冊InputChannel
        mWmService.mInputManager.registerInputChannel(mInputChannel);
        mInputWindowHandle.token = mInputChannel.getToken();
        // 填充返回給ViewRootImpl的outInputChannel
        if (outInputChannel != null) {
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
        } 
        ......
        mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
    }

這個方法中,首先獲取InputChannel對,然后向IMS中注冊InputChannel,最后將inputChannels[1]返回給ViewRootImpl。

3.7 更新焦點窗口

??如果該WindowState能接收key事件,則接下來會通過WMS#updateFocusedWindowLocked()方法更新全局的焦點窗口。在更新時,將會自頂向下,從RootWindowContainer → DisplayContent 對每個WindowState進行遍歷,最終獲取到一個獲得焦點的WindowState對象。

??更新焦點窗口的流程比較長,對這部分流程后續進行單獨的分析。

3.8 更新InputWindow

??“InputWindow”是指能夠接收input事件的窗口。在更新焦點窗口前,就通過InputMonitor#setUpdateInputWindowsNeededLw()方法,將InputMonitor#mUpdateInputWindowsNeeded屬性設置為true,表示接下來需要更新Input窗口。

// frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
    void updateInputWindowsLw(boolean force) {
        if (!force && !mUpdateInputWindowsNeeded) {
            return;
        }
        scheduleUpdateInputWindows();
    }

從Android Q開始,在SurfaceFlinger中通過Binder調用更新InputWindow給InputFlinger。

??到此為止,整個Window添加過程執行完成。整個過程時序圖如下:

WMS09.png

4.Activity的添加窗口過程

??無論是哪種窗口,它的的添加過程在WMS處理部分中基本是類似的,只不過會在權限和窗口顯示次序等方面會有些不同。但是在WindowManager處理部分會有所不同,這里以最典型的應用程序窗口Activity為例,Activity在啟動過程中,如果Activity所在的進程不存在則會創建新的進程,創建新的進程之后就會運行代表主線程的實例ActivityThread。當界面要與用戶進行交互時,會調用ActivityThread的handleResumeActivity方法,如下所示。

//frameworks/base/core/java/android/app/ActivityThread.java
 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
       ...   
         r = performResumeActivity(token, clearHide, reason);//1.最終調用Activity的onResume       
  ...
     if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();//2
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//3
                }
...                
}

注釋1處的performResumeActivity方法最終會調用Activity的onResume方法。在注釋2處得到ViewManager類型的wm對象,在注釋3處調用了wm的addView方法,而addView方法的實現則是在WindowManagerImpl中,此后的過程在上面的系統窗口的添加過程已經講過,唯一需要注意的是addView的第一個參數是DecorView。

5.總結

  1. 添加窗口時,客戶端拿到的WindowManager對象是一個WindowManagerImpl實例,且沒個Activity和Window都各自的WindowManagerImpl實例,并且和Context綁定。相比WindowManagerImpl對象,每個進程則只有一個WindowManagerGlobal對象;

  2. ViewRootImpl作為每個View的管理者,和WMS進行交互,兩進程交互過程通過IWindowSession和IWindow接口進行;

  3. WMS中,WindowState對象代表一個窗口,WindowToken作為WindowState的父容器,管理著一組窗口。

??通過WMS#addWindow()方法,創建了相對于system_server的窗口管理對象,但需要顯示窗口內容的Surface還沒有創建。

??在addView()過程中,有一個requestLayout()操作,它監聽Vsync信號,并在收到Vsync信號后,請求WMS進行relayout操作,從而開始執行布局過程。后面文章將對布局的過程進行分析。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容