View的繪製一:View是如何被新增到螢幕視窗上的

talang發表於2019-03-24

從activity的setContentView()方法從原始碼的角度來解析Android繪製UI的流程。

先來看看View是如果新增到螢幕視窗上的:

首先開啟activity的setContentView()的方法,原始碼如下:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}複製程式碼

由此可見activity的setContentView()方法中是呼叫的getWindow()方法的setContentView。getWindow()方法中返回的是Window,Window類是一個抽象類,它只有唯一的一個實現類為PhoneWindow。所有我們可以去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) {
        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);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}複製程式碼

這裡我們主要關注installDecor()和 mLayoutInflater.inflate(layoutResID, mContentParent)這兩個方法。首先我們先看看installDecor()方法的實現:

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);  //給mDecor賦值  
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

        ......
    }
}

複製程式碼

首先在instanllDecor()方法中首先對mDecor判斷是否為空,mDecor即為DecorView,DecorView是一個繼承自FrameLayout 的佈局。如果mDecor為空,  就會呼叫 generateDecor(),在這個方法中建立了DecorView物件。

然後又對mContentParent判斷是否為空,如果為空則會呼叫generateLayout(mDecor)方法,在此方法中給mContentParent賦值,我們再來看看generateLayout(mDecor)程式碼的具體實現:

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.

    TypedArray a = getWindowStyle();

    ....... 
    //根據主題樣式呼叫requestFeature()和setFlags()等方法。

    // Inflate the window decor. 

    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    //這裡根據設定的features不同來給layoutResource設定不同的值。
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);

    ...... 此處省幾百行程式碼

    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    //將layoutResource解析成view,並將view新增到DecorView中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  


    //這裡是獲取到主容器,ID_ANDROID_CONTENT為主容器的資源id,一定存在,有系統定義    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ......

    return contentParent;
}複製程式碼

此方法中重點看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);呼叫了DecorView的onResourcesLoaded()方法,下面檢視下onResourcesLoaded()方法的實現:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ......
    //將layoutResource進行解析,建立root物件
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {

        // Put it below the color views.  將root這個View物件新增到DecorView中
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}複製程式碼

有以上程式碼可見DecorView的onResourcesLoaded()方法主要做了兩件事:解析layoutResource為root物件,並將root新增到DecorView中。

總結一下PhoneWindow中的installDecor()方法:通過generateDecor()方法建立了一個DecorView根佈局。 然後呼叫generateLayout()方法根據不同的主題渲染不同的佈局並新增到DecorView中。

phoneWindow在setContentView中呼叫了installDecor()方法後,然後又呼叫mLayoutInflater.inflate(layoutResID, mContentParent);這裡layoutResID是我們activity中設定的佈局id,mContentParent是id為Android系統定義的@android:id/content 佈局,如下圖所示。最終我們再activity中定義的佈局就是新增到mContentParent中。


View的繪製一:View是如何被新增到螢幕視窗上的

總結:View是如何新增到螢幕上的?

1.系統會建立頂層佈局容器DecorView,DecorView繼承自FrameLayout,是PhoneWindow物件持有的一個例項,它是所有應用的頂層View,在系統內部進行初始化;

2.DecorView初始化完成之後,系統會根據應用程式的主題樣式去載入一個基礎容器,如NoActionBar、DarkActionBar這些主題對應不同的基礎容器,但是每個容器都有android系統內部定義的id為android.R.content 的FrameLayout的容器。

3.開發者通過setContentView方法設定的layout佈局就是新增到這個FarmeLayout容器中。


相關文章