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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淫痰,隨后出現(xiàn)的幾起案子最楷,更是在濱河造成了極大的恐慌,老刑警劉巖黑界,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件管嬉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朗鸠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門础倍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烛占,“玉大人,你說我怎么就攤上這事沟启∫浼遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵德迹,是天一觀的道長芽卿。 經(jīng)常有香客問我,道長胳搞,這世上最難降的妖魔是什么卸例? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮肌毅,結(jié)果婚禮上筷转,老公的妹妹穿的比我還像新娘。我一直安慰自己悬而,他們只是感情好呜舒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笨奠,像睡著了一般袭蝗。 火紅的嫁衣襯著肌膚如雪唤殴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天到腥,我揣著相機(jī)與錄音朵逝,去河邊找鬼。 笑死左电,一個(gè)胖子當(dāng)著我的面吹牛廉侧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播篓足,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼段誊,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了栈拖?” 一聲冷哼從身側(cè)響起连舍,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涩哟,沒想到半個(gè)月后索赏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贴彼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年潜腻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片器仗。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡融涣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出精钮,到底是詐尸還是另有隱情威鹿,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布轨香,位于F島的核電站忽你,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏臂容。R本人自食惡果不足惜科雳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望策橘。 院中可真熱鬧炸渡,春花似錦、人聲如沸丽已。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吼畏,卻和暖如春督赤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泻蚊。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工躲舌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人性雄。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓没卸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秒旋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子约计,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容