setContentView主要工作以及VIew整体布局层次
View测量布局绘制流程都是从根View(DecorView)开始的,DecorView究竟在那里创建?在Activity.onCreate生命周期中setContentView()主要做了哪些工作;以及Activity中View的整体布局层次;带着这些疑问,我们来一步步寻找问题的答案;
在Activity.onCreate中会调用setContentView(R.layout.xxx),Activity的视图由setContentView提供,R.layout.xxx是布局文件的资源id;
public void setContentView(@LayoutRes int layoutResID) {
/* Activity的视图由setContentView提供,layoutResID是布局文件资源id;
Window是一个抽象类,具体实现位于Phonewindow中
*/
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow.setContentView代码如下:
//在onResume的makeVisible方法中,DecorView会真正完成添加和显示着两个过程,那时Activity的视图才能被看到
@Override
public void setContentView(int layoutResID) {
// 1.Acitivity刚启动时,mContentParent为null,会执行installDecor();
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 {
// 2.将Activity中的布局文件添加到DecorView的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
在PhoneWindow.setContentView中有两个关键的方法:
installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent);
下面来分别看下这两个方法主要做了哪些工作:
PhoneWindow.installDecor();
private void installDecor() {
mForceDecorInstall = false;
//第一次执行installDecor时,mDecor和mContentParent都为null;
if (mDecor == null) {
/*mDecor是DecorView实例,DecorView的根View.
generateDecor会创建DecorView*/
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//generateLayout主要工作:找到系统默认布局,并将默认布局文件添加到DecorView中
mContentParent = generateLayout(mDecor);
}
}
PhoneWindow.generateDecor(-1)如下,可以看出DecorView的是在PhoneWindow.installDecor()中创建的。
protected DecorView generateDecor(int featureId) {
//主要工作是创建并返回DecorView实例;
return new DecorView(context, featureId, this, getAttributes());
}
PhoneWindow.generateLayout(mDecor);
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
/*layoutResource是系统默认的布局文件的资源id;
以下代码根据设置的window属性(notitle、noactionbar等)查找对应系统根布局文件;
系统布局文件位于/framworks/base/core/res/layout/screen_title、screen_simple(默认布局文件);
这些系统布局文件中都有ID为Content的控件,并且id为content控件基本都是一个FrameLayout;
*/
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
//根据window属性选择对应的默认布局文件;
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;
}
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_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 {
//根据window属性选择对应的默认布局文件;
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 {
layoutResource = R.layout.screen_simple;
}
//通过以上代码可知:设置window的flag在setContentView之前才能起作用;
/*DecorView.onResourcesLoaded方法:通过layoutInfalter将系统默认布局加载出来,
然后调用decorview的addView方法,将系统默认布局文件加载到DecorView中,这样系统默认布局文件
就加载到了DecorView中。
*/
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
/*通过findViewById(ID_ANDROID_CONTENT);查找ID为com.android.internal.R.id.content;布局文件。
这就是应用布局的父view。Phonewindow.findViewById()实际是获取Decor.findViewById()方法;
因为Decorview已经把系统默认布局文件添加早自己的view中,默认布局中有ID为content布局,找到ID为content
FrameLayout布局之后,将该framelayout赋值给mContentParent;
setContentView设置布局最后是加载到mContentParent中。
*/
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent;
}
相关说明:
(1)首先根据设置的window属性(notitle、noactionbar等)查找对应系统根布局文件;系统布局文件位于/framworks/base/core/res/layout/screen_title、screen_simple(默认布局文件);这些系统布局文件中都有ID为Content的控件,并且id为content控件基本都是一个FrameLayout;
(2)找到系统默认布局文件的资源id之后,调用DecorView.onResourcesLoaded(layoutinflater,系统默认布局文件资源id)
(3)DecorView.onResourcesLoaded方法:步骤一:通过layoutInfalter将系统默认布局加载出来,然后调用decorview的addView方法,将系统默认布局文件加载到DecorView中,这样系统默认布局文件就加载到了DecorView中;
(4)通过findViewById(ID_ANDROID_CONTENT);查找ID为com.android.internal.R.id.content;布局文件。这就是应用布局的父view。Phonewindow.findViewById()实际是获取Decor.findViewById()方法;因为Decorview已经把系统默认布局文件添加早自己的view中,默认布局中有ID为content布局,找到ID为content FrameLayout布局之后,将该framelayout赋值给mContentParent;setContentView设置布局最后是加载到mContentParent中。
(5)installDecor执行之后,回到setContentView中,继续执行mLayoutInflater.inflate(layoutResID,mContentParent);layoutResID:我们应用中setContentView的布局资源id; mContentParent:系统默认布局文件中id为content的Framelayout中;这样setContentView工作就完成了。
(6)以上解释了为什么设置window的属性必须在setContentView之前才能起作用;
(7)View整体层次为:DecorView>系统默认根布局文件>应用setContentView的布局添加到系统默认布局文件中Id为android.R.id.content的布局中。
(8)以上已经完成将布局文件添加到系统根布局文件中,Android中所有视图都是通过Window来呈现的,此时DecorView还没有被WM添加到Window中。Window addView之后,会调用ViewRootImpl来完成界面的绘制工作;因为View还没有开始绘制,这时候是不会显示出来的。View测量布局绘制是从ViewRootImpl的performTraversals方法中开始的,
(9)通过以上分析可以得出View的整体层次结构如下图所示(转自工匠若水)