Android XML文件结构 和 用XmlPullParser 来解析xml文件
标签: Android App xml xml文件结构 XmlPullParser解析
1. 背景
在工作中,不管是写app代码,还是阅读framework中的源码,涉及到解析xml的代码片段非常多,本篇文章从两个点来讲解一下,第一个点:xml文件结构 第二个点:怎么用XmlPullParser去解析。掌握这些知识后,对在阅读分析PMS解析包安装管理xml文件的源码,就会比较清晰明了。
2. XML文件结构
2.1 XML 定义
XML: Extentsible Markup Language(可扩展标记语言)的缩写,它的格式与HTML文件的格式类似,xml是使用自定义标记来定义对象和每个对象中的数据,xml文件可以被认为是基于文本的数据库。
2.2 XML文件结构和基本语法
一个XML文档由两部分构成:第一部分是文档声明,第二部分是文档元素(节点)
文档声明通常位于XML文档的顶端,根元素之前出现,它是一个特定的包含XML 文档设定信息的部分
XML 文档由如下几个部分组成:
- XML 声明:用来设置XML文档解析时所需的基本参数。
- 处理指令:为某个特定类型的软件反馈一条特殊的指令。
- 文档类型定义:用来设置更多高级的信息,如实体、属性及有效性相关的信息。
- 注释:用于提醒XML文档作者或临时标注出文档中不完善的部分。
接下来介绍几个重要的概念:
xml文档声明
XML 文档声明是为XML 解析器进行文档处理时提供相关信息的一个很小的配置信息集合。每一个XML 文档应当包含一个XML 声明,并且XML 声明必须放在文档的第一行。如下:
<?xml version="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文件中出现的标签,一个标签分为起始和结束标签(不能省略),一个标签有如下几种书写形式
---- 包含标签主体: <tagName> some content</tagName>
----不含标签主体: <mytag><mytag>
一个标签中可以嵌套若干个子标签,但所有标签必须合理的嵌套,不允许有交叉嵌套
---- <tag1><tag2></tag1></tag2> 错误
一个XML文档必须有且仅有一个根标签。其他标签都是这个根标签的子标签或孙标签
属性:
一个元素可以有多个属性,每个属性都有它自己的名称和取值,例如:<tag name="张三"/>
属性值一定要用引号“单引号或双引号”引用起来
元素中的属性是不允许重复的
在XML技术中,标签属性所代表的信息也可以用子元素的形式来描述。
语法格式如下:
<tagName attribute1="value1" attribute2="value2"......>tagData</tagName>
注释:
XML 注释的语法格式: <!--这里是注释-->
注意点:XML声明前不能有注释,也就是第一行之前不能有注释,否则报错
注释不能嵌套
2.3 XML实例
demo.xml代码如下:
<?xml version="1.0" encoding="utf-8"?> <!--xml文档声明-->
<resources> <!--根元素-->
这里是资源文本
<!--元素第一种标准写法-->
<color value="#FF0000" name="红色"></color>
<!--元素第二种写法-->
<color value="#00FF00" name="绿色"/>
</resources>
代码解读:
第一行为文档声明
<resources> 为根元素
<color> 为子元素,其内部有2个属性 value 和 name
第二个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 version="1.0" encoding="UTF-8"?> 就是XML声明,当前面版本号为1, 使用的是UTF-8编码
students 为根元素: 文件体的最顶层元素称为根元素,根元素只能有一对,其名字可以自定义。所有其他元素均以子元素的形式存在于根元素内。子元素是可以重复出现的。
子元素: person
上面的xml中 子元素 person,定义了三个属性值 id name age 上面的三种写法都是等价的
好了,理解上面xml各标签的定义后,接下来开始解析内容。
3. XmlPullPaser 解析
先看看第一个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 ( "<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
代码解读:
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", "=====这里是文档声明====<?xml version=\"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中的文章中会介绍,这里先提前说明一下
4. PMS源码片段
在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;
}
智能推荐
IDEA之Android解析xml文件失败
异常错误: Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference 解决方案: ①在项目的main文件夹下创建assets,把需要解析的xml文件...
Android:利用xstream解析xml文件
一、前言 利用xstream解析xml文件的文章网上有很多,自己也参考了不少,首先对这些默默奉献的猿们表示感谢! 由于我项目中服务器给我返回的xml需要解析成集合类型,且涉及标签内属性,折腾了一番,做个简单的记录,仅供参考! 一般常见的xml文件格式如下: 这种格式的解析是相对比较简单的,难度不大,参考网上的文章很容易解析成功。 然而有时候服务器端返回的xml文件并不是这种形式,可能是下面这种: ...
第十一讲:Android中的xml和Json文件的解析
目录 1.数据格式的引入 2.XML数据格式简介 3.解析XML数据的方法 3.1DOM方式解析 3.2 SAX解析方式 3.3 PULL解析方式 4.JSON数据格式简介 5.解析JSON数据的方法 1.数据格式的引入 在开发应用程序的时候经常性的会遇到服务器与客户端通讯,或者不同语言间数据传递与交互的情况。就这样人们就总结出了统一的数据格式来传递数据,这就是数据格式或叫做数据交换格式。 常见的...
Python3:使用lxml库来解析xml文件和html文件(使用xpath方式解析)
1.前言 今天知道了一个python的xml解析库,所以今天决定学习当前lxml库! 2.安装当前的lxml 由于本人下载不下来所以直接在官网下载文件直接安装的 3.简单的使用当前的lxml解析xml文件 1.首先创建一个需要被解析的xml文件,users.xml文件 这是一个测试的数据模拟的 2.开始导入lxml模块 然后开始解析 发现显示的类型为:<Element user at 0x1...
Android项目结构和AndroidManifest.xml
创建项目 在开发一款Android应用的时候,第一步我们需要在Android的IDE开发工具中去创建一个项目。接下来会对创建项目和项目结构中各个步骤,路径功能做个梳理和讲解。 Application name:当前应用程序的名称,就是我们手机中看到的应用程序图标下面的那个名字。 Company domain:公司域名(会被转化为包名)。 Project location:项目存放路径。创建好的an...
猜你喜欢
Android项目结构和AndroidManifest.xml
Android项目结构 注意:图片为Eclipse中查看Android项目的项目结构,因为无论使用AndroidStudio还是Eclipse去开发Android项目,其生成的项目结构是和各目录路径所负责的功能是一样的。 src:src是source的意思,存放java源代码的路径,我们可以在src目录下创建包和包中的源代码文件。在AndroidStudio中的android项目目录中src目录下...
(C++)Leetcode狂刷200题——标签“动态规划篇--简单难度10道(两道重复) #198. 打家劫舍
第五道、#198. 打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 示例 1: 输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (...
8. Linux mint 安装Tim、网盘、wps等常用软件
Linux mint 安装 常用软件 1. 安装网易云音乐(官网直接有安装包) 可以听歌享受一下的旅程 2.安装tim 上面直接引入原文的开头了, 教程其实原文已经非常详细,还有一些问题的注意,都已经讲的分明白这里直接引入原文链接了 2019年wine QQ最完美解决方案 软件 Github 地址 不过看完原文可能需要花一些时间,如果只是想快速安装QQ的话,按照下面步骤就可以了 Tim的下载地址:...
Android Service与IntentService测试 带你理清两者的细节
目录 Service IntentService Service 布局文件中定义了四个按钮,分别设置了对应的监听事件:startService(),stopService(),bindService(),unbindService()。 在 MyService 的回调方法中打印该方法名以及对应线程。 测试: 点击顺序: Start -> Stop 可知 Service 的几个回调方法都是在 ...
京东抢购服务高并发实践
声明:本位来自京东张开涛的微信公众号(kaitao-1234567),授权CSDN转载,如需转载请联系作者。 作者:张子良,京东高级开发工程师,在京东负责抢购后端服务系统架构和开发工作。 责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件[email protected],另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,...
