从activity的setContentView()方法从源码的角度来解析Android绘制UI的流程。
先来看看View是如果添加到屏幕窗口上的。
首先打开activity的setContentView()的方法,源码如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
由此可见activity的setContentView()方法中是调用的getWindow()方法的setContentView。getWindow()方法中返回的是Window,Window类是一个抽象类,它只有唯一的一个实现类为PhoneWindow。所有我们可以去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)这两个方法。首先我们先看看installDecor()方法的实现:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1); //给mDecor赋值
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);
......
}
}
首先在instanllDecor()方法中首先对mDecor判断是否为空,mDecor即为DecorView,DecorView是一个继承自FrameLayout 的布局。如果mDecor为空, 就会调用 generateDecor(),在这个方法中创建了DecorView对象。
然后又对mContentParent判断是否为空,如果为空则会调用generateLayout(mDecor)方法,在此方法中给mContentParent赋值,我们再来看看generateLayout(mDecor)代码的具体实现:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
.......
//根据主题样式调用requestFeature()和setFlags()等方法。
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//这里根据设置的features不同来给layoutResource设置不同的值。
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
...... 此处省几百行代码
} 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();
//将layoutResource解析成view,并将view添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//这里是获取到主容器,ID_ANDROID_CONTENT为主容器的资源id,一定存在,有系统定义 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
return contentParent;
}
此方法中重点看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);调用了DecorView的onResourcesLoaded()方法,下面查看下onResourcesLoaded()方法的实现:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
......
//将layoutResource进行解析,创建root对象
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. 将root这个View对象添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
有以上代码可见DecorView的onResourcesLoaded()方法主要做了两件事:解析layoutResource为root对象,并将root添加到DecorView中。
总结一下PhoneWindow中的installDecor()方法:通过generateDecor()方法创建了一个DecorView根布局。 然后调用generateLayout()方法根据不同的主题渲染不同的布局并添加到DecorView中。
phoneWindow在setContentView中调用了installDecor()方法后,然后又调用mLayoutInflater.inflate(layoutResID, mContentParent);这里layoutResID是我们activity中设置的布局id,mContentParent是id为Android系统定义的@android:id/content 布局,如下图所示。最终我们再activity中定义的布局就是添加到mContentParent中。
总结:View是如何添加到屏幕上的?
1.系统会创建顶层布局容器DecorView,DecorView继承自FrameLayout,是PhoneWindow对象持有的一个实例,它是所有应用的顶层View,在系统内部进行初始化;
2.DecorView初始化完成之后,系统会根据应用程序的主题样式去加载一个基础容器,如NoActionBar、DarkActionBar这些主题对应不同的基础容器,但是每个容器都有android系统内部定义的id为android.R.content 的FrameLayout的容器。
3.开发者通过setContentView方法设置的layout布局就是添加到这个FarmeLayout容器中。