转载请注明出处:http://blog.csdn.net/fishle123/article/details/50812266
在Activity中,常常第一件事就是在onCreate里面调用setContentView来设置布局。大家都知道setContentView用来设置Activity的布局,但是有没有研究过setContentView究竟做了哪些事呢?本文就一起看看setContentView是如何加载我们的布局的。看完之后大家就会知道为什么使用requestWindowFeature设置窗口风格的时候一定要在setContentView之前调用。
在Android里面,Activity的内容就是一个Window,Dialog和Toast也都是通过Window来展示的。在实际使用当中并不能直接访问Window,需要通过WindowManager才能访问到Window,WindowManager提供了addView、updateViewLayout和removeView三个方法来管理Window中的View。Window类是一个抽象类,它唯一的实现是PhoneWindow。在PhoneWindow里面有个属性mDecor,它的类型是DecorView,DecorView是PhoneWindow的内部类。DecorView是FrameLayout的子类,包含整个PhoneWindow所有要显示的View,包括系统的状态栏、标题栏、以及Activity的布局等,下面通过一张图来说明它们之间的关系,这张图展示了Activity的默认视图结构。
从这张图可以很清楚看到,Android中看到的视图都是通过Window来呈现,具体地是通过PhoneWindow来管理的。需要说明的是不同样式风格的Window内部视图结构可能会有所不同,比如有的Window没有TitleBar,但是mContentParent是一定要有的。DecorView对应着Widnow要呈现的内容。我们通过setContentView设置的其实是mParentConent的子View,mParentContent对应的id是com.android.internal.R.id.content,因此使用setContentView就非常贴切了。
在onCreate里面调用的是Activity的setContentView,setContentView有三个重载版本,它们的逻辑都是差不多的,这里就看一下setContentView(int layoutResID)的源码:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里可以看到,Activity其实是调用Window的setContentView来加载布局的。
上面有提到Window类是一个抽象类,唯一实现了Window的是PhoneWindow。下面就是看一下PhoneWindow的setContentView:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
首次调用setContentView的时候,mContentParent为null,需要调用installDecor来初始化mDecor。然后在第20行调用mLayoutInflater.inflate(layoutResID, mContentParent)将我们指定的布局添加到mDecor即添加到DecorView中。从这里我们可以看到mContentParent就是setContentView(layoutResID)中layoutResID的父视图,后面会看到mContentParent是DecorView的一部分。这里看一下installDecor的实现:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
//............................省略部分代码
}
installDecor的代码很长,这里只挑重要的看:首次调用installDecor的时候mDecor和mContentParent都为null,需要调用generateDecor和genreateLayout。我们先看generateDecor方法,从名字来看generateDecor里面似乎会构造一个DecorView。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
实际上,在generateDecor里面非常简单,直接new了一个DecorView。再看一下DecorView的构造函数。
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
}
DecorView的构造函数只作了一些简单的初始化,这个时候DecorView还是空的。所以这个时候mParentContent还是null,因此在installDecor方法中调用完generateDecor创建mDecor后,会继续调用genreateLayout来往DecorView中添加内容。genrateLayout会根据我们设置的窗口样式来初始化DecorView。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//先获取Window的风格样式
TypedArray a = getWindowStyle();
...................................
//下面的这段代码都是获取Window详细的样式设置,并赋值给相应的feature
//是否为悬浮框
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
//是否添加标题栏
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//沉浸式状态栏
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
//...........................省略其他样式的处理,逻辑是类似的
// 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();
//根据样式选择好布局后,将相应的布局inflate为View
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;//mContentRoot保存了整个窗口的内容
//找到DecorView中ID为com.android.internal.R.id.content的view并赋值给contentParent ,我们为Activity等设置的View都是contentParent 的子view,setContentView这个名字就显得很贴切了。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//...............................................省略部分代码
//在installDecor中可以看到,generateLayout的返回值有mContentParent来接收,
//mContentParent就是setContentView(layoutResID)中layoutResID的父容器
return contentParent;
}
上面这段代码先获取Window的样式,然后根据样式选择相应的布局并inflate转化成ViewGroup,并返回mContentParent对应的ViewGroup。Window的样式可以通过requestWindowFeature或者给Activity设置android:theme来指定。现在应该知道requestWindowFeature一定要在setContentView之前调用才能生效了吧。
完成上面的处理之后DecorView就创建好了,最后在PhoneWindow的setContentView中回调Activity的onContentChanged,通知Activity它指定的布局已经添加到mContentParent(DecorView)。回调过程如下:
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
到此为止,DecorView已经创建并完成初始化,Activity的布局也添加到DecorView中,但是这个时候我们还看不到DecorView,因为它还没有通过WindowManager添加到Window中。DecorView真正显示是在ActivityThread的handleResumeActivity方法中,handleResumeActivity先调用Activity的onResume,然后调用Activity的makeVisible把DecorView添加到Window中。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
最后总结一下setContentView将布局文件添加并最终显示的过程:
1)在PhoneWindow的setContentView中先调用installDecor初始化DecorView。具体的,在installDecor里面先调用generateDecor创建一个DecorView,然后调用generateLayout根据Window的样式风格选择布局文件并初始化DecorView;
2)通过mLayoutInflater将Activity的布局添加到mContentParent中;
3)回调Activity的onContentChanged,通知Activity布局文件已经成功添加到DecorView中;
4)最后在ActivityThread的handleResumeActivity中先调用Activity的onResume,然后调用Activity的makeVisible把DecorView添加到Window中,并显示到手机上。
前面三步都是在Activity的setContentView返回之前完成。