这个方法,其实会最终调用PhoneWindow的setContentView方法,下面看看PhoneWindow的源码:(PhoneWindow.java)
@Override
public void setContentView(int layoutResID) {
//当mContentParent为null时,则执行installDecor()
if (mContentParent == null) {
installDecor(); // 1
} 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
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
从这个方法可以看出,第一次在Activity的onCreate方法中执行setContentView时,mContentParent是为null的,这是会执行installDecor()方法,下面我们看看,installDecor方法的源码:(PhoneWindow.java)
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);// 3
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); // 4
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
//省略部分代码
// ...
}
}
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()); // 5
}
从这个方法可以看出,这个方法中,通过generateDecor(-1)方法创建一个mDecor对象,generateDecor方法中,是通过new DecorView(context, featureId, this, getAttributes())的方式来创建DecorView对象的。这里注意一下第三个参数this,这个this,就是代表了当前PhoneWindow,所以,DecorView对象内部是持有PhoneWindow的引用的。看完generateDecor(-1)方法后,我们继续看注释4处,由于第一次加载布局,所以,
mContentParent肯定是为null的,这样就会执行generateLayout(mDecor)方法,下面跟进到这个方法内部,看看具体实现:(PhoneWindow.java)
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
// 省略部分代码,这些代码都是根据window的样式来进行一些设置
// ...
//下面这部分代码是加载window的decor
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//下面代码是根据features,来确定相应的布局文件被添加到DecorView中
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} 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);// 6
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 7
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
mDecor.finishChanging();
return contentParent;
}
generateLayout这个方法内部,是根据widow的一些样式来进行一些设置,根据features来确定添加到DecorView的布局。确定完要被添加的布局文件的id后, 就通过DecorView的onResourcesLoaded方法来将布局文件的view添加到DecorView中。下面来看看DecorView的onResourcesLoaded(LayoutInflater inflater, int layoutResource)
方法的源码:(DecorView.java)
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
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,在将传入布局文件加载成view,
接着判断,如果mDecorCaptionView不为null,并且mDecorCaptionView的父控件也不为null,则将mDecorCaptionView添加到其父控件中,并且,将传入的布局文件的view添加到
mDecorCaptionView中,如果mDecorCaptionView为null,则直接将传入的布局文件的view,直接添加到DecorView中。说的这里,大家可能会奇怪,DecorView的addView方法是怎么添加view的,其实大家也不必奇怪,因为DecorView其实是继承FrameLayout的,它也是一个ViewGroup,所以,它的添加view的方法其实就是使用的ViewGroup的添加view的方法。下面是DecorView的类的继承关系:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//省略代码
}
完成了将传入的布局文件的view添加到DecorView中后,将这个布局文件的viwe赋值给了mContentRoot变量。回到PhoneWindow类的generateLayout方法中,看看注释7的位置,将布局文件的view添加到DecorView中以后,通过findViewById的方法将这个ViewGroup赋值给了contentParent,ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
最后,返回这个contentParent。到这里,PhoneWindow的installDecor()方法就分析完了,
下面接着看PhoneWindow的setContentView方法的注释2处,mLayoutInflater.inflate(layoutResID, mContentParent); 这里将我们在Activity中的布局文件的id加载到了mContentParent中。这样就完成了将我们自己在Activity的setContetView
方法中设置的布局文件添加到了mContentParent这个ViewGroup中。由于mContentParent是DecorView的子控件,所以,相当于是添加到DecorView中了。
前面分析PhoneWindow的generateLayout方法的注释7处,可能有些人会有疑问,怎么DecorView将一个布局文件作为自己的子view后,怎么就可以通过findViewById(ID_ANDROID_CONTENT)就能找到contentParent呢?这个findViewById方法中传入的id为什么就是ID_ANDROID_CONTENT这个常量呢?我们回到上面的PhoneWindow的ViewGroup generateLayout(DecorView decor) 这个方法内部看看,layoutResource这个变量被赋值的情况,可以看到这个layoutResource可能有如下几种赋值:
R.layout.screen_swipe_dismiss
R.layout.screen_title_icons;
R.layout.screen_progress;
R.layout.screen_custom_title
R.layout.screen_action_bar
R.layout.screen_title
R.layout.screen_simple_overlay_action_mode
R.layout.screen_simple;
这些布局文件位于 frameworks/base/core/res/res/layout/ (参照9.0源码查看)
下面介绍一下常见的系统提供的布局文件
R.layout.screen_title
<!--
This is an optimized layout for a screen, with the minimum set of features
enabled.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</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>
R.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:id/content 的FrameLayout,我们在PhoneWindow的generateLayout的注释7处的ID_ANDROID_CONTENT其实就是指的 @android:id/content这个id,所以,能够通过ID_ANDROID_CONTENT来查找其代表的控件FrameLayout。到这里,也可以明白,为什么这个ID_ANDROID_CONTENT的控件取名叫mContentParent了,因为它就是我们传入的布局的父控件,我们传入的
布局都是被添加到它里面的。
到这里,其实只是完成了Decorview的初始化,并将我们自己的布局添加到了Decorview的mContentParent中,但是DecorView还未显示,也不能接收外界的输入,在完成ActivityThread的handleResumeActivity方法的调用后,回调了Activity的onReusme方法后,在接着调用Activity的makeVisible()方法才将DecorView添加到WindowManager,并将DecorView显示。
通过上面整个流程的分析,我们可以总结一下几点:
1.要去除标题栏是,要在setContentView方法前调用requestWindowFeature(Window.FEATURE_NO_TITLE);这是因为,我们DecorView添加布局,是根据feautres来决定添加的布局文件的id的。只有先设置了feature,DecorView才知道去添加哪个布局作为自己的子view。
2.DecorView中,只有一个直接的子view,这个子view要么是DecorCaptionView要么是根据feature拿到的layoutResource,比如R.layout.screen_title 或 R.layout.screen_simple等,上面列出的几种布局中的一种。
系统的状态栏和底部导航栏是不包含在DecorView中,他们是SystemUI的一部分