1. 程式人生 > >Android面試收集錄11 Window+Activity+DecorView+ViewRoot之間的關系

Android面試收集錄11 Window+Activity+DecorView+ViewRoot之間的關系

包含 err play name 抽象 getattr 代碼 操作 rem

一、職能簡介

Activity

Activity並不負責視圖控制,它只是控制生命周期和處理事件。真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個窗口。Activity就像一個控制器,統籌視圖的添加與顯示,以及通過其他回調方法,來與Window、以及View進行交互。

Window

Window是視圖的承載器,內部持有一個 DecorView,而這個DecorView才是 view 的根布局。Window是一個抽象類,實際在Activity中持有的是其子類PhoneWindow。PhoneWindow中有個內部類DecorView,通過創建DecorView來加載Activity中設置的布局R.layout.activity_main

。Window 通過WindowManager將DecorView加載其中,並將DecorView交給ViewRoot,進行視圖繪制以及其他交互。

DecorView

DecorView是FrameLayout的子類,它可以被認為是Android視圖樹的根節點視圖。DecorView作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout,**在這個LinearLayout裏面有上下三個部分,上面是個ViewStub,延遲加載的視圖(應該是設置ActionBar,根據Theme設置),中間的是標題欄(根據Theme設置,有的布局沒有),下面的是內容欄。**具體情況和Android版本及主體有關,以其中一個布局為例,如下所示:

<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中通過setContentView所設置的布局文件其實就是被加到內容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過content來得到對應加載的布局。

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

ViewRoot

ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發等交互都是通過它來執行或傳遞的。

ViewRoot對應ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測量(measure),布局(layout),繪制(draw))均通過ViewRoot來完成。

ViewRoot並不屬於View樹的一份子。從源碼實現上來看,它既非View的子類,也非View的父類,但是,它實現了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件並分發,Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過ViewRoot進行分發的。

下面結構圖可以清晰的揭示四者之間的關系:

技術分享圖片

二、DecorView的創建

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

setContentView

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

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

可以看到實際是交給Window裝載視圖。下面來看看Activity是怎麽獲得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,
            Window window) {
    ..................................................................
        mWindow = new PhoneWindow(this, window);//創建一個Window對象
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);//設置回調,向Activity分發點擊或狀態改變等事件
        mWindow.setOnWindowDismissedCallback(this);
     .................................................................
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//給Window設置WindowManager對象
 ....................................................................
    }

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

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

看了下來,可能有一個疑惑:mContentParent到底是什麽? 就是前面布局中@android:id/content所對應的FrameLayout。

通過上面的流程我們大致可以了解先在PhoneWindow中創建了一個DecroView,其中創建的過程中可能根據Theme不同,加載不同的布局格式,例如有沒有Title,或有沒有ActionBar等,然後再向mContentParent中加入子View,即Activity中設置的布局。到此位置,視圖一層層嵌套添加上了。

下面具體來看看installDecor();方法,怎麽創建的DecroView,並設置其整體布局?

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設置布局格式,並返回mContentParent
        ...
        } 
    }
}

再來看看 generateDecor()

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

很簡單,創建了一個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);
        }
     ................
        // 根據主題樣式,加載窗口布局
        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,即文章開頭介紹DecorView時提到的布局格式,那只是一個例子,根據主題樣式不同,加載不同的布局。
        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;
    }

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

以上就是DecorView的創建過程,其實到installDecor()就已經介紹完了,後面只是具體介紹其中的邏輯。

三、DecorView的顯示

以上僅僅是將DecorView建立起來。通過setContentView()設置的界面,為什麽在onResume()之後才對用戶可見呢?

這就要從ActivityThread開始說起。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //就是在這裏調用了Activity.attach()呀,接著調用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由於只是初始化了mDecor,添加了布局文件,還沒有把
    //mDecor添加到負責UI顯示的PhoneWindow中,所以這時候對用戶來說,是不可見的
    Activity a = performLaunchActivity(r, customIntent);

    ......

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

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

重點看下handleResumeActivity(),在這其中,DecorView將會顯示出來,同時重要的一個角色:ViewRoot也將登場。

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

            //這個時候,Activity.onResume()已經調用了,但是現在界面還是不可見的
            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對用戶不可見
                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;
                    //被添加進WindowManager了,但是這個時候,還是不可見的
                    wm.addView(decor, l);
                }

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

當我們執行了Activity.makeVisible()方法之後,界面才對我們是可見的。

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

到此DecorView便可見,顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因為其內部創建了一個ViewRootImpl對象,負責繪制顯示各個子View。

具體來看addView()方法,因為WindowManager是個接口,具體是交給WindowManagerImpl來實現的。

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()方法去實現

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

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

                 ViewRootImpl root;
                  //實例化一個ViewRootImpl對象
                 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) {
            }

            }
 }

看到其中實例化了ViewRootImpl對象,然後調用其setView()方法。其中setView()方法經過一些列折騰,最終調用了performTraversals()方法,然後依照下圖流程層層調用,完成繪制,最終界面才顯示出來。

技術分享圖片

其實ViewRootImpl的作用不止如此,還有許多功能,如事件分發。

要知道,當用戶點擊屏幕產生一個觸摸行為,這個觸摸行為則是通過底層硬件來傳遞捕獲,然後交給ViewRootImpl,接著將事件傳遞給DecorView,而DecorView再交給PhoneWindow,PhoneWindow再交給Activity,然後接下來就是我們常見的View事件分發了。

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

不詳細介紹了,如果感興趣,可以看這篇文章。

由此可見ViewRootImpl的重要性,是個連接器,負責WindowManagerService與DecorView之間的通信。

四、總結

以上通過源碼形式介紹了Window、Activity、DecorView以及ViewRoot之間的錯綜關系,以及如何創建並顯示DecorView。

通過以上了解可以知道,

Activity就像個控制器,不負責視圖部分。

Window像個承載器,裝著內部視圖。

DecorView就是個頂層視圖,是所有View的最外層布局。

ViewRoot像個連接器,負責溝通,通過硬件的感知來通知視圖,進行用戶之間的交互。

Android面試收集錄11 Window+Activity+DecorView+ViewRoot之間的關系