Android Activity的UI绘制流程之setContentView方法详解
概述
对于Android开发人员来说,想必对setContentView方法不会陌生,每当我们创建一个Activity时,都会重写该Activity的onCreate方法,在该方法中我们必须要调用setContentView方法来显示我们指定的布局或者View。那么setContentView方法又是如何将我们指定的布局或者View放入到指定的Activity中显示出来的呢?
今天这篇博客主要就是讲解Activity的UI绘制流程,而setContentView就是Activity的UI绘制起始过程。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
这里先贴一张图,便于大家更好的理解这个流程
PhoneWindow对象
当我们创建一个Activity时,该类都是默认继承自Activity方法,所以首先我们需要进入到Activity方法中,然后找到该类的setContentView方法,该方法还有几个重载的方法,这里我们主要讨论的是定义资源id参数layoutResID的方法,代码如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
通过这个方法我们可以看出,在该方法中调用了 getWindow().setContentView(layoutResID),那么这个getWindow又是什么呢?
mWindow = new PhoneWindow(this, window);
public Window getWindow() {
return mWindow;
}
setContentView方法
通过源码源码可知,getWindow方法返回的时一个Window类对象,而Window是系统定义一个抽象类,在Activity中我们实例化的是Window的一个实现类PhoneWindow。所以我们需要进入到PhoneWindow类中,查看setContentView方法具体实现。
但是通过Eclipse或者Android studio是不能直接查看到PhoneWindow的源代码的,要想查看该类的源码需要去sdk的源码中查找,具体路径为: /sdk/sources/android-21/com/android/internal/policy/impl/PhoneWindow.java。
进入该类之后找到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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
installDecor方法
通过源码可知,当第一次初始化的时候会执行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);
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
在installDecor方法中首先会判断mDecor对象是否为空,如果为空则会调用generateDecor方法,generateDecor方法很简单就是生成一个DecorView对象。
在mDecor对象在执行完成之后又会判断mContentParent对象是否为空,如果为空则会通过generateLayout方法生成mContentParent,大家注意这个时候mDecor是作为参数传入到generateLayout方法中,不用质疑在该方法中会对mDecor做一些操作,那么或许有人会疑问这个DecorView又是什么呢?
DecorView
DecroView,其实就是PhoneWindow对象的一个内部类,该类继承自FrameLayout,对于DecorView,这里先不做过多的详细介绍,大家只需要知道DecroView其实就是作为Activity的顶级布局显示出来的就可以了。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
....
}
generateLayout方法
generateLayout方法如下:
protected ViewGroup generateLayout(DecorView decor) {
//1,获取<Application android:theme=""/>, <Activity/>节点指定的themes或者代码requestWindowFeature()中指定的Features, 并设置
TypedArray a = getWindowStyle();
//...
//2,获取窗口Features, 设置相应的修饰布局文件,这些xml文件位于frameworks/base/core/res/res/layout下
int layoutResource;
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = com.android.internal.R.layout.screen_progress;
//...
mDecor.startChanging();
//3, 将上面选定的布局文件inflate为View树,添加到decorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//将窗口修饰布局文件中id="@android:id/content"的View赋值给mContentParent, 后续自定义的view/layout都将是其子View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//...
}
在该方法中,首先会通过getWindowStyle方法获取window的样式闲逛属性并对Window进行一系列的初始化,这里大家可以看看代码,方法开始时这里通过一系列的判断调用requestFeature方法,对于requestFeature方法想必大家也不陌生,在开发中我们会经常在activity中调用该方法来设置FEATURE_NO_TITLE等属性。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
而且该方法的调用必须是在setContentView方法之前,或许在一开始大家并不明白为什么要在setContentView方法之前调用requestFeature方法,但是现在应该就明白了。因为在setContentView方法中会间接的初始化Window的属性,这里会调用requestFeature等方法,如果在开发中我们在setContentView方法之后调用requestFeature方法改变一些属性值,那么此时window的初始化已经完成,在调用requestFeature方法就没有作用了。
接下来,在generateLayout方法中,mLayoutInflater会根据layoutResource创建一个View对象,这个View对象in会被放入到decor中,也就是之前创建的DecroView中。
而layoutResource的获取则和之前requestFeature方法初始化有关,这里我们分析下默认情况下layoutResource的值,也就是 layoutResource = R.layout.screen_simple;,这里我们可以看看screen_simple的布局,该布局的文件位置在sdk/platforms/android-21/data/res/layout/screen_simple.xml。当然,如果大家有兴趣也可以看看其他情况下如actionbar下的布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
在该布局中的FrameLayout的id为content,想必大家就不陌生了,我们通过setContentView方法指定的布局最终就是给这个FrameLayout添加一个子布局。
最后,对DecorView添加完view对象之后,会通过该一下代码获取一个ViewGroup对象,这个对象就是generateLayout方法按最终返回的ViewGroup对象。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
最终我们在回到setContentView方法中,在该方法中我们会填充指定给activity的布局,并且将mContentParent作为view的跟布局,而这个mContentParent就是通过generateLayout方法创建的。
@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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
总结
对于setContentView方法的分析就到此为止,这里我们总结一下:
- 当我们创建一个Activity时,会有一个PhoneWindow的对象被创建,PhoneWindow是抽象类Window的具体实现
- 在PhoneWindow中有一个内部类DecorView,DecroView是继承自FrameLayout,该类是所有应用窗口的根View
- 在DecorView中会添加一个具体的View,该View会根据不同的theme和feature而不同,但是有一个共同点就是在该View中会有一个id为”@android:id/content”的FrameLayout存在,该类将作为activity中显示指定布局的父布局存在,也就是activity显示的布局江北添加到这个FrameLayout中
最后贴上一张图,通过hierarchyviewer工具获取的activity的view的结构,也可以一一印证上面的结论。
还没有评论,来说两句吧...