前言
AttributeSet与XmlPullParser这2个接口在解析xml文件时经常配合使用,二者可进行相互转换,理清二者的关系对于深入理解LayoutIflater.inflate()源码时,有着重要作用。
故本文的目的在于理清二者之间的关系,简述XmlPullParser的基本用法,为后续理解LayoutIflater.inflate方法源码打下基础。
类图结构
AttributeSet与XmlPullParser的类图关系如下图所示:
AttributeSet接口抽象了访问xml节点属性的方法,XmlPullPaser接口抽象了解析xml文件的方法,这个2个接口具有部分相同的方法声明,如下图所示。这些方法是XmlPull解析常用来访问xml节点属性的方法。
int getAttributeCount();
String getAttributeName(int index);
String getAttributeValue(int index);
String getAttributeValue(String namespace, String name);
String getPositionDescription();
AttributeSet与XmlPullParser的关系
在Android中AttributeSet接口的实现类有XmlPullAttribute和Parser(XmlResourceParser接口的实现类,该接口继承了XmlPullParser和AttributeSet接口)。
我们先来看看XmlPullAttribute的实现,部分代码如下:
class XmlPullAttributes implements AttributeSet {
public XmlPullAttributes(XmlPullParser parser) {
mParser = parser;
}
public int getAttributeCount() {
return mParser.getAttributeCount();
}
public String getAttributeName(int index) {
return mParser.getAttributeName(index);
}
public String getAttributeValue(int index) {
return mParser.getAttributeValue(index);
}
public String getAttributeValue(String namespace, String name) {
return mParser.getAttributeValue(namespace, name);
}
public String getPositionDescription() {
return mParser.getPositionDescription();
}
public int getAttributeNameResource(int index) {
return 0;
}
public int getAttributeListValue(String namespace, String attribute,
String[] options, int defaultValue) {
return XmlUtils.convertValueToList(
getAttributeValue(namespace, attribute), options, defaultValue);
}
............
//AttributeSet接口的剩余方法实现省略,其实现与getAttributeListValue()方法
//相似,都依赖XmlUtils与getAttributeValue()。
}
可见该类关联了一个XmlPullParser类型的成员变量(实际上是KXmlParser),AttributeSet接口与XmlPullParser接口相同声明函数的实现都是调用的mParser的同名函数;而AttributeSet接口的其他方法皆依赖于getAttributeValue方法来实现,即也依赖于mParser。
从代码实现来看,很自然的会联想到装饰者设计模式,虽然从类图结构上来看,并非属于严格意义上的装饰者模式,但我想可以将AttributeSet理解为XmlPullParser的装饰者,只不过这个装饰者没有全部装饰而已,只是装饰了XmlParser用于访问xml节点属性的那一部分。
自创部分装饰设计,强行解说2333....
综上所述:AttributeSet是对XmlPullParser的部分装饰,装饰了用于访问xml节点属性的部分。或者理解为AttributeSet是对XmlPullParser中用于访问xml节点属性方法的封装。
至于为什么要这么做呢?拙见如下:
- AttributeSet这个接口更适合Android的设计,其语意性更强;如果将View的构造函数中的AttributeSet换为XmlPullParser,看起来是不是有一种别扭的感觉的,而AttributeSet(属性集合)这个命名更易让人理解接受,装饰的作用之一就是可以“改头换面”嘛。
- 单一职责原则,XmlPullParser接口的职责不单单是用于访问xml节点的属性,从中分离出一个用于访问节点属性的接口岂不美哉?
- 对XmlPullParser中的Xml节点属性访问方法进行扩展,方便使用,如getAttributeIntValue方法可直接获取int类型的属性值,若使用XmlPullParser则只能获取String类型的数据,还需要我们自己转换一次。
下面来看看XmlResourceParser接口。
该接口同时继承了XmlPullParser接口以及AttributeSet接口,作用有二。
- 对XmlPullParser起到部分装饰的作用,实现XmlResourceParser接口,即可达到目的。
- 继承XmlPullParser接口,实现一个专用于Android Xml资源文件的解析器,如解析layout、drawable等目录下的xml文件。
Android中的常见打开方式如下:
Resources resources = getResources();
XmlResourceParser parser0 = resources.getXml(R.xml.test);
XmlResourceParser parser1 = resources.getLayout(R.layout.activity_main);
XmlResourceParser parser2 = resources.getAnimation(R.anim.test);
Drawable目录下的xml解析并未提供我们可直接获取XmlResourceParser的API,其解析封装在ResourseImpl类(@hide)的方法中。
/**
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,Resources.Theme theme) {
............
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
............
}
XmlResourceParser的实现类为Parser类,该类属于BlockXml类的一个内部类,用于解析Android中的layout布局文件。
这里说一个小技巧,方便想要自行查看源码的朋友:
在Android Studio中可通过双击Shift键来快速查找类文件,可以查到一些隐藏类文件的源码。
总结:
1、AttributeSet接口是对XmlPullParser接口的部分装饰,装饰了XmlPullParser中xml节点属性的访问方法。AttributeSet接口语意性更强,更适合Android架构的整体设计。
2、其实现类无论是Parser还是XmlPullAttribute,它只是起到一个装饰作用,最终对xml节点属性的访问都是通过里面的XmlPullParser的节点属性访问方法来完成的。
至此,AttributeSet与XmlPullParser的关系已经分析清楚,后文只是方便自己后续查看,主要记录XmlPullParser的基本使用。
转换分析
XmlPullParser转换为AttributeSet的方法为:
Xml.asAttributeSet(parser0);
进去看看源码:
//如果传入的XmlPullParser类型为XmlResourceParser(Parser),返回的就是XmlResourceParser(Parser)
//反之则new 一个XmlPullAttributeSet
public static AttributeSet asAttributeSet(XmlPullParser parser) {
return (parser instanceof AttributeSet)
? (AttributeSet) parser
: new XmlPullAttributes(parser);
}
XmlPullParser的基本使用
由上文可知,Android中XmlPull解析器有2种类型,一种是专用于Android xml资源文件的解析器,一种是org.xmlpull.v1自带的xml解析器,无论是那种类型,对外而言,其用法都一样,这就是面向接口编程的一个好处。
实际应用当中,使用较多的应该是org.xmlpull.v1自带的xml解析器,毕竟Android的专用资源解析器,它已经封装好自动解析了。
public static void main (String args[])
throws XmlPullParserException, IOException
{
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//区分namespace
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
/**
* 解析最开始并非位于START_DOCUMENT,多次调用next,会依次位于
* START_DOCUMENT
* START_TAG(XML节点属性解析只能在该事件类型中进行解析)
* TEXT(无Text部分“Hello World”则跳过该事件类型)
* END_TAG
* End document
*/
xpp.setInput( new StringReader ( "<foo>Hello World!</foo>" ) );
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_DOCUMENT) {
System.out.println("Start document");
} else if(eventType == XmlPullParser.START_TAG) {
System.out.println("Start tag "+xpp.getName());
} else if(eventType == XmlPullParser.END_TAG) {
System.out.println("End tag "+xpp.getName());
} else if(eventType == XmlPullParser.TEXT) {
System.out.println("Text "+xpp.getText());
}
eventType = xpp.next();
}
System.out.println("End document");
}
结果:
Start document
Start tag foo
Text Hello World!
End tag foo
End document