一个Activity是如何显示界面的呢?要从Activity中的setContentView开始分析(用的源码是Android9.0源码):
点进Activity的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到,Activity中的setContentView方法内部实际上调用的是PhoneWindow类(getWindow方法获取到的是PhoneWindow实例)的setContentView方法。
在PhoneWindow类中找到setContentView方法
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在PhoneWindow中的setContentView方法中首先判断mContentParent是否为空(第一次执行肯定是空的),为空就调用installDecor方法。这里就产生了一个问题:mContentParent是什么?installDecor中Decor又是什么?提前剧透一下,mContentParent就是自己调用setContentView方法中传进来的那个的布局(是一个ViewGroup);而installDecor中的Decor其实是一个DecorView,它继承自FrameLayout(也是一个ViewGroup),是Activity的根布局,也就是说,mContentParent其实是嵌套在DecorView中的一个子View。
这时候可以猜到,installDecor方法中生成了DecorView和mContentParent,是不是这样呢?
点进installDecor方法(这里只截取了有用的部分):
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
//.....
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
首先如果mDecor为空的话(mDecor就是DecorView)就通过generateDecor方法生成DecorView,接着如果mContentParent为空就通过generateLayout方法生成mContentParent。generateDecor方法没啥好说的,就是内部new了一个DecorView;那么就点进generateLayout方法看看(也是截取了有用的部分):
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
int layoutResource;
int features = getLocalFeatures();
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) {
//加载资源
layoutResource = R.layout.screen_custom_title;
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
layoutResource = res.resourceId;
layoutResource = R.layout.screen_title;
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//......
mDecor.finishChanging();
return contentParent;
}
generateLayout()方法会首先根据Theme和Style,得到一个系统的布局layoutResource,
layoutResource的可能取值有:
com.android.internal.R.layout.screen_swipe_dismiss.xml
com.android.internal.R.layout.screen_title_icons
com.android.internal.R.layout.screen_progress
com.android.internal.R.layout.screen_custom_title
com.android.internal.R.layout.screen_action_bar
com.android.internal.R.layout.screen_title
com.android.internal.R.layout.screen_simple_overlay_action_mode
com.android.internal.R.layout.screen_simple等。
在得到layoutResource之后,调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法将这个布局加载到mDecor中,之后通过findViewById方法获取到contentParent对象(其实就是mContentParent)。
点进findViewById方法:
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
发现是调用的DecorView的findViewById方法那么findViewById方法中的参数ID_ANDROID_CONTENT是什么呢?点进去看看:
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
总的来说就是DecorView通过findViewById方法找到了com.android.internal.R.id.content的contentParent对象,这也进一步证明了mContentParent是嵌套在mDecor中的。
再回到generateLayout方法中,最后一行将contentParent对象返回,这样就将install方法分析完了。那么让我们再回到PhoneWindow的setContentView方法中来:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
可以看到在installDecor方法的下面有一句mLayoutInflater.inflate(layoutResID, mContentParent),其中的layoutResID参数 就是 调用我们在Activity中调用 setContentView 方法时传入的(也就是我们自己编写的布局),这里将这个资源加载到了 mContentParent 上面。
好了,这样基本就将setContentView分析完了,用一张图总结一下:
图中水平箭头表示的是右面框里的内容是在左面框方法的内部执行的,竖直箭头表示下面的框是在上面框中方法执行结束后执行的。
下面这张图展示了布局加载的流程(用的别人的图):