Android中關於Activity的setContentView方法的剖析

前言

最近看了Android中,Activity->ViewGroup->View的事件分發機制,於是順帶就看了下setContentView方法的源碼。在這裏和大家分享一下我的個人所得。

正文

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

這是Activity中setContentView的源碼,先看getWindow().setContentView(layoutResID);這裏的getWindow()(我們都知道每個Activity中都持有一個自己的Window對象,而Window類是一個abstract抽象類,它的唯一的實現類,即PhoneWindow)實際上指的是PhoneWindow對象。那麼我們就一起看一下PhoneWindow這個類中的setContentView方法:

@Override
    public void setContentView(int layoutResID) {
       
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ...
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
       ....
    }

這裏省略了一部分代碼,我們看主要的:

如果mContentParent這個變量爲空,那麼就執行 installDecor();而這裏的mContentParent,我們看源碼可以知道,其實是一個ViewGroup:

ViewGroup mContentParent;

先不管這個,我們看一下installDecor()方法:

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            ...
           }
}

還是省略一些代碼,我們看主要的,這裏對mDecor變量進行了判斷,mDecor實際上是個DecorView,也就是說在這裏判斷有沒有DecorView對象,如果有就把這個DecorView的對象,通過setWindow(this)和PhoneWindow做關聯,這也解釋了爲什麼一個PhoneWindow裏面包含了一個DecorView,而DecorView是繼承於FrameLayout的,FrameLayout又是一個ViewGroup。扯遠了,言歸正傳,我們看一下generateDecor(-1):

protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
    return new DecorView(context, featureId, this, getAttributes());
}

我們發現在generateDecor中創建了DecorView的對象。

再返回上面看installDecor方法,這裏還對mContentParent變量做了判空,如果這個變量爲空,那麼就通過generateLayout方法創建,我們來看下generateLayout方法:

protected ViewGroup generateLayout(DecorView decor) {
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
           ...
        } else {
            layoutResource = R.layout.screen_simple;
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...

        return contentParent;
    }

代碼太長,我們只看重點部分,這裏有個screen_simple佈局,我們用這個screen_simple佈局來做剖析,我們看下screen_simple佈局是什麼樣子:

發現這個佈局的根佈局是個垂直方向的LinearLayout,裏面包含了兩個View,一個是ViewStub,另一個就是FrameLayout,注意這個FrameLayout的id:android:id="@android:id/content"。我們在這裏先管這個FrameLayout叫做內容佈局吧。

再返回來看generateLayout方法中的這行代碼:

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

我們看下DecorView中的onReSourcesLoaded方法做了哪些事情?

先看這個方法的兩個參數,一個是佈局填充器,一個就是我們的R.layout.screen_simple佈局。

 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...
        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.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
}

通過查看onResourcesLoaded方法可以知道,裏面調用了addView方法,把R.layout.screen_simple佈局填充的View添加到了DecorView中。

還接着看generateLayout方法,看裏面的:

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

我們看下這裏的ID_ANDROID_CONTENT:

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

這裏通過findViewById方法,找到了id爲,R.id.content的View,也就是我們前面看到的screen_simple佈局中的那個FrameLayout。(這裏我們把findViewById方法也找到了,這個其實是和我們常用的findViewById方法都是一樣的,其歸根結底都是調用的Window類裏面的findViewById方法)

@Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

然後generateLayout方法返回了這個contentParent,也就是我們在上面看到了mContentParent變量賦了值。

小結:由上面的分析可知,其實Activity中的setContentView方法,最終是把佈局添加到了DecorView中一個id爲content的FrameLayout裏面。

這裏借用某大神的一張一個Activity的層次圖:

其實我們平時在Activity中使用的setContentView就是把Activity的佈局添加到了黃色部分的FrameLayout上。

瞭解上面關於Content部分的View,不要忘記,上面我們在Activity的setContentView方法裏面還有一行代碼:

initWindowDecorActionBar();

這個就是關於ActionBar的顯示,我們一起來看一下這個方法:

private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

在這個方面裏面創建了一個WindowDecorActionBar對象,我們來看一下,WindowDecorActionBar對應的構造方法:

@RestrictTo(LIBRARY_GROUP_PREFIX)
    public WindowDecorActionBar(View layout) {
        assert layout.isInEditMode();
        init(layout);
}

發現裏面有一個init方法:

private void init(View decor) {
        mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent);
        if (mOverlayLayout != null) {
            mOverlayLayout.setActionBarVisibilityCallback(this);
        }
        mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
        mContextView = (ActionBarContextView) decor.findViewById(
                R.id.action_context_bar);
        mContainerView = (ActionBarContainer) decor.findViewById(
                R.id.action_bar_container);

        if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
                    "with a compatible window decor layout");
        }

        mContext = mDecorToolbar.getContext();

        // This was initially read from the action bar style
        final int current = mDecorToolbar.getDisplayOptions();
        final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
        if (homeAsUp) {
            mDisplayHomeAsUpSet = true;
        }

        ActionBarPolicy abp = ActionBarPolicy.get(mContext);
        setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
        setHasEmbeddedTabs(abp.hasEmbeddedTabs());

        final TypedArray a = mContext.obtainStyledAttributes(null,
                R.styleable.ActionBar,
                R.attr.actionBarStyle, 0);
        if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
            setHideOnContentScrollEnabled(true);
        }
        final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
        if (elevation != 0) {
            setElevation(elevation);
        }
        a.recycle();
    }

這裏面有這麼幾行代碼:

mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
        mContextView = (ActionBarContextView) decor.findViewById(
                R.id.action_context_bar);
        mContainerView = (ActionBarContainer) decor.findViewById(
                R.id.action_bar_container);

在分析Content的時候,我們用的是R.layout.screen_simple,這裏我們看下含有ActionBar對應的佈局:screen_action_bar,

以screen_action_bar佈局爲例:

<com.android.internal.widget.ActionBarOverlayLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/decor_content_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false"
    android:theme="?attr/actionBarTheme">
    <FrameLayout android:id="@android:id/content"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" />
    <com.android.internal.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:transitionName="android:action_bar"
        android:touchscreenBlocksFocus="true"
        android:keyboardNavigationCluster="true"
        android:gravity="top">
        <com.android.internal.widget.ActionBarView
            android:id="@+id/action_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="?attr/actionBarStyle" />
        <com.android.internal.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="?attr/actionModeStyle" />
    </com.android.internal.widget.ActionBarContainer>
    <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="?attr/actionBarSplitStyle"
                  android:visibility="gone"
                  android:touchscreenBlocksFocus="true"
                  android:keyboardNavigationCluster="true"
                  android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>

可以看到這裏的id爲action_bar以及id爲action_context_bar以及id爲action_bar_container,這些都是在DecorView裏面的,所以也不難明白這些ActionBar爲什麼都存在於DecorView中了。

總結

通過上面對Activity中setContentView方法的源碼剖析,可以瞭解到一個Activity的視圖層次,以及一個Activity在setContentView的時候都做了哪些操作,以及怎樣把我們創建的佈局添加到Activity中展示出來的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章