window、Activity、DecorView、ViewRoot關系

mtwz2846 8年前發布 | 29K 次閱讀 Activity Android開發 移動開發

簡介

  • Activity并不負責視圖控制,它只是控制生命周期和處理事件,真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個窗口,Window 中持有一個 DecorView,而這個DecorView才是 view 的根布局

  • DecorView是FrameLayout的子類,它可以被認為是Android視圖樹的根節點視圖。DecorView作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout,在這個LinearLayout里面有上下兩個部分(具體情況和Android版本及主體有關),上面的是標題欄,下面的是內容欄。在Activity中通過setContentView所設置的布局文件其實就是被加到內容欄之中的,而內容欄的id是content,在代碼中可以通過ViewGroup content = (ViewGroup)findViewById(R.android.id.content)來得到content對應的layout。

  • <!-- more -->

  • ViewRoot對應 ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶 ,View的三大流程(測量(measure),布局(layout),繪制(draw))均通過ViewRoot來完成。ViewRoot并不屬于View樹的一份子。從源碼實現上來看,它既非View的子類,也非View的父類,但是, 它實現了ViewParent接口,這讓它可以作為View的名義上的父視圖 。RootView繼承了Handler類,可以接收事件并分發,Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過ViewRoot進行分發的。ViewRoot可以被理解為“View樹的管理者”——它有一個mView成員變量,它指向的對象和上文中Window和Activity的mDecor指向的對象是同一個對象。

從 setContentView 分析起

眾所周知,在 activity 中,setContentView方法可以設置我們需要的布局,那么就從這個作為切入點開始分析

//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;
}</code></pre> 

繼續尋找 mWindow 的賦值時機

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

可以看到在attach,實例化了一個PhoneWindow對象(window 的實現),繼續看 window 中 setContentView的實現

@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,那么就新建一個
   } 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的注釋,后續做分析
    // 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;

創建DecorView

繼續看 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] 以上都是根據不同的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中,所以應該是其第一個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;
    }

由以上代碼可以看出,該方法還是做了相當多的工作的,首先根據設置的主題樣式來設置DecorView的風格,比如說有沒有titlebar之類的,接著為DecorView添加子View,而這里的子View則是上面提到的mContentParent,如果上面設置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一個子View,這也解釋了上面的注釋: mContentParent是DecorView本身或者是DecorView的一個子元素。

小結:

  • DecorView是頂級View, 內部有titlebar和contentParent兩個子元素,contentParent的id是content,而我們設置的main.xml布局則是contentParent里面的一個子元素。
  • 在DecorView創建完畢后,讓我們回到PhoneWindow#setContentView方法,LayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們設置的main.xml布局文件,并且設置mContentParent為main.xml的父布局,

到目前為止,通過setContentView方法,創建了DecorView和加載了我們提供的布局,但是這時,我們的View還是不可見的,因為我們僅僅是加載了布局,并沒有對View進行任何的測量、布局、繪制工作。在View進行測量流程之前,還要進行一個步驟,那就是把DecorView添加至window中,然后經過一系列過程觸發ViewRootImpl#performTraversals方法,在該方法內部會正式開始測量、布局、繪制這三大流程。

將DecorView添加至Window

每一個Activity組件都有一個關聯的Window對象,用來描述一個應用程序窗口。每一個應用程序窗口內部又包含有一個View對象,用來描述應用程序窗口的視圖。上文分析了創建DecorView的過程,現在則要把DecorView添加到Window對象中。而要了解這個過程,我們首先要簡單先了解一下Activity的創建過程:

首先,在ActivityThread#handleLaunchActivity中啟動Activity,在這里面會調用到Activity#onCreate方法,從而完成上面所述的DecorView創建動作,當onCreate()方法執行完畢,在handleLaunchActivity方法會繼續調用到ActivityThread#handleResumeActivity方法,

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

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

        //...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // 獲得window對象
            View decor = r.window.getDecorView(); // 獲得DecorView對象
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 獲得windowManager對象
            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); // 調用addView方法
            }
            //...
        }
    }
}

在該方法內部,獲取該activity所關聯的window對象,DecorView對象,以及windowManager對象,而WindowManager是抽象類,它的實現類是WindowManagerImpl,所以后面調用的是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);
    }
}

接著調用了mGlobal的成員函數,而mGlobal則是WindowManagerGlobal的一個實例,那么我們接著看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;
        }
    }

先看①號代碼處,實例化了ViewRootImpl類,接著,在②號代碼處,調用ViewRootImpl#setView方法,并把DecorView作為參數傳遞進去,在這個方法內部,會通過跨進程的方式向WMS(WindowManagerService)發起一個調用,從而將DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關聯,至于詳細過程這里不展開來說了。

最后通過WMS調用ViewRootImpl#performTraverals方法開始View的測量、布局、繪制流程

參考

http://gold.xitu.io/entry/571338c7c4c9710054cea455

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

 

來自:http://www.jianshu.com/p/049df709ddbf

 

 本文由用戶 mtwz2846 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!