setContentView方法大家再熟悉不过了,但是能说说setContentView加载原理的并不多,现在我们就来了解下这个熟悉且神秘的朋友

1  源码分析

(1)Activity

Activity是在onCreate方法中使用setContentView方法来加载布局的,所以当然要进入Activity

public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }

这里的getWindow()无疑就是PhoneWindow,在前面的源码分析中出现多次,所以重点在PhoneWindow中。对于Window和PhoneWindow将在他们的主战场进行分析

(2)PhoneWindow

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        ......
    }

可以看到,首先判断mContentParent是否为null,是则调用installDecor(),否则移除其内部所有的子Views,然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中,mContentParent是个ViewGroup且包裹我们整个布局文件;而installDecor()就是去初始化我们这个mContentParent

下面重点看看installDecor方法

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
           ......
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
       ......
     }
}

很显然,在这里实例化了大名鼎鼎的DecorView,DecorView继承FrameLayout,然后将DecorView传入generateLayout生成mContentParent 
这个mContentParent 又是什么鬼呢?

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

这里new DecorView(getContext(), -1);没干啥


public DecorView(Context context, int featureId) {  
          super(context);  
          mFeatureId = featureId;  
      }

所以重点在重点在generateLayout中


protected ViewGroup generateLayout(DecorView decor) {  
                    // 首先getWindowStyle获取我们当前Window中定义的theme属性,我们在清单文件中设置的Activity theme在这里进行处理 
                    TypedArray a = getWindowStyle();  
              
                    //然后通过WindowStyle中设置的各种属性,对Window进行requestFeature或者setFlags  
              
                    if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {  
                        requestFeature(FEATURE_NO_TITLE);  
                    }  
                    //...  
                    if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {  
                        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));  
                    }  
                    
                    // 下面的过程通过对features和mIsFloating的判断为layoutResource赋值
                    // features的设置我们可以通过android:theme="..."我们也可以在onCreate的setContentView之前进行requestFeature
                    int layoutResource;  
                    int features = getLocalFeatures();  
                    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
                        if (mIsFloating) {  
                            TypedValue res = new TypedValue();  
                            getContext().getTheme().resolveAttribute(  
                                    com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);  
                            layoutResource = res.resourceId;  
                       ......
                    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {  
                        layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;  
                    } else {  
                        layoutResource = com.android.internal.R.layout.screen_simple;  
                    }  
              
              	    //得到了layoutResource以后,通过LayoutInflater把布局转化成view,加入到我们的decorView中
                    View in = mLayoutInflater.inflate(layoutResource, null);  
                    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
              
                    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
                    //...  
              
                    return contentParent;  
                }  
         }




这里出现了decorView,layoutResource ,mContentParent 他们是什么关系呢?


我们知道decoreView是继承FrameLayout,layoutResource 是根据theme选择出来的系统布局,类似下面的XML


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" />
    <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>

以及


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:fitsSystemWindows="true">  
    <!-- Popout bar for action modes -->  
    <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" />  
  
    <FrameLayout android:id="@android:id/title_container"   
        android:layout_width="match_parent"   
        android:layout_height="?android:attr/windowTitleSize"  
        style="?android:attr/windowTitleBackgroundStyle">  
    </FrameLayout>  
    <FrameLayout android:id="@android:id/content"  
        android:layout_width="match_parent"   
        android:layout_height="0dip"  
        android:layout_weight="1"  
        android:foregroundGravity="fill_horizontal|top"  
        android:foreground="?android:attr/windowContentOverlay" />  
</LinearLayout>

layoutResource 被infalte成View并加入到DecorView中,这类layoutResource的view都有一个id为content的FrameLayout。


generateLayout中ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT) 就是将id为content的


FrameLayout返回给mContentParent,然后setContentView中mLayoutInflater.inflate(layoutResID, mContentParent); 就是将


setContentView传进来的layoutResID(Activity所在的布局id)加载到mContentParent中




2 总结


总结下整个流程:


首先初始化mDecor(DecorView为FrameLayout的子类,就是我们整个窗口的根视图了)。


然后,根据theme中的属性值,选择合适的布局,通过infalter.inflater放入到我们的mDecor中。


在这些布局中,会包含ActionBar,Title,和一个id为content的FrameLayout。


最后,我们在Activity中设置的布局,会通过infalter.inflater压入到我们的id为content的FrameLayout中去

最后附上经典图片:

Android setUserVisibleHint用法 android setcontentview方法_xml