Android解析WindowManager(三)Window的添加過程

前言

在此前的系列文章中我們學(xué)習(xí)了WindowManager體系和Window的屬性,這一篇我們接著來講Window的添加過程。建議閱讀此篇文章前先閱讀本系列的前兩篇文章。

1.概述

WindowManager對(duì)Window進(jìn)行管理,說到管理那就離不開對(duì)Window的添加、更新和刪除的操作,在這里我們把它們統(tǒng)稱為Window的操作。對(duì)于Window的操作,最終都是交由WMS來進(jìn)行處理。窗口的操作分為兩大部分,一部分是WindowManager處理部分,另一部分是WMS處理部分。我們知道Window分為三大類,分別是:Application Window(應(yīng)用程序窗口)、Sub Windwow(子窗口)和System Window(系統(tǒng)窗口),對(duì)于不同類型的窗口添加過程會(huì)有所不同,但是對(duì)于WMS處理部分,添加的過程基本上是一樣的, WMS對(duì)于這三大類的窗口基本是“一視同仁”的。

本篇主要會(huì)講解Window的操作的WindowManager處理部分,至于WMS處理部分會(huì)在后續(xù)的解析WMS系列文章中進(jìn)行講解。

2.系統(tǒng)窗口的添加過程

三大類窗口的添加過程會(huì)有所不同,這里以系統(tǒng)窗口StatusBar為例,StatusBar是SystemUI的重要組成部分,具體就是指系統(tǒng)狀態(tài)欄,用于顯示時(shí)間、電量和信號(hào)等信息。我們來查看StatusBar的實(shí)現(xiàn)類PhoneStatusBar的addStatusBarWindow方法,這個(gè)方法負(fù)責(zé)為StatusBar添加Window,如下所示。
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處用于構(gòu)建StatusBar的視圖。在注釋2處調(diào)用了StatusBarWindowManager的add方法,并將StatusBar的視圖(StatusBarWindowView)和StatusBar的傳進(jìn)去。
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);
}

首先通過創(chuàng)建LayoutParams來配置StatusBar視圖的屬性,包括Width、Height、Type、 Flag、Gravity、SoftInputMode等,不了Window屬性的請(qǐng)查看Android解析WindowManager(二)Window的屬性這篇文章。 關(guān)鍵在注釋1處,設(shè)置了TYPE_STATUS_BAR,表示StatusBar視圖的窗口類型是狀態(tài)欄。在注釋2處調(diào)用了WindowManager的addView方法,addView方法定義在WindowManager的父類接口ViewManager中,而實(shí)現(xiàn)addView方法的則是WindowManagerImpl中,如下所示。
frameworks/base/core/java/android/WindowManagerImpl.java

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

在WindowManagerImpl的addView方法中,接著會(huì)調(diào)用WindowManagerGlobal的addView方法:

frameworks/base/core/java/android/view/WindowManagerGlobal.java

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...//參數(shù)檢查
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);//1
        } else {
        ...
        }

        ViewRootImpl root;
        View panelParentView = null;
         ...
            root = new ViewRootImpl(view.getContext(), display);//2
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);//3
            mParams.add(wparams);
        }
        try {
            root.setView(view, wparams, panelParentView);//4
        } catch (RuntimeException e) {
           ...
        }
    }

首先會(huì)對(duì)參數(shù)view、params和display進(jìn)行檢查。注釋1處,如果當(dāng)前窗口要作為子窗口,就會(huì)根據(jù)父窗口對(duì)子窗口的WindowManager.LayoutParams類型的wparams對(duì)象進(jìn)行相應(yīng)調(diào)整。注釋2處創(chuàng)建了ViewRootImp并賦值給root,緊接著在注釋3處將root存入到ArrayList<ViewRootImpl>類型的mRoots中,除了mRoots,mViews和mParams也是ArrayList類型的,分別用于存儲(chǔ)窗口的view對(duì)象和WindowManager.LayoutParams類型的wparams對(duì)象。注釋4處調(diào)用了ViewRootImpl的setView方法。
ViewRootImpl身負(fù)了很多職責(zé):

  • View樹的根并管理View樹
  • 觸發(fā)View的測(cè)量、布局和繪制
  • 輸入事件的中轉(zhuǎn)站
  • 管理Surface
  • 負(fù)責(zé)與WMS進(jìn)行進(jìn)程間通信

frameworks/base/core/java/android/view/ViewRootImpl.java

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
           ...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
                ...
    }

setView方法中有很多邏輯,這里只截取了一小部分,主要就是調(diào)用了mWindowSession的addToDisplay方法。mWindowSession是IWindowSession類型的,它是一個(gè)Binder對(duì)象,用于進(jìn)行進(jìn)程間通信,IWindowSession是Client端的代理,它的Server端的實(shí)現(xiàn)為Session,此前包含ViewRootImpl在內(nèi)的代碼邏輯都是運(yùn)行在本地進(jìn)程的,而Session的addToDisplay方法則運(yùn)行在WMS所在的進(jìn)程。
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 outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

addToDisplay方法中會(huì)調(diào)用了WMS的addWindow方法,并將自身也就是Session,作為參數(shù)傳了進(jìn)去,每個(gè)應(yīng)用程序進(jìn)程都會(huì)對(duì)應(yīng)一個(gè)Session,WMS會(huì)用ArrayList來保存這些Session。這樣剩下的工作就交給WMS來處理,在WMS中會(huì)為這個(gè)添加的窗口分配Surface,并確定窗口顯示次序,可見負(fù)責(zé)顯示界面的是畫布Surface,而不是窗口本身。WMS會(huì)將它所管理的Surface交由SurfaceFlinger處理,SurfaceFlinger會(huì)將這些Surface混合并繪制到屏幕上。
窗口添加的WMS處理部分會(huì)在后續(xù)介紹WMS的系列文章進(jìn)行講解,系統(tǒng)窗口的添加過程的時(shí)序圖如下所示。

3.Activity的添加過程

無論是哪種窗口,它的的添加過程在WMS處理部分中基本是類似的,只不過會(huì)在權(quán)限和窗口顯示次序等方面會(huì)有些不同。但是在WindowManager處理部分會(huì)有所不同,這里以最典型的應(yīng)用程序窗口Activity為例,Activity在啟動(dòng)過程中,如果Activity所在的進(jìn)程不存在則會(huì)創(chuàng)建新的進(jìn)程,創(chuàng)建新的進(jìn)程之后就會(huì)運(yùn)行代表主線程的實(shí)例ActivityThread,不了解的請(qǐng)查看Android應(yīng)用程序進(jìn)程啟動(dòng)過程(前篇)這篇文章。ActivityThread管理著當(dāng)前應(yīng)用程序進(jìn)程的線程,這在Activity的啟動(dòng)過程中運(yùn)用的很明顯,不了解的請(qǐng)查看Android深入四大組件(一)應(yīng)用程序啟動(dòng)過程(后篇)這篇文章。當(dāng)界面要與用戶進(jìn)行交互時(shí),會(huì)調(diào)用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           
  ...
 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方法最終會(huì)調(diào)用Activity的onResume方法。在注釋2處得到ViewManager類型的wm對(duì)象,在注釋3處調(diào)用了wm的addView方法,而addView方法的實(shí)現(xiàn)則是在WindowManagerImpl中,此后的過程在上面的系統(tǒng)窗口的添加過程已經(jīng)講過,唯一需要注意的是addView的第一個(gè)參數(shù)是DecorView。

結(jié)語

ViewManager不只定義了addView方法用來添加窗口,還定義了updateViewLayout和removeView方法用來更新和刪除窗口,如下所示。

package android.view;
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

其定義的updateViewLayout和removeView方法的處理流程和addView方法是類似的,都是要經(jīng)過WindowManagerGlobal處理,最后通過Session與WMS進(jìn)行跨進(jìn)程通信,將更新和刪除窗口的工作交由WMS來處理,這里不會(huì)對(duì)其進(jìn)行介紹,想了解可以查看源碼或者查看《Android開發(fā)藝術(shù)探索》第八章。

參考資料
《深入理解Android內(nèi)核設(shè)計(jì)思想》第二版
《深入理解Android:卷III》
《Android開發(fā)藝術(shù)探索》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容