一直想写关于android解析xml文件构建View视图的学习笔记,到今天 才算静下心来大致过了一遍它的源码,特此简单的分析一下它的大致脉络。具体怎么解析xml的是由XmlPullParser这个类来完成的,至于解析的细节就不作说明。算是个简单的学习笔记。
在android里面是由LayoutInflater这个类来完成xml构建View的工作,主要工作交给该类的如下几个重载方法来完成:
前面上那个inflate方法最终会调用租后一个inflate方法来完成解析的工作,让我们看看这个方法都做了些什么:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//获取AttributeSet对象
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
//保存mContext对象,在最终用反射生成View对象的时候会用到该参数
mConstructorArgs[0] = mContext;
//最终解析获取产生的返回的View对象
View result = root;
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//节点名,即API中的控件或者自定义View完整限定名
final String name = parser.getName();
//处理 标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
//Temp是xml文件里面的根View
View temp;
if (TAG_1995.equals(name)) {//处理标签
temp = new BlinkLayout(mContext, attrs);
} else {//
//创建该xml布局文件所对应的根View。
temp = createViewFromTag(root, name, attrs);
}
//root 不为null的情况下就创建LayoutParams参数
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
//如果xml的根View不跟root绑定
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//设置xml根View的params
temp.setLayoutParams(params);
}
}
// Inflate all children under temp
//即系xml文件根View里面的 的所有子节点,也就是添加temp View的所有的子View
rInflate(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//如果root!=null并且绑定,就返回root
if (root != null && attachToRoot) {
//将params设置给temp,
//并将temp添加到root里面
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
//如果root为null或者不绑定root就返回temp
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
1)如果root为null的情况下,xml文件对应的根View(也就是上面对应的temp)的LayoutParams是为null的,其实这正是我们在ListView直接调用:
convertView = App.getLayoutInflater().inflate(R.layout.item, null);
使得item.xml的根View的宽和高设置无效的原因,解决方法很简,详细解说 点击此处。
2)LayoutParam是由AttributeSet属性传递个generateLayoutParams方法来构建具体的LayoutParams对象,该AttributeSet对象在inflate的第一行产生。
在ViewGroup 类里面generateLayoutParams方法如下:
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
//设置基本的layout_width和layout_height属性
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
//可以看出,如果不设置layout_width或者layout_height的话,会抛出异常。
public int getLayoutDimension(int index, String name) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
}
throw new RuntimeException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
也就是说在创建xml跟对象的时候会设置其LayoutParams对象并初始化其width和height属性,这两个属性在对View进行Measure的时候用到,这点需要注意,在ViewGroup中提供Measurechild方法:在该方法中会调用View的getLayoutParams方法来获取设置的LayoutParams对象,并取的其lp.width和lp.height来进行child的Measure操作。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//需要获取lp.width
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//需要获取lp.height
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其实通过上面的代码还可以得出一个关于View Measure的结论:childView的measureSpec是是由parentView的measureSpec和childView自身的LayoutParams来共同决定的
3)如果root不为null,那么就调用root.root.generateLayoutParams(attrs);方法产生一个LayoutParams对象,来设置xml根View的layoutParams.具体有两种方法
3.1)attachToRoot为false的情况下直接temp.setLayoutParams(params)
3.2)attachToRoot为true的情况下调用root.addView(temp.params)来讲params对象设置给temp.而ViewGroup的addView方法最终会调用如下方法:
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
//将params设置给child也就是上面所说的temp
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
}
4)通过createViewFromTag来创建xml布局文件对应的根View,下面看看这个方法都做了些什么:
//此时第一次调用该方法,parent为上面所说的temp也就是xml的根View
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
View view;
//此处省略的工厂方法创建创建view的实例的代码,因为在平时的使用中并没有用到这些工厂,所以忽略此代码
if (view == null) {
//android 自带view的实例
if (-1 == name.indexOf('.')) {
//parent参数才此处没有用到
view = onCreateView(parent, name, attrs);
} else {//创建自定义view的实例
view = createView(name, null, attrs);//注意第二个参数为null
}
}
return view;
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor extends View> constructor = sConstructorMap.get(name);
Class extends View> clazz = null;
if (constructor == null) {//缓存中没有name对应View的constructor对象
// Class not found in the cache, see if it's real, and try to add it
//获取name标签所代表view类的class对象
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
。。。。。
constructor = clazz.getConstructor(mConstructorSignature);
//放入缓存中去
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
}
}
//传递参数,生成View实例对象
Object[] args = mConstructorArgs;
args[1] = attrs;
return constructor.newInstance(args);
}
1)该方法根据第二个参数prefix来判断是否是自定义组件,如果null的话说明是程序员自定义的view组件,否则就是android api里面的view组件
2)为了提高解析xml创建View的效率,在LayoutInflate里面对tag及该tag对应View的Constructor做了缓存处理。这样在同一个xml文件里面有相同的Tag的时候就不用再次用ClassLoader加载clss文件了。
3)最后 三行代码完成了构建View的工作,其中args[0]对应的是mContext对象,而args[1]就AttributeSet这个对象了。最总通过构造器Constructor.newInstance来创建完成View的创建。这也是为什么View或者ViewGroup没有提供默认构造器的原因:防止自定义View的时候不重写View或者ViewGroup的构造器,这样解析的时候会出错。
到此位置xml的根标签解析成View对象的工作已经完成,那么xml文件中根View包含的子View又是怎么解析的呢?其实想想也应该知道,肯定也是根据子View的tag调用createViewFromTag来创建具体的子View,因为是一个View Tree也就是简单的递归调用而已。事实上查看inflate方法可知道在对xml子view的解析是在xml根View创建完成后调用rInlfate方法来完成的
//即系xml文件根View里面的 的所有子节点,也就是添加temp View的所有的子View
rInflate(parser, temp, attrs, true);
最后就让我们看看rInflate方法都做了些什么吧:
/
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//获取节点名
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {//解析 标签
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {//解析 标签
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
//解析 标签
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {//处理 标签
throw new InflateException(" must be the root element");
} else if (TAG_1995.equals(name)) {//处理标签
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else {
//根据当前节点名构建一个View实例对象
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
//创建一个layoutPrarams对象
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//继续递归调用 解析当前view 的子view
rInflate(parser, view, attrs, true);
//把生成的view添加到parent view中
//需要注意的是addView每次都调用了该子view的parentView所产生的Layoutparams对象
viewGroup.addView(view, params);
}
}//end while
if (finishInflate) parent.onFinishInflate();
}
由此,android通过LayoutInflater解析xml创建View对象的简单脉络分析完毕,很清晰,通过追踪它的源码很容易就把此脉络理清;至于更深层次的解析原理,水平有限就不做分析了,如有不当之处欢迎批评指正