Android中窗口的創(chuàng)建過程

WindowManagerService的角度來看,標題中所說的“窗口”并非Window類,而是一個View類考榨。單從語義來講号显,窗口就是用戶所看到的屏幕上的某個獨立的界面臭猜,比如一個Activity界面,一個對話框等等押蚤。
Window類是一個頂級窗口外觀和行為策略的抽象基類蔑歌。它只是提供標準的UI策略,如背景揽碘,標題區(qū)域次屠,默認鍵處理等。

窗口的類型

Framework定義了三種窗口類型钾菊,三種類型的定義在WindowManager類中帅矗。

  • 第一種為應(yīng)用窗口。所謂的應(yīng)用窗口是指該窗口對應(yīng)一個Activity,由于加載Activity是由AmS完成的煞烫,因此浑此,對于應(yīng)用程序來講,要創(chuàng)建一個應(yīng)用類窗口滞详,只能在Activity內(nèi)部完成凛俱。
  • 第二種是子窗口紊馏。所謂的子窗口是指,該窗口必須有一個父窗口蒲犬,父窗口可以是一個應(yīng)用類型窗口朱监,也可以是任何其他類型的窗口。比如PopupWindow原叮、OptionMenu赫编。
  • 第三類是系統(tǒng)窗口。系統(tǒng)窗口不需要對應(yīng)任何Activity,也不需要有父窗口奋隶。對于應(yīng)用程序而言擂送,理論上是無法創(chuàng)建系統(tǒng)窗口的,因為所有的應(yīng)用程序都沒有這個權(quán)限唯欣,然而系統(tǒng)進程卻可以創(chuàng)建系統(tǒng)窗口嘹吨。

創(chuàng)建應(yīng)用窗口

Android進程啟動流程一文中已經(jīng)分析了創(chuàng)建App進程,創(chuàng)建Application境氢,以及實例化Activity等流程蟀拷。

那么視圖又是如何呈現(xiàn)出來的?直觀提供給開發(fā)者的是onCreate()/onResume()等回調(diào)方法萍聊,通過setContentView()設(shè)置需要顯示的布局问芬,那么View是怎么顯示到屏幕中的?為什么在onCreate方法中獲取不到View的寬和高呢脐区?

  1. ActivityThread實例化Activity愈诚,接著調(diào)用Activityattach方法,此方法的作用①設(shè)置Activity的內(nèi)部變量牛隅,②為此Activity創(chuàng)建Window對象炕柔。
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    
        //省略設(shè)置內(nèi)部變量代碼
    
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        //給mWindow中的mWindowManager變量賦值
        mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        mWindowManager = mWindow.getWindowManager();
}
  1. 創(chuàng)建好Window對象后,需要給Window對象中的mWindowManager變量賦值媒佣,該變量的類型是WindowManager類 匕累。每一個Window內(nèi)部都有一個WindowManager對象。而WindowManager只是一個接口類默伍,真正的實現(xiàn)是WindowManagerImpl欢嘿。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //這里可以看出mWindowManager是WindowManagerImpl類型。
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
  1. 接下來ActivityThread調(diào)用了performLaunchActivity()也糊,內(nèi)部調(diào)用了 callActivityOnCreate()炼蹦,輾轉(zhuǎn)調(diào)用到ActivityonCreate()。感覺重見天日一般狸剃,此方法對于我們來說再熟悉不過了掐隐。此時我們毫不猶豫調(diào)用了setContentView();實際上調(diào)用了Window對象setContentView()
public void setContentView(@LayoutRes int layoutResID) {
      getWindow().setContentView(layoutResID);
}

接著繼續(xù)分析Window中如何把一個layout.xml文件作為Window界面虑省。

  1. Window的具體實現(xiàn)是PhoneWindow匿刮,PhoneWindow中setContentView代碼如下:
@Override
public void setContentView(View view) {
      setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
      if (mContentParent == null) {
          installDecor();
      } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          mContentParent.removeAllViews();
      }

      if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          view.setLayoutParams(params);
          final Scene newScene = new Scene(mContentParent, view);
          transitionTo(newScene);
      } else {
          mContentParent.addView(view, params);
      }
}

①首先調(diào)用installDecor()方法,創(chuàng)建DecorView探颈。
②而后調(diào)用generateLayout(DecorView decor)創(chuàng)建mContentParent熟丸,并把它加入到DecorView中。
③給 mContentParent 變量賦值伪节,其值是通過調(diào)用ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT)獲 得 的 ,ID_ANDROID_CONTENT 正是 id=contentFrameLayout光羞。
④安裝完窗口修飾后,就可以把用戶界面layout.xml文件添加到窗口修飾中怀大,這是通過在setContentView()中 調(diào)用inflate()方法完成的狞山,該方法的第二個參數(shù)正是mContentParent,即 id=contentFrameLayout叉寂。
⑤最后,回調(diào) cb.onContentChanged()方法总珠,通知應(yīng)用程序窗口內(nèi)容發(fā)生了改變屏鳍,因為從無到有了。而cb正是Activity自身局服,因為Activity實現(xiàn)了 Window.CallBack接口钓瞭,并且在attach()方法中將自身作為Window對象的Callback接口實現(xiàn)。

Activity的窗口組成

至此淫奔,僅僅是創(chuàng)建了DecorView山涡,并添加了mContentParentDecorView還沒有被WindowManager正式addView唆迁。

  1. 接下來ActivityThread調(diào)用了handleResumeActivity()鸭丛,首先調(diào)用performResumeActivity(),輾轉(zhuǎn)調(diào)用了ActivityonResume()唐责,接著會調(diào)用ActivitymakeVisible()鳞溉。該方法及后續(xù)的各種調(diào)用將完成真正的把窗口添加進WmS之中。
void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
}
  1. 在第2點中講到WindowManager只是一個接口類鼠哥,真正的實現(xiàn)是WindowManagerImpl熟菲,因此真正的addView操作就在此類中。然而它又交給了WindowManagerGlobal處理朴恳。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
      mGlobal.addView(view, params, mDisplay, mParentWindow);
}
  1. WindowManagerGlobaladdView過程,一個應(yīng)用程序內(nèi)部無論有多少個Activity, 但只有一個WindowManagerGlobal對象在 WindowManagerGlobal類中維護三個集合,用于保存該應(yīng)用程序中所擁有的窗口的狀態(tài)情屹。而后調(diào)用ViewRootImpl.setView()消请,完成最后的、真正意義上的添加工作恍飘。
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
        //省略檢查參數(shù)合法性的代碼
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            //省略部分代碼
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            //省略
            throw e;
        }
}
  1. ViewRootImpl.setView方法篇幅相對較長榨崩,其主要執(zhí)行的操作有
    ①給 ViewRoot的重要變量賦值谴垫;
    ②調(diào)用requestLayout(),發(fā)出界面重繪請求母蛛;進而執(zhí)行scheduleTraversals()翩剪,進行View的繪制工作。
    ③)調(diào)用sWindowSession.addToDisplay()彩郊,通知WmS顯示View前弯。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

mWindowSession的類型是IWindowSession,它是一個Binder對象秫逝,真正的實現(xiàn)類是Session恕出,也就是說Window的添加過程是一次IPC調(diào)用。添加請求交給WmS去處理了违帆。

到此為止浙巫,從客戶端的角度來講,已經(jīng)完成了窗口創(chuàng)建的全部工作刷后。從而界面才會呈現(xiàn)到用戶面前的畴。可見在onCreate的時候View并沒有做測量尝胆、布局丧裁、繪制操作,因此無法獲取到寬高數(shù)據(jù)含衔。

ViewRootImpl

  • 鏈接WindowManager和DecorView的紐帶煎娇,更廣一點可以說是Window和View之間的紐帶。
  • 完成View的繪制過程贪染,包括measure缓呛、layout、draw過程杭隙。
  • 向DecorView分發(fā)收到的用戶發(fā)起的event事件强经,如按鍵,觸屏等事件
    Android中的ViewRootImpl類源碼解析

篇幅較長寺渗,很感謝你能從頭閱讀完匿情。具體內(nèi)容可參閱《Android內(nèi)核剖析》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市信殊,隨后出現(xiàn)的幾起案子炬称,更是在濱河造成了極大的恐慌,老刑警劉巖涡拘,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玲躯,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機跷车,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門棘利,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朽缴,你說我怎么就攤上這事善玫。” “怎么了密强?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵茅郎,是天一觀的道長。 經(jīng)常有香客問我或渤,道長系冗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任薪鹦,我火速辦了婚禮掌敬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘池磁。我一直安慰自己涝开,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布框仔。 她就那樣靜靜地躺著,像睡著了一般拄养。 火紅的嫁衣襯著肌膚如雪离斩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天瘪匿,我揣著相機與錄音跛梗,去河邊找鬼。 笑死棋弥,一個胖子當著我的面吹牛核偿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顽染,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼漾岳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粉寞?” 一聲冷哼從身側(cè)響起尼荆,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唧垦,沒想到半個月后捅儒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年巧还,在試婚紗的時候發(fā)現(xiàn)自己被綠了鞭莽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡麸祷,死狀恐怖澎怒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摇锋,我是刑警寧澤丹拯,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站荸恕,受9級特大地震影響乖酬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜融求,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一咬像、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧生宛,春花似錦县昂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至莱睁,卻和暖如春待讳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仰剿。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工创淡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人南吮。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓琳彩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親部凑。 傳聞我的和親對象是個殘疾皇子露乏,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

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