簡(jiǎn)析Window、Activity、DecorView以及ViewRoot之間的錯(cuò)綜關(guān)系

關(guān)于Android中View視圖的呈現(xiàn),我們平常看到最多的就是在Activity中通過(guò)setContentView(R.layout.activity_main);設(shè)置來(lái)顯示。然而其實(shí)視圖的顯示并不是由Activity來(lái)完成的,其中涉及到了Window、DecorView、ViewRoot。四者之間關(guān)系交互復(fù)雜,共同完成視圖的顯示以及與用戶之間的交互。本篇文章簡(jiǎn)單介紹四者的各自職能,以及視圖的顯示過(guò)程。由于程度有限,認(rèn)識(shí)可能不到位,歡迎指正。

一、職能簡(jiǎn)介

Activity

Activity并不負(fù)責(zé)視圖控制,它只是控制生命周期和處理事件。真正控制視圖的是Window。一個(gè)Activity包含了一個(gè)Window,Window才是真正代表一個(gè)窗口。Activity就像一個(gè)控制器,統(tǒng)籌視圖的添加與顯示,以及通過(guò)其他回調(diào)方法,來(lái)與Window、以及View進(jìn)行交互。

Window

Window是視圖的承載器,內(nèi)部持有一個(gè) DecorView,而這個(gè)DecorView才是 view 的根布局。Window是一個(gè)抽象類,實(shí)際在Activity中持有的是其子類PhoneWindow。PhoneWindow中有個(gè)內(nèi)部類DecorView,通過(guò)創(chuàng)建DecorView來(lái)加載Activity中設(shè)置的布局R.layout.activity_main。Window 通過(guò)WindowManager將DecorView加載其中,并將DecorView交給ViewRoot,進(jìn)行視圖繪制以及其他交互。

DecorView

DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹(shù)的根節(jié)點(diǎn)視圖。DecorView作為頂級(jí)View,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout,在這個(gè)LinearLayout里面有上下三個(gè)部分,上面是個(gè)ViewStub,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar,根據(jù)Theme設(shè)置),中間的是標(biāo)題欄(根據(jù)Theme設(shè)置,有的布局沒(méi)有),下面的是內(nèi)容欄。具體情況和Android版本及主體有關(guān),以其中一個(gè)布局為例,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在Activity中通過(guò)setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過(guò)content來(lái)得到對(duì)應(yīng)加載的布局。

ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);
ViewRoot

ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發(fā)等交互都是通過(guò)它來(lái)執(zhí)行或傳遞的。

ViewRoot對(duì)應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測(cè)量(measure),布局(layout),繪制(draw))均通過(guò)ViewRoot來(lái)完成。

ViewRoot并不屬于View樹(shù)的一份子。從源碼實(shí)現(xiàn)上來(lái)看,它既非View的子類,也非View的父類,但是,它實(shí)現(xiàn)了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件并分發(fā),Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過(guò)ViewRoot進(jìn)行分發(fā)的。

下面結(jié)構(gòu)圖可以清晰的揭示四者之間的關(guān)系:

二、DecorView的創(chuàng)建

這部分內(nèi)容主要講DecorView是怎么一層層嵌套在Actvity,PhoneWindow中的,以及DecorView如何加載內(nèi)部布局。

setContentView

先是從Activity中的setContentView()開(kāi)始。

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

可以看到實(shí)際是交給Window裝載視圖。下面來(lái)看看Activity是怎么獲得Window對(duì)象的?

 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,
            Window window) {
    ..................................................................
        mWindow = new PhoneWindow(this, window);//創(chuàng)建一個(gè)Window對(duì)象
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);//設(shè)置回調(diào),向Activity分發(fā)點(diǎn)擊或狀態(tài)改變等事件
        mWindow.setOnWindowDismissedCallback(this);
     .................................................................
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//給Window設(shè)置WindowManager對(duì)象
 ....................................................................
    }

在Activity中的attach()方法中,生成了PhoneWindow實(shí)例。既然有了Window對(duì)象,那么我們就可以設(shè)置DecorView給Window對(duì)象了。

 public void setContentView(int layoutResID) {
        if (mContentParent == null) {//mContentParent為空,創(chuàng)建一個(gè)DecroView
            installDecor();
        } else {
            mContentParent.removeAllViews();//mContentParent不為空,刪除其中的View
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);//為mContentParent添加子View,即Activity中設(shè)置的布局文件
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();//回調(diào)通知,內(nèi)容改變
        }
    }

看了下來(lái),可能有一個(gè)疑惑:mContentParent到底是什么?
就是前面布局中@android:id/content所對(duì)應(yīng)的FrameLayout。

通過(guò)上面的流程我們大致可以了解先在PhoneWindow中創(chuàng)建了一個(gè)DecroView,其中創(chuàng)建的過(guò)程中可能根據(jù)Theme不同,加載不同的布局格式,例如有沒(méi)有Title,或有沒(méi)有ActionBar等,然后再向mContentParent中加入子View,即Activity中設(shè)置的布局。到此位置,視圖一層層嵌套添加上了。

下面具體來(lái)看看installDecor();方法,怎么創(chuàng)建的DecroView,并設(shè)置其整體布局?

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); // 為DecorView設(shè)置布局格式,并返回mContentParent
        ...
        } 
    }
}

再來(lái)看看 generateDecor()

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

很簡(jiǎn)單,創(chuàng)建了一個(gè)DecorView。

再看generateLayout

protected ViewGroup generateLayout(DecorView decor) {
        // 從主題文件中獲取樣式信息
        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);
        }
     ................
        // 根據(jù)主題樣式,加載窗口布局
        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

       //往DecorView中添加子View,即文章開(kāi)頭介紹DecorView時(shí)提到的布局格式,那只是一個(gè)例子,根據(jù)主題樣式不同,加載不同的布局。
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 
        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;
    }

雖然比較復(fù)雜,但是邏輯還是很清楚的。先從主題中獲取樣式,然后根據(jù)樣式,加載對(duì)應(yīng)的布局到DecorView中,然后從中獲取mContentParent。獲得到之后,可以回到上面的代碼,為mContentParent添加View,即Activity中的布局。

以上就是DecorView的創(chuàng)建過(guò)程,其實(shí)到installDecor()就已經(jīng)介紹完了,后面只是具體介紹其中的邏輯。

三、DecorView的顯示

以上僅僅是將DecorView建立起來(lái)。通過(guò)setContentView()設(shè)置的界面,為什么在onResume()之后才對(duì)用戶可見(jiàn)呢?

這就要從ActivityThread開(kāi)始說(shuō)起了,由于個(gè)人也是一知半解,僅僅闡述下個(gè)人理解吧。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //就是在這里調(diào)用了Activity.attach()呀,接著調(diào)用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由于只是初始化了mDecor,添加了布局文件,還沒(méi)有把
    //mDecor添加到負(fù)責(zé)UI顯示的PhoneWindow中,所以這時(shí)候?qū)τ脩魜?lái)說(shuō),是不可見(jiàn)的
    Activity a = performLaunchActivity(r, customIntent);

    ......

    if (a != null) {
    //這里面執(zhí)行了Activity.onResume()
    handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

    if (!r.activity.mFinished && r.startsNotResumed) {
        try {
                    r.activity.mCalled = false;
                    //執(zhí)行Activity.onPause()
                    mInstrumentation.callActivityOnPause(r.activity);
                    }
        }
    }
}

重點(diǎn)看下handleResumeActivity(),在這其中,DecorView將會(huì)顯示出來(lái),同時(shí)重要的一個(gè)角色:ViewRoot也將登場(chǎng)。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

            //這個(gè)時(shí)候,Activity.onResume()已經(jīng)調(diào)用了,但是現(xiàn)在界面還是不可見(jiàn)的
            ActivityClientRecord r = performResumeActivity(token, clearHide);

            if (r != null) {
                final Activity a = r.activity;
                  if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //decor對(duì)用戶不可見(jiàn)
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
              
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //被添加進(jìn)WindowManager了,但是這個(gè)時(shí)候,還是不可見(jiàn)的
                    wm.addView(decor, l);
                }

                if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                     //在這里,執(zhí)行了重要的操作,使得DecorView可見(jiàn)
                     if (r.activity.mVisibleFromClient) {
                            r.activity.makeVisible();
                        }
                    }
            }

當(dāng)我們執(zhí)行了Activity.makeVisible()方法之后,界面才對(duì)我們是可見(jiàn)的。

void makeVisible() {
   if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到WindowManager
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);//DecorView可見(jiàn)
    }

到此DecorView便可見(jiàn),顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因?yàn)槠鋬?nèi)部創(chuàng)建了一個(gè)ViewRootImpl對(duì)象,負(fù)責(zé)繪制顯示各個(gè)子View。

具體來(lái)看addView()方法,因?yàn)閃indowManager是個(gè)接口,具體是交給WindowManagerImpl來(lái)實(shí)現(xiàn)的。

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);
    }
}

交給WindowManagerGlobal 的addView()方法去實(shí)現(xiàn)

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

              final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
             ......
                 synchronized (mLock) {

                 ViewRootImpl root;
                  //實(shí)例化一個(gè)ViewRootImpl對(duì)象
                 root = new ViewRootImpl(view.getContext(), display);
                 view.setLayoutParams(wparams);

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

             try {
                //將DecorView交給ViewRootImpl 
                root.setView(view, wparams, panelParentView);
            }catch (RuntimeException e) {
            }

            }
 }

看到其中實(shí)例化了ViewRootImpl對(duì)象,然后調(diào)用其setView()方法。其中setView()方法經(jīng)過(guò)一些列折騰,最終調(diào)用了performTraversals()方法,然后依照下圖流程層層調(diào)用,完成繪制,最終界面才顯示出來(lái)。

其實(shí)ViewRootImpl的作用不止如此,還有許多功能,如事件分發(fā)。

要知道,當(dāng)用戶點(diǎn)擊屏幕產(chǎn)生一個(gè)觸摸行為,這個(gè)觸摸行為則是通過(guò)底層硬件來(lái)傳遞捕獲,然后交給ViewRootImpl,接著將事件傳遞給DecorView,而DecorView再交給PhoneWindow,PhoneWindow再交給Activity,然后接下來(lái)就是我們常見(jiàn)的View事件分發(fā)了。

硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity

不詳細(xì)介紹了,如果感興趣,可以看這篇文章

由此可見(jiàn)ViewRootImpl的重要性,是個(gè)連接器,負(fù)責(zé)WindowManagerService與DecorView之間的通信。

四、總結(jié)

以上通過(guò)源碼形式介紹了Window、Activity、DecorView以及ViewRoot之間的錯(cuò)綜關(guān)系,以及如何創(chuàng)建并顯示DecorView。

通過(guò)以上了解可以知道,Activity就像個(gè)控制器,不負(fù)責(zé)視圖部分。Window像個(gè)承載器,裝著內(nèi)部視圖。DecorView就是個(gè)頂層視圖,是所有View的最外層布局。ViewRoot像個(gè)連接器,負(fù)責(zé)溝通,通過(guò)硬件的感知來(lái)通知視圖,進(jìn)行用戶之間的交互。

參考文章:
Android View源碼解讀:淺談DecorView與ViewRootImpl
window、Activity、DecorView、ViewRoot關(guān)系
Android 源碼解析 之 setContentView
淺析 android 應(yīng)用界面的展現(xiàn)流程(二)布局與視圖的創(chuàng)建
【凱子哥帶你學(xué)Framework】Activity界面顯示全解析(下)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372

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