从setContentView看Activity的构成

  常规地,我们会在Activity的onCreate()方法内调用setContentView(R.layout.xxx)以此来设置页面布局。那么,这个方法背后到底隐藏了什么操作呢?PS:以下源码是参照API 25

  首先,我们先进入Activity#setContentView(int layoutResID)

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

    public Window getWindow() {
        return mWindow;
    }

  然后我们可以发现,实际是调用了getWindow()的setContentView(),而getWindow()返回的是成员变量mWindow,mWindow是Window类型,是个抽象类。而mWindow在Activity#attach时被赋值:

    mWindow = new PhoneWindow(this, window);

  所以实际调用的是PhoneWindow#setContentView(),进入其中:

    public void setContentView(int layoutResID) {
        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;
    }

  从第13行代码看出:从外部传进来的layoutResID会被LayoutInflater inflate的,然后被添加到mContentParent里。接下来我们一起探究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.
    ViewGroup mContentParent;

  注释上说,这个View是用来放window的内容的,可以是mDecor或者是mDecor的子View。而这个mDecor其实就是DecorView,而DecorView的解释是:

     // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

  它是Window最上层的View,它包含了Window的装饰。
  扯远了扯远了,我们回过头来看刚才的PhoneWindow#setContentView(),从上往下看,当mContentParent == null时,调用installDecor():

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //实例化DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }

        if (mContentParent == null) {
            //生成mContentParent
            mContentParent = generateLayout(mDecor);
        ...
   }

    protected DecorView generateDecor(int featureId) {
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }

  判断mDecor为空则new一个DecorView,并且把当前Window实例传给它。然后mContentParent为空则把DecorView传进generateLayout()里生成一个mContentParent。generateLayout这个方法巨长,省略一些不太重要的代码片段:

protected ViewGroup generateLayout(DecorView decor) {
        ...

        // 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 ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } 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();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        ...

        mDecor.finishChanging();

        return contentParent;
}

  前面一大段都在根据FEATURE值来决定到底是要用哪个layout文件,也就是layoutResource的值。然后调用mDecor.onResourcesLoaded()方法,再通过findViewById(ID_ANDROID_CONTENT)找到contentParent最后返回,赋值给PhoneWindow里的mContentParent。

  从前面一大段if else看出,layoutResource可以是layout.screen_title_icons,layout.screen_custom_title,layout.screen_title等。打开这些layout,可以发现都存在一个id为”@android:id/content”的FrameLayout。以layout.screen_simple为例:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <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:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

  而ID_ANDROID_CONTENT的值其实就是

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

  刚好能在上述layout里找到,所以能find到,是不是很magic~。既然能find到,说明肯定有把layoutResource inflate出来再add。

  我们再进入mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);看一看。注意这里是调用了DecorView里的方法,所以接下来的addView都是add到DecorView里

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...

        mDecorCaptionView = createDecorCaptionView(inflater);
        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();
    }

  可以看到,会生成mDecorCaptionView和root。如果mDecorCaptionView不为空,则把它加入到DecorView里,然后再把root加入到它里面;否则,直接把root加入到DecorView里。

总结

Activity里有phoneWindow,phoneWindow内有decorView,decorView里子View有两种情况:
  1. 先是mDecorCaptionView,往里一级才是layoutResource inflate出来的root布局
  2. 直接是root布局
再然后,root里一定包含id为”@android:id/content”的FrameLayout,FrameLayout里才是我们通过setContentView传进去的布局

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值