android view(2) Activity、Window、DecorView

參考
Android View源碼解讀:淺談DecorView與ViewRootImpl
從ViewRootImpl類分析View繪制的流程
Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃
Android開發藝術探索 Window和WindowManager

一、setContentView

一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:
setContentView(R.layout.main);
顯然,這是為activity設置一個我們定義好的main.xml布局,我們跟蹤一下源碼,看看這個方法是怎樣做的,Activity#setContentView:

public void setContentView(@LayoutRes int layoutResID) {
     getWindow().setContentView(layoutResID);  //調用getWindow方法,返回mWindow
     initWindowDecorActionBar();
}
...
public Window getWindow() {   
     return mWindow;
}

從上面看出,里面調用了mWindow的setContentView方法,那么這個“mWindow”是何方神圣呢?嘗試追蹤一下源碼,發現mWindow是Window類型的,但是它是一個抽象類,setContentView也是抽象方法,所以我們要找到Window類的實現類才行。我們在Activity中查找一下mWindow在哪里被賦值了,可以發現它在Activity#attach方法中有如下實現:

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) {
        ...
        mWindow = new PhoneWindow(this);
        ...
    }

我們只看關鍵部分,這里實例化了PhoneWindow類,由此得知,PhoneWindow是Window的實現類,那么我們在PhoneWindow類里面找到它的setContentView方法,看看它又實現了什么,PhoneWindow#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) { // 1
        installDecor();
    } 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); // 2
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

首先判斷了mContentParent是否為null,如果為空則執行installDecor()方法,那么這個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;

它是一個ViewGroup類型,結合②號代碼處,可以得知,這個mContentParent是我們設置的布局(即main.xml)的父布局。注釋還提到了,這個mContentParent是mDecor本身或者是mDecor的一個子元素,這句話什么意思呢?這里先留一個疑問,下面會解釋。

這里先梳理一下以上的內容:Activity通過PhoneWindow的setContentView方法來設置布局,而設置布局之前,會先判斷是否存在mContentParent,而我們設置的布局文件則是mContentParent的子元素。

二、創建DecorView

接著上面提到的installDecor()方法,我們看看它的源碼,PhoneWindow#installDecor:

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

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

可以看出,這里實例化了DecorView,而DecorView則是PhoneWindow類的一個內部類,繼承于FrameLayout,由此可知它也是一個ViewGroup。那么,DecroView到底充當了什么樣的角色呢?其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應用的界面。在該布局下面,有標題view和內容view這兩個子元素,而內容view則是上面提到的mContentParent。我們接著看②號代碼,PhoneWindow#generateLayout方法

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(...){
            ...
        }

        // 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(...){
            ...
        }

        //加載layoutResource
        View in = mLayoutInflater.inflate(layoutResource, null);
        //往DecorView中添加子View,即mContentParent
        decor.addView(in, new ViewGroup.LayoutParams(
        MATCH_PARENT, MATCH_PARENT)); 
        mContentRoot = (ViewGroup) in;
        // 這里獲取的就是mContentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 
        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方法,直接看②號代碼: mLayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們設置的main.xml布局文件,并且設置mContentParent為main.xml的父布局,至于它怎么加載的,這里就不展開來說了。

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

以上參考自Android View源碼解讀:淺談DecorView與ViewRootImpl
以下參考自從ViewRootImpl類分析View繪制的流程

三、將DecorView添加至Window
Paste_Image.png

DecorView是怎么添加到窗口的呢?這時候我們不得不從Activity是怎么啟動的說起,當Activity初始化 Window和將布局添加到

PhoneWindow的內部類DecorView類之后,ActivityThread類會調用handleResumeActivity方法將頂層視圖DecorView添加到PhoneWindow窗口,來看看handlerResumeActivity方法的實現:

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

    ..................

    if (r.window == null && !a.mFinished && willBeVisible) {
        //獲得當前Activity的PhoneWindow對象
        r.window = r.activity.getWindow();
        //獲得當前phoneWindow內部類DecorView對象
        View decor = r.window.getDecorView();
        //設置窗口頂層視圖DecorView可見度
        decor.setVisibility(View.INVISIBLE);
        //得當當前Activity的WindowManagerImpl對象
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (a.mVisibleFromClient) {
            //標記根布局DecorView已經添加到窗口
            a.mWindowAdded = true;
            //將根布局DecorView添加到當前Activity的窗口上面
            wm.addView(decor, l);

    .....................

WindowManager只是一個接口,它的真正實現是WindowManagerImpl類,然后它又將所有操作委托給WindowManagerGlobal來實現,這是典型的橋接模式。

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

源碼很簡單,直接調用了 mGlobal對象的addView()方法。繼續跟蹤,mGlobal對象是WindowManagerGlobal類。進入WindowManagerGlobal類看addView()方法。

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

        ............

        ViewRootImpl root;
        View panelParentView = null;

        ............

        //獲得ViewRootImpl對象root
         root = new ViewRootImpl(view.getContext(), display);

        ...........

        // do this last because it fires off messages to start doing things
        try {
            //將傳進來的參數DecorView設置到root中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
          ...........
        }
    }

該方法中創建了一個ViewRootImpl對象root,然后調用ViewRootImpl類中的setView成員方法()。繼續跟蹤代碼進入ViewRootImpl類分析:

public void setView(View view, WindowManager.
LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
            //將頂層視圖DecorView賦值給全局的mView
                mView = view;
            .............
            //標記已添加DecorView
             mAdded = true;
            .............
            //請求布局
            requestLayout();

            .............     
        }
 }

該方法實現有點長,我省略了其他代碼,直接看以上幾行代碼:
將外部參數DecorView賦值給mView成員變量
標記DecorView已添加到ViewRootImpl
調用requestLayout方法請求布局

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    ................

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }

..............

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

...............

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

            try {
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    }

............

跟蹤代碼,最后DecorView的繪制會進入到ViewRootImpl類中的performTraversals()成員方法,這個過程可以參考上面的代碼流程圖。現在我們主要來分析下 ViewRootImpl類中的performTraversals()方法。

private void performTraversals() {
        // cache mView since it is used so much below...
        //我們在Step3知道,mView就是DecorView根布局
        final View host = mView;
        //在Step3 成員變量mAdded賦值為true,因此條件不成立
        if (host == null || !mAdded)
            return;
        //是否正在遍歷
        mIsInTraversal = true;
        //是否馬上繪制View
        mWillDrawSoon = true;

        .............
        //頂層視圖DecorView所需要窗口的寬度和高度
        int desiredWindowWidth;
        int desiredWindowHeight;

        .....................
        //在構造方法中mFirst已經設置為true,表示是否是第一次繪制DecorView
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //如果窗口的類型是有狀態欄的,那么頂層視圖
            //DecorView所需要窗口的寬度和高度就是除了狀態欄
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {//否則頂層視圖DecorView所需要窗口的寬度和高度就是整個屏幕的寬高
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
    }
............
//獲得view寬高的測量規格,mWidth和mHeight表示窗口的寬高,
//lp.widthhe和lp.height表示DecorView根布局寬和高
 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  // Ask host how big it wants to be
  //執行測量操作
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

........................
//執行布局操作
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);

.......................
//執行繪制操作
performDraw();

}
Paste_Image.png

a

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容