请尊重原创,转载请注明出处【tianyl_Melody】的博客
1、说明
在Android中,我们的布局通常是通过先写在xml文件中,然后通过setContentView这个方法,就能将布局加载到对应的Activity中的,那么这个xml的布局文件到底在加载到Activity中,做了哪些事情呢,今天,就从setContentView这个方法出发,看看xml文件是如何加载到屏幕上的。
2、setContentView
首先,从activity的setContentView出发
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
通过这两个方法,我们可以看到它首先是拿到了一个mWindow对象,然后调用了这个mWindow对象的setContentView方法。而Window是一个抽象类,它仅有一个实现类,那就是PhoneWindow。
3、PhoneWindow
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
可见,在PhoneWindow中,首先会判断mContentParent是否为null,如果为null,那么会调用installDecor,可以猜测这是一个创建mContentParent的方法,如果不为null,那么就移除掉所有的子view进行复用。
4、installDecor
在installDecor方法中,首先会判断mDecor是否为null,如果mDecor对象也为null,那么就会调用generateDecor方法。
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
在generateDecor方法中,会创建一个PhoneWindow的内部类DecorView对象;
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
在创建完DecorView之后,会对DecorView的属性做一些设置;然后再判断mContentParent是否为null,如果mContentParent为null,那么把刚刚创建的mDecor作为参数传入,调用generateLayout方法;
mContentParent = generateLayout(mDecor)
5、generateLayout
generateLayout这个方法主要就是对window的一些属性进行设定。首先,它会拿到我们在xml中设置的window属性;
TypedArray a = getWindowStyle();
public final TypedArray getWindowStyle() {
synchronized (this) {
if (mWindowStyle == null) {
mWindowStyle = mContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
}
return mWindowStyle;
}
}
然后再对这些属性进行实现,比如我们经常使用的Window_windowNoTitle:
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
它会通过requestFeature(int featureId)这个方法来将我们设置的window属性保存起来,将所有属性保存完之后,就会通过这个方法进行取出:
int features = getLocalFeatures();
然后加载对应的xml布局文件,比如:
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
做完这些事情之后,就开始给DecorView添加布局了:
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
首先,mDecor.startChanging():
public void startChanging() {
mChanging = true;
}
然后,会将我们之前拿到的xml文件填充出来,并且通过addView添加到decor中:
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
其中,LayoutParams我们可以看到都是MATCH_PARENT。然后填充出来是view会当成mContentRoot,而contentParent就是我们之前的xml文件中,id为content的布局。
最后,会将我们构建的contentParent返回出去:
return contentParent;
这些就是我们installDecor做的主要过程了,接下来就是这段代码:
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
首先会判断FEATURE_CONTENT_TRANSITIONS这个值,FEATURE_CONTENT_TRANSITIONS是Android中的过渡,简单理解就是界面跳转的一些动画,如果没有,那么就调用inflate方法,并且把外界传入的layoutResID和自己构建出来的mContentParent作为对象传入。
6、inflate
在上面的installDecor中,最后调用到了inflate方法,而inflate最终会调用到这个方法:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
这里,会将Resources传入,拿到一个对应的xml的解析器parser,然后再调用inflate方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
//首先会拿到xml布局中的属性
final AttributeSet attrs = Xml.asAttributeSet(parser);
......
//然后不断的循环,找到xml布局中的根节点
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//然后拿到这个跟节点的名字
final String name = parser.getName();
......
//如果这个跟节点不是merge,那么就会生成对应的根节点
final View temp = createViewFromTag(root, name, attrs, false);
......
//然后通过root的generateLayoutParams方法,拿到对应的属性,设置到刚生成的根节点布局中
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
其中root就是在之前的方法installDecor中创建并且返回出来的contentParent对象,而根节点就是我们自己写的布局文件的最外层布局,比如LinearLayout。然后通过contentParent来解析出LinearLayout的对应属性,通过setLayoutParams给LinearLayout设置上。
7、总结
1、在setContentView中,首先,会判断mDecor是否为null,如果为null,那么就会new一个DecorView给mDecor赋值;
2、然后在getWindowStyle方法中拿到xml中设置的window属性通过requestFeature方法保存到对象中。
3、接下来通过getLocalFeatures取出window的属性对象,并且根据对象的值,加载对应的布局文件。
4、通过mLayoutInflater.inflate填充出window对应的布局文件,并且通过addview添加到mDecor中,作为mDecor的child对象。而填充出来的布局文件就是contentParent,返回出来给外面使用。
5、在inflate中,首先会拿到从setContentView中传入的布局id,并且通过这个id,得到对应的xml解析器。
6、在xml解析器中,拿到跟布局,然后创建出跟布局的根节点,通过contentParent的generateLayoutParams方法,将从xml解析器中拿到的属性对象attrs传入,拿到对应的属性参数,最后将这个属性参数设置给布局的根节点temp。