在工作中,不管是写app代码,还是阅读framework中的源码,涉及到解析xml的代码片段非常多,本篇文章从两个点来讲解一下,第一个点:xml文件结构 第二个点:怎么用XmlPullParser去解析。掌握这些知识后,对在阅读分析PMS解析包安装管理xml文件的源码,就会比较清晰明了。
XML: Extentsible Markup Language(可扩展标记语言)的缩写,它的格式与HTML文件的格式类似,xml是使用自定义标记来定义对象和每个对象中的数据,xml文件可以被认为是基于文本的数据库。
一个XML文档由两部分构成:第一部分是文档声明,第二部分是文档元素(节点)
文档声明通常位于XML文档的顶端,根元素之前出现,它是一个特定的包含XML 文档设定信息的部分
XML 文档由如下几个部分组成:
接下来介绍几个重要的概念:
xml文档声明
XML 文档声明是为XML 解析器进行文档处理时提供相关信息的一个很小的配置信息集合。每一个XML 文档应当包含一个XML 声明,并且XML 声明必须放在文档的第一行。如下:
"1.0" encoding="utf-8" standalone="no"?>
XML 声明中包括三个属性,每个属性设置的具体形式为:属性名称="属性值"。其中属性值需要是使用双引号或者单引号括起来,多个属性之间使用空格进行分隔。XML 声明中的三个属性的名称分别是:version、encoding和standalone。
version: 此属性用来声明XML 文档所遵循的XML 标准版本。现在通常情况下该属性的值都是1.0,尽管 XML 1.1 已经称为 W3C 的推荐标准,但是大部分的 XML 解析器还是采用 XML 1.0 标准。version 是 XML 声明中必须包含的一个属性。
encoding: encoding 属性用来告诉 XML 解析程序当前 XML 文档使用什么样的字符编码。该属性是可选的。当 XML 声明中没有明确给出字符编码方式时,XML 解析程序将默认为 XML 文档采用的是 UTF-8 字符编码。
standalone : standalone 属性定义了是否可以在不读取任何其他文件的情况下处理该文档。例如,XML 文档没有引用任何其他文件,则可以指定属性值为 yes。如果 XML 文档引用其他描述该文档可以包含的文件,则可以指定属性值为 no。因为 no 是 standalone 属性默认的属性值,所以较少会在 XML 声明中看到 standalone 属性。
注意点:如果同时设置了 encoding 和 standalone 属性,standalone 属性必须位于 encoding 属性之后。
元素
XML元素指XML文件中出现的标签,一个标签分为起始和结束标签(不能省略),一个标签有如下几种书写形式
---- 包含标签主体:
----不含标签主体:
一个标签中可以嵌套若干个子标签,但所有标签必须合理的嵌套,不允许有交叉嵌套
----
一个XML文档必须有且仅有一个根标签。其他标签都是这个根标签的子标签或孙标签
属性:
一个元素可以有多个属性,每个属性都有它自己的名称和取值,例如:
属性值一定要用引号“单引号或双引号”引用起来
元素中的属性是不允许重复的
在XML技术中,标签属性所代表的信息也可以用子元素的形式来描述。
语法格式如下:
<tagName attribute1="value1" attribute2="value2"......>tagData</tagName>
注释:
XML 注释的语法格式:
注意点:XML声明前不能有注释,也就是第一行之前不能有注释,否则报错
注释不能嵌套
demo.xml代码如下:
- "1.0" encoding="utf-8"?>
- <resources>
- 这里是资源文本
-
-
- <color value="#FF0000" name="红色">color>
-
-
- <color value="#00FF00" name="绿色"/>
- resources>
代码解读:
第一行为文档声明
第二个xml例子: 标签属性所代表的信息也可以用子元素的形式来描述,比如name 和age 就是用子元素形式表示的,但是不提倡这么写,这样子在解析的时候,代码就会比较难写。推荐上面的那种写法。
- <?xml version="1.0" encoding="UTF-8"?>
- <students>
- <person id="1111">
- <name>李四</name> <!--标签属性所代表的信息也可以用子元素的形式来描述-->
- <age>30</age>
- </person>
-
- <person id="2222" name="张三" age="20"></person >
-
- <person id="3333" name="王五" age="10"/>
- </students>
第一行 就是XML声明,当前面版本号为1, 使用的是UTF-8编码
students 为根元素: 文件体的最顶层元素称为根元素,根元素只能有一对,其名字可以自定义。所有其他元素均以子元素的形式存在于根元素内。子元素是可以重复出现的。
子元素: person
上面的xml中 子元素 person,定义了三个属性值 id name age 上面的三种写法都是等价的
好了,理解上面xml各标签的定义后,接下来开始解析内容。
先看看第一个Demo:
- import java.io.IOException;
- import java.io.StringReader;
-
- import org.xmlpull.v1.XmlPullParser;
- import org.xmlpull.v1.XmlPullParserException;
- import org.xmlpull.v1.XmlPullParserFactory;
-
- public class SimpleXmlPullApp
- {
-
- public static void main (String args[])
- throws XmlPullParserException, IOException
- {
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- factory.setNamespaceAware(true);
- XmlPullParser xpp = factory.newPullParser();
-
- xpp.setInput( new StringReader ( "
Hello World! " ) ); - 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
代码解读:
eventType 有五种解析类型:
| int值 | 事件名 | 事件定义 |
| 0 | START_DOCUMENT | 开始读取文档 |
| 1 | END_DOCUMENT | 结束文档 |
| 2 | START_TAG | 读取标签 |
| 3 | END_TAG | 结束标签 |
| 4 | TEXT | 标签中的数据内容 |
然后根据获取标签数值,做对应的逻辑处理,xmlpullparser是一行一行的从上到下解析文件的。
XmlPullParser相关的API
| 方法 | 说明 |
getEventType() | 获取当前解析的事件类型,有5种:START_DOCUMENT END_DOCUMENT START_TAG END_TAG TEXT |
getName() | 获取元素的标签名 在 START_TAG事件中使用 |
| getText() | 获取文本内容, 在TEXT 事件中使用 |
getAttributeValue(String namespace,
String name); | 类似于通过key值来获取value值, 第一个参数为default值, 第二个参数为 key 返回:一个字符串 |
如果上个例子不是很明白,我们接下来继续这个Demo:
- public class MainActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- try {
- //解析本地assets下的 xml资源文件
- parserAssetXml();
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private void parserAssetXml()throws XmlPullParserException, IOException {
- //获取XmlPullParserFactory实例
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- //通过XmlPullParserFactory 来获取 xmlPullParser对象
- XmlPullParser pullParser = factory.newPullParser();
-
- //通过此API来获取输入流,作为pullParser的参数
- InputStream inputStream = getAssets().open("demo.xml");
-
- //pullParser.setInput(输入流,"编码类型");
- pullParser.setInput(inputStream, "UTF-8");
-
- //获取解析的类型, eventType有五种类型:START_DOCUMENT END_DOCUMENT START_TAG END_TAG TEXT
- int eventType = pullParser.getEventType();
- Log.e("test", "=====第一次打印的eventType值为 :" + eventType);
-
- // 开始解析
- while (eventType != XmlPullParser.END_DOCUMENT) {
- eventType = pullParser.getEventType();
- Log.e("test", "=====解析过程中eventType值为 :" + eventType);
- switch (eventType) {
- case XmlPullParser.START_DOCUMENT:
- Log.e("test", "=====这里是文档声明====1.0\" encoding=\"UTF-8\"?>====");
- break;
-
- case XmlPullParser.START_TAG:
- String tagName = pullParser.getName();
- Log.e("test", "=====解析过程中tagName值为 :" + tagName);
- //如果 子元素的tagName为 color,就开始打印其内容
- if (tagName != null && tagName.equals("color")) {
- String colorValue = pullParser.getAttributeValue("", "value");
- Log.e("test", "=====解析过程中colorValue值为 :" + colorValue);
- String colorName = pullParser.getAttributeValue("", "name");
- Log.e("test", "=====解析过程中colorName值为 :" + colorName);
- }
- break;
-
- case XmlPullParser.END_TAG:
- Log.e("test", "====这里是元素的结束标签==="+pullParser.getName());
- break;
-
- case XmlPullParser.TEXT:
- Log.e("test", "====解析文本内容==="+pullParser.getText());
- break;
- default:
- break;
-
-
- }
- //继续解析下一个子元素
- eventType = pullParser.next();
- }
- }
- }
注意Android studio工程中的assets的资源路径,不要放错位置了,否则会提示资源找不到

打印log内容如下:
- 17:30:33.903 29787 29787 E test : =====第一次打印的eventType值为 :0
-
- 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :0
- 17:30:33.903 29787 29787 E test : =====这里是文档声明====<?xml version="1.0" encoding="UTF-8"?>====
- 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :2
- 17:30:33.903 29787 29787 E test : =====解析过程中tagName值为 :resources
- 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :4
- 17:30:33.903 29787 29787 E test : ====解析文本内容===
- 17:30:33.903 29787 29787 E test : 这里是资源文本
- 17:30:33.903 29787 29787 E test :
- 17:30:33.903 29787 29787 E test :
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :2
- 17:30:33.904 29787 29787 E test : =====解析过程中tagName值为 :color
- 17:30:33.904 29787 29787 E test : =====解析过程中colorValue值为 :#FF0000
- 17:30:33.904 29787 29787 E test : =====解析过程中colorName值为 :红色
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
- 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===color
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :4
- 17:30:33.904 29787 29787 E test : ====解析文本内容===
- 17:30:33.904 29787 29787 E test :
- 17:30:33.904 29787 29787 E test :
- 17:30:33.904 29787 29787 E test :
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :2
- 17:30:33.904 29787 29787 E test : =====解析过程中tagName值为 :color
- 17:30:33.904 29787 29787 E test : =====解析过程中colorValue值为 :#00FF00
- 17:30:33.904 29787 29787 E test : =====解析过程中colorName值为 :绿色
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
- 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===color
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :4
- 17:30:33.904 29787 29787 E test : ====解析文本内容===
- 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
- 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===resources
把代码结合log一起看:
其中 0 表示 START_DOCUMENT ,开始解析文档
每次解析一个元素的标签时,事件类型为2 START_TAG,
接下来都会走 打印 4 TEXT , 你从log中看出,只有第一次解析根元素resources标签时打印了“
这里是资源文本 接下来解析子元素color时,打印都是为空 这是为什么?
接下来打印 3 END_TAG 事件 这个元素就解析完毕
接下来就 调用 next()方法,继续解析下一个元素,重复打印 2 3 4事件。
好了,我们回答上面提出的问题?解释如下:
就是不管你有没有在 元素的标签后面添加文本内容,代码解析的时候都会走4 TEXT事件。
而且test.xml 文件你用浏览器查看的时候也是ok的,说明格式是正确的,如下:

好了,到这里,我们就把解析流程梳理完成了。 把这些理解清楚后,我们可以看看PMS中解析AndroidManifest.xml的代码片段,后续会在framework中的文章中会介绍,这里先提前说明一下
在PackageParser.java中,有解析AndroidManifest.xml的片段代码,如下:
- private boolean parseSplitApplication(Package owner, Resources res, XmlResourceParser parser,
- int flags, int splitIndex, String[] outError)
- throws XmlPullParserException, IOException {
- TypedArray sa = res.obtainAttributes(parser,
- com.android.internal.R.styleable.AndroidManifestApplication);
-
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) {
- owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE;
- }
-
- final String classLoaderName = sa.getString(
- com.android.internal.R.styleable.AndroidManifestApplication_classLoader);
- if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
- owner.applicationInfo.splitClassLoaderNames[splitIndex] = classLoaderName;
- } else {
- outError[0] = "Invalid class loader name: " + classLoaderName;
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return false;
- }
-
- final int innerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- ComponentInfo parsedComponent = null;
-
- // IMPORTANT: These must only be cached for a single <application> to avoid components
- // getting added to the wrong package.
- final CachedComponentArgs cachedArgs = new CachedComponentArgs();
- String tagName = parser.getName();
- //解析清单文件中的activity信息
- if (tagName.equals("activity")) {
- Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
- owner.baseHardwareAccelerated);
- if (a == null) {
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return false;
- }
-
- owner.activities.add(a);
- parsedComponent = a.info;
- //解析清单文件中的广播信息
- } else if (tagName.equals("receiver")) {
- Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
- true, false);
- if (a == null) {
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return false;
- }
-
- owner.receivers.add(a);
- parsedComponent = a.info;
- //解析清单文件中的服务信息
- } else if (tagName.equals("service")) {
- Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
- if (s == null) {
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return false;
- }
-
- owner.services.add(s);
- parsedComponent = s.info;
-
- } else if (tagName.equals("provider")) {
- Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
- if (p == null) {
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return false;
- }
-
- owner.providers.add(p);
- parsedComponent = p.info;
-
- }