Activity,Window,DecorView,ViewRoot,View的關(guān)系

簡(jiǎn)介

  • Activity是一個(gè)工人,它來控制Window;Window是一面顯示屏痹兜,用來顯示信息蛮粮;View就是要顯示在顯示屏上的信息益缎,這些View都是層層重疊在一起(通過infalte()和addView())放到Window顯示屏上的。而LayoutInfalter就是用來生成View的一個(gè)工具然想,XML布局文件就是用來生成View的原料

  • Activity并不負(fù)責(zé)視圖控制莺奔,它只是控制生命周期和處理事件,真正控制視圖的是Window变泄。一個(gè)Activity包含了一個(gè)Window令哟,Window才是真正代表一個(gè)窗口,Window 中持有一個(gè) DecorView妨蛹,而這個(gè)DecorView才是 view 的根布局屏富。

  • DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹的根節(jié)點(diǎn)視圖蛙卤。DecorView作為頂級(jí)View狠半,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout噩死,在這個(gè)LinearLayout里面有上下兩個(gè)部分(具體情況和Android版本及主體有關(guān)),上面的是標(biāo)題欄神年,下面的是內(nèi)容欄已维。在Activity中通過setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的,而內(nèi)容欄的id是content已日,在代碼中可以通過ViewGroup content = (ViewGroup)findViewById(R.android.id.content)來得到content對(duì)應(yīng)的layout垛耳。

  • ViewRoot對(duì)應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶捂敌,View的三大流程(測(cè)量(measure)艾扮,布局(layout),繪制(draw))均通過ViewRoot來完成占婉。ViewRoot并不屬于View樹的一份子泡嘴。從源碼實(shí)現(xiàn)上來看,它既非View的子類逆济,也非View的父類酌予,但是,它實(shí)現(xiàn)了ViewParent接口奖慌,這讓它可以作為View的名義上的父視圖抛虫。RootView繼承了Handler類,可以接收事件并分發(fā)简僧,Android的所有觸屏事件建椰、按鍵事件、界面刷新等事件都是通過ViewRoot進(jìn)行分發(fā)的岛马。ViewRoot可以被理解為“View樹的管理者”——它有一個(gè)mView成員變量棉姐,它指向的對(duì)象和上文中Window和Activity的mDecor指向的對(duì)象是同一個(gè)對(duì)象。

從 setContentView 分析起

眾所周知啦逆,在 activity 中伞矩,setContentView方法可以設(shè)置我們需要的布局,那么就從這個(gè)作為切入點(diǎn)開始分析

//activity
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }

    /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

繼續(xù)尋找 mWindow 的賦值時(shí)機(jī)

 final void attach(Context context, ActivityThread aThread,
...
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);//當(dāng)window接收系統(tǒng)發(fā)送給它的IO輸入事件時(shí),例如鍵盤和觸摸屏事件,就可以轉(zhuǎn)發(fā)給相應(yīng)的Activity
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }

可以看到在attach夏志,實(shí)例化了一個(gè)PhoneWindow對(duì)象(window 的實(shí)現(xiàn))乃坤,繼續(xù)看 window 中 setContentView的實(shí)現(xiàn)

@Override
public void setContentView(int layoutResID) {
   // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
   // decor, when theme attributes and the like are crystalized. Do not check the feature
   // before this happens.
   if (mContentParent == null) {
       installDecor();//[window]如何沒有DecorView,那么就新建一個(gè)
   } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
       mContentParent.removeAllViews();
   }

   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
       final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
               getContext());
       transitionTo(newScene);
   } else {
       mLayoutInflater.inflate(layoutResID, mContentParent); //[window]第二步,將layout添加到mContentParent
   }
   mContentParent.requestApplyInsets();
   final Callback cb = getCallback();
   if (cb != null && !isDestroyed()) {
       cb.onContentChanged();
   }
}

//注意 mContentParent的注釋,后續(xù)做分析
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

創(chuàng)建DecorView

繼續(xù)看 PhoneWindow 中的installDecor() 方法

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //DecorView 被賦值
    mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

...

protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 從主題文件中獲取樣式信息
        TypedArray a = getWindowStyle();

        ...

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        if(...){
            ...
        }
//[window] 以上都是根據(jù)不同的style生成不同的decorview
        // Inflate the window decor.
        // 加載窗口布局
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if(...){
            ...
        }

        View in = mLayoutInflater.inflate(layoutResource, null);    //加載layoutResource
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //往DecorView中添加子View沟蔑,即mContentParent湿诊,// 加入到deco中,所以應(yīng)該是其第一個(gè)child
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 這里獲取的就是mContentParent
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks();
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        ...

        return contentParent;
    }

由以上代碼可以看出,該方法還是做了相當(dāng)多的工作的溉贿,首先根據(jù)設(shè)置的主題樣式來設(shè)置DecorView的風(fēng)格枫吧,比如說有沒有titlebar之類的,接著為DecorView添加子View宇色,而這里的子View則是上面提到的mContentParent,如果上面設(shè)置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一個(gè)子View宣蠕,這也解釋了上面的注釋:mContentParent是DecorView本身或者是DecorView的一個(gè)子元素例隆。

Paste_Image.png

小結(jié):

  • DecorView是頂級(jí)View,內(nèi)部有titlebar和contentParent兩個(gè)子元素抢蚀,contentParent的id是content镀层,而我們?cè)O(shè)置的main.xml布局則是contentParent里面的一個(gè)子元素。
  • 在DecorView創(chuàng)建完畢后皿曲,讓我們回到PhoneWindow#setContentView方法唱逢,LayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們?cè)O(shè)置的main.xml布局文件,并且設(shè)置mContentParent為main.xml的父布局屋休,

到目前為止坞古,通過setContentView方法,創(chuàng)建了DecorView和加載了我們提供的布局劫樟,但是這時(shí)痪枫,我們的View還是不可見的,因?yàn)槲覀儍H僅是加載了布局叠艳,并沒有對(duì)View進(jìn)行任何的測(cè)量奶陈、布局、繪制工作附较。在View進(jìn)行測(cè)量流程之前吃粒,還要進(jìn)行一個(gè)步驟,那就是把DecorView添加至window中拒课,然后經(jīng)過一系列過程觸發(fā)ViewRootImpl#performTraversals方法徐勃,在該方法內(nèi)部會(huì)正式開始測(cè)量、布局捕发、繪制這三大流程疏旨。

將DecorView添加至Window

每一個(gè)Activity組件都有一個(gè)關(guān)聯(lián)的Window對(duì)象,用來描述一個(gè)應(yīng)用程序窗口扎酷。每一個(gè)應(yīng)用程序窗口內(nèi)部又包含有一個(gè)View對(duì)象檐涝,用來描述應(yīng)用程序窗口的視圖。上文分析了創(chuàng)建DecorView的過程法挨,現(xiàn)在則要把DecorView添加到Window對(duì)象中谁榜。而要了解這個(gè)過程,我們要簡(jiǎn)單先了解一下Activity的創(chuàng)建過程:
首先凡纳,在ActivityThread#handleLaunchActivity中啟動(dòng)Activity窃植,在這里面會(huì)調(diào)用到Activity#onCreate方法,從而完成上面所述的DecorView創(chuàng)建動(dòng)作荐糜,當(dāng)onCreate()方法執(zhí)行完畢巷怜,在handleLaunchActivity方法會(huì)繼續(xù)調(diào)用到ActivityThread#handleResumeActivity方法葛超,

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
    //...
    ActivityClientRecord r = performResumeActivity(token, clearHide); // 這里會(huì)調(diào)用到onResume()方法

    if (r != null) {
        final Activity a = r.activity;

        //...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // 獲得window對(duì)象
            View decor = r.window.getDecorView(); // 獲得DecorView對(duì)象
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 獲得windowManager對(duì)象
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 調(diào)用addView方法
            }
            //...
        }
    }
}

在該方法內(nèi)部,獲取該activity所關(guān)聯(lián)的window對(duì)象延塑,DecorView對(duì)象绣张,以及windowManager對(duì)象,而WindowManager是抽象類关带,它的實(shí)現(xiàn)類是WindowManagerImpl侥涵,所以后面調(diào)用的是WindowManagerImpl#addView方法,我們看看源碼:

public final class WindowManagerImpl implements WindowManager {    
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
}

接著調(diào)用了mGlobal的成員函數(shù)宋雏,而mGlobal則是WindowManagerGlobal的一個(gè)實(shí)例芜飘,那么我們接著看WindowManagerGlobal#addView方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...

            root = new ViewRootImpl(view.getContext(), display); // 1

            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); // 2
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

先看①號(hào)代碼處,實(shí)例化了ViewRootImpl類磨总,接著嗦明,在②號(hào)代碼處,調(diào)用ViewRootImpl#setView方法舍败,并把DecorView作為參數(shù)傳遞進(jìn)去招狸,在這個(gè)方法內(nèi)部,會(huì)通過跨進(jìn)程的方式向WMS(WindowManagerService)發(fā)起一個(gè)調(diào)用邻薯,從而將DecorView最終添加到Window上裙戏,在這個(gè)過程中,ViewRootImpl厕诡、DecorView和WMS會(huì)彼此關(guān)聯(lián)累榜,至于詳細(xì)過程這里不展開來說了。
最后通過WMS調(diào)用ViewRootImpl#performTraverals方法開始View的測(cè)量灵嫌、布局壹罚、繪制流程.

參考

http://www.reibang.com/p/687010ccad66

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寿羞,隨后出現(xiàn)的幾起案子猖凛,更是在濱河造成了極大的恐慌,老刑警劉巖绪穆,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辨泳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡玖院,警方通過查閱死者的電腦和手機(jī)菠红,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來难菌,“玉大人试溯,你說我怎么就攤上這事〗季疲” “怎么了遇绞?”我有些...
    開封第一講書人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵键袱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我试读,道長(zhǎng)杠纵,這世上最難降的妖魔是什么荠耽? 我笑而不...
    開封第一講書人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任钩骇,我火速辦了婚禮,結(jié)果婚禮上铝量,老公的妹妹穿的比我還像新娘倘屹。我一直安慰自己,他們只是感情好慢叨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開白布纽匙。 她就那樣靜靜地躺著,像睡著了一般拍谐。 火紅的嫁衣襯著肌膚如雪烛缔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,919評(píng)論 1 290
  • 那天轩拨,我揣著相機(jī)與錄音践瓷,去河邊找鬼。 笑死亡蓉,一個(gè)胖子當(dāng)著我的面吹牛晕翠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砍濒,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼淋肾,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了爸邢?” 一聲冷哼從身側(cè)響起樊卓,我...
    開封第一講書人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杠河,沒想到半個(gè)月后碌尔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡感猛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年七扰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陪白。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颈走,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咱士,到底是詐尸還是另有隱情立由,我是刑警寧澤轧钓,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站锐膜,受9級(jí)特大地震影響毕箍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜道盏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一而柑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荷逞,春花似錦媒咳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坠敷,卻和暖如春妙同,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膝迎。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工粥帚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弄抬。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓茎辐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掂恕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拖陆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

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