上一篇说了Activity和Window关联源码分析,现顺着源码查看下Activity的setContentView的具体原理,了解为什么我们在Activity的布局的添加流程。
Activity的setContentView()方法,源码如下:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
本质是调用的Activity内部的window的setContentView()方法,上面文章了解到,Activity内部的mWindow对象,实际是一个PhoneWindow实例,所以activity的setContentView方法,就是调用了PhoneWindow内部的setContentView(),PhoneWindow内部方法如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
......
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}
......
}
方法中的,mContentParent,就是我们日常知道的android:id/content的布局,而我们日常的setContentView的这个view或者是layout,父布局就是此处的mContentParent。顺着源码看,首先判断mContentParent是否为空,初次调用,此处为空,进入installDecor()方法。
installDecor()方法,首先判断了mDecor,这个mDecor是PhoneWindow的一个内部类DecorView的实例,是一个FrameLayout,acitivty对应的布局界面的最顶层的视图。首先判空处理,为空则调用generateDecor()方法生成一个对象。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
接下来是判断mContentParent,若为null,则调用generateLayout()方法生成mContentParent,方法源码如下
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
......
mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
......
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {//布局设置左右图标
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 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 = com.android.internal.R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {//自定义title
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_custom_title;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
} 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) {
layoutResource = com.android.internal.R.layout.dialog_title;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else {//notitle的情形
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
return contentParent;
}
根据设置的window属性(feature和windowStyle),做不同处理。默认情况下,features为DEFAULT_FEATURES,它的详细定义在Window内部,可自行查看。根据不同的features,添加不同的布局,默认布局为R.layout.screen_title,调用PhoneWindow的成员变量mLayoutInflater,加载布局文件并添加到根视图mDecor中;而mContentParent,则是mDecor内部的一个id为ID_ANDROID_CONTENT的viewGroup。
回到installDecor()方法中,获取mContentParent后,设置title相关内容。如果设置的features不是FEATURE_NO_TITLE,则表示有title,便会去设置title。同时根据上述代码,可以知道,为什么activity内部调用requestWindowFeature设置窗口属性,需要放在setContentView前面才可以,它本质是就是通过PhoneWindow的requestFeature()方法去实现的。
回到PhoneWindow的setContentView方法,调用了installDecor()方法,可以获取mContentParent,然后将布局或视图加载到当前的mContentParent中(通过LayoutInflater.inflate或者是addView去实现),最终,布局设置完成,回调当前Window.Callback对象的onContentChanged(),返回给Activity,剩下的由用户自行处理。
......
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
......
所以,我们现在知道了,acitivity内部维持了一个Window对象,实际是PhoneWindow实例,而activity的布局视图,就是放在PhoneWindow内部的一个mDecor布局中,而 我们的布局,其实是放在了mDecor内部的一个content的FrameLayout里面。