【Android Developers Training】 81. 解析XML数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接:http://developer.android.com/training/basics/network-ops/xml.html
可扩展标记语言(XML)是一种将文档编码为机器可阅读的形式的规则集合。XML是一种在互联网中分享数据的比较流行的格式。那些频繁更新内容的网站(如新的站点或者博客),经常会提供一个XML源,这样外部程序就可以与内容变更保持同步。上传及解析XML数据对于需要联网的应用来说是一个很平常的任务。这节课将讲解如何解析XML文档并使用它们的数据。
一). 选择一个解析器
我们推荐使用XmlPullParser,它是一个在Android上解析XML的一种比较有效及稳定的方法。历史中Android有两种实现该接口的方法:
- 通过XmlPullParserFactory.newPullParser()实现的
KXmlParser。 - 通过Xml.newPullParser()实现的ExpatPullParser。
每一种选择都是可以的。不过这里我们使用第二个例子。
二). 分析源
解析源的第一步是决定哪些字段是你感兴趣的。解析器会提取这些你感兴趣的字段数据并把其余的忽略。
下面是在应用中被解析的源的一段摘录。每一个到StackOverflow.com的推送都会在源中显示为一个entry标签,并包含若干entry子标签:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ...">
<title type="text">newest questions tagged android - Stack Overflow</title>
...
<entry>
...
</entry>
<entry>
<id>http://stackoverflow.com/q/9439999</id>
<re:rank scheme="http://stackoverflow.com">0</re:rank>
<title type="text">Where is my data file?</title>
<category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/>
<category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/>
<author>
<name>cliff2310</name>
<uri>http://stackoverflow.com/users/1128925</uri>
</author>
<link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" />
<published>2012-02-25T00:30:54Z</published>
<updated>2012-02-25T00:30:54Z</updated>
<summary type="html">
<p>I have an Application that requires a data file...</p> </summary>
</entry>
<entry>
...
</entry>
...
</feed>
应用会提取会提取entry标签及其子标签:title,link和summary子标签的数据。
三). 初始化解析器
下一步是初始化解析器,并启动解析的步骤。在下面的代码片段中,一个不处理命名空间的解析器被初始化,并使用InputStream作为参数。通过调用nextTag()开始解析的步骤,并激活readFeed()方法,该方法提取并处理应用感兴趣的数据:
public class StackOverflowXmlParser {
// We don't use namespaces
private static final String ns = null;
public List parse(InputStream in) throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(in, null);
parser.nextTag();
return readFeed(parser);
} finally {
in.close();
}
}
...
}
四). 阅读源
readFeed()方法执行一些工作来处理源。它寻找entry标签作为开始递归处理的起始点。如果一个标签不是entry标签,那么就忽略它。一点整个源都被递归处理完了,readFeed()方法返回一个包含它从源中提取的字段的List(包含子数据成员)。该List被解析器返回。
private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
List entries = new ArrayList();
parser.require(XmlPullParser.START_TAG, ns, "feed");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
// Starts by looking for the entry tag
if (name.equals("entry")) {
entries.add(readEntry(parser));
} else {
skip(parser);
}
}
return entries;
}
五). 解析XML
解析一个XML源的步骤如下:
- 如第二节中所述,在你的应用中标识出你希望包含的标签。该例子中提取的数据为entry标签及其子标签:title,link和summary子标签的数据。
- 创建下列方法:
- 为每个你感兴趣的标签创建“read”方法。例如,readEntry(),readTitle()等。解析器从输入流中读取标签。当它遇到了名为entry,title,link或summary时,它会为标签调用相应的方法。否则就略过该标签。
- 为每个不同类型标签提取数据并将解析器推进到下一个标签的方法。例如:
- 对于title和summary标签,解析器调用readText()。该方法提取通过调用parser.getText(),从这些标签中提取数据。
- 对于link标签,解析器首先确定该link是否是自己感兴趣的,如果是的话就调用parser.getAttributeValue()来提取它的值。
- 对于entry标签,解析器会调用readEntry()。该方法解析entry中的子标签,并返回一个Entry对象,其中包含了数据成员:title,link和summary。
- 一个用以辅助的方法skip()。更多信息可以阅读:Skip Tags You Don't Care About。
下列代码片段展示了如何解析上述标签:
public static class Entry {
public final String title;
public final String link;
public final String summary;
private Entry(String title, String summary, String link) {
this.title = title;
this.summary = summary;
this.link = link;
}
}
// Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off
// to their respective "read" methods for processing. Otherwise, skips the tag.
private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, ns, "entry");
String title = null;
String summary = null;
String link = null;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("title")) {
title = readTitle(parser);
} else if (name.equals("summary")) {
summary = readSummary(parser);
} else if (name.equals("link")) {
link = readLink(parser);
} else {
skip(parser);
}
}
return new Entry(title, summary, link);
}
// Processes title tags in the feed.
private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, ns, "title");
String title = readText(parser);
parser.require(XmlPullParser.END_TAG, ns, "title");
return title;
}
// Processes link tags in the feed.
private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {
String link = "";
parser.require(XmlPullParser.START_TAG, ns, "link");
String tag = parser.getName();
String relType = parser.getAttributeValue(null, "rel");
if (tag.equals("link")) {
if (relType.equals("alternate")){
link = parser.getAttributeValue(null, "href");
parser.nextTag();
}
}
parser.require(XmlPullParser.END_TAG, ns, "link");
return link;
}
// Processes summary tags in the feed.
private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, ns, "summary");
String summary = readText(parser);
parser.require(XmlPullParser.END_TAG, ns, "summary");
return summary;
}
// For the tags title and summary, extracts their text values.
private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
String result = "";
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
return result;
}
...
}
六). 跳过你不关注的标签
上面所描述的解析XML步骤中,其中有一步是解析器跳过我们不关注的标签。下面是skip()方法的代码:
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
它为何这样就能实现跳过的功能呢:
- 如果当前遇到的不是START_TAG,那么抛出一个异常。
- 它接收START_TAG,以及之后遇到的内容,并匹配END_TAG。
- 为了确保它在正确的END_TAG停止,而不是在START_TAG之后遇到的第一个标签,它会一直向子标签深度搜索。
因此如果当前标签含有子标签,那么depth的值不会变成0,直到解析器处理了所有在原始的START_TAG和与它匹配的END_TAG之间的所有标签。例如,考虑该解析器如何略过<author>标签,该标签含有两个子标签<name>和<uri>:
- 第一次while循环,解析器在<author>之后遇到了START_TAG:<name>,此时depth的值增加到2。
- 第二次while循环,解析器遇到了END_TAG:</name>
。此时depth的值减少到1。 - 第三次while循环,解析器遇到了
START_TAG:<uri>。此时depth的值增加到2。 - 第四次while循环,解析器遇到了END_TAG:</uri>
。此时depth的值减少到1。 - 最后一次while循环,解析器遇到了END_TAG:</author>。此时depth的值减少到0,表明<author>已经被成功忽略了。
七). 处理XML数据
样例代码中,使用了AsyncTask获取并解析XML源。这样该过程就不会再UI主线程中执行。当处理执行完毕,应用会更新主Activity(NetworkActivity)的UI。
在下面摘录的代码片段中,loadPage()方法进行了如下的处理:
用XML源的URL初始化一个String变量。
在用户的设置及网络连接允许的情况下,调用new DownloadXmlTask().execute(url)。这将初始化一个新的DownloadXmlTask对象(AsyncTask的子类)并运行它的execute()方法,它会下载并解析源并将结果以String的形式返回,显示在UI上。
public class NetworkActivity extends Activity {
public static final String WIFI = "Wi-Fi";
public static final String ANY = "Any";
private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";
// Whether there is a Wi-Fi connection.
private static boolean wifiConnected = false;
// Whether there is a mobile connection.
private static boolean mobileConnected = false;
// Whether the display should be refreshed.
public static boolean refreshDisplay = true;
public static String sPref = null;
...
// Uses AsyncTask to download the XML feed from stackoverflow.com.
public void loadPage() {
if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) {
new DownloadXmlTask().execute(URL);
}
else if ((sPref.equals(WIFI)) && (wifiConnected)) {
new DownloadXmlTask().execute(URL);
} else {
// show error
}
}
AsyncTask的子类:DownloadXmlTask如下所示,它实现了下列AsyncTask的方法:
doInBackground()执行loadXmlFromNetwork(),它将源的URL作为参数传入。loadXmlFromNetwork()方法获取并处理源。当它结束以后,它会返回String作为结果。
onPostExecute()接收结果String并将它显示在UI上。
// Implementation of AsyncTask used to download XML feed from stackoverflow.com.
private class DownloadXmlTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
return loadXmlFromNetwork(urls[0]);
} catch (IOException e) {
return getResources().getString(R.string.connection_error);
} catch (XmlPullParserException e) {
return getResources().getString(R.string.xml_error);
}
} @Override
protected void onPostExecute(String result) {
setContentView(R.layout.main);
// Displays the HTML string in the UI via a WebView
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadData(result, "text/html", null);
}
}
下面是方法:loadXmlFromNetwork(),它被DownloadXmlTask调用,它执行下列任务:
- 初始化一个StackOverflowXmlParser,它也创建一个装载entry对象的List(entries),以及title,url,和summary,来存储从XML源中相应字段里提取出的数据。
- 调用downloadUrl(),它获取源并以InputStream的形式返回
- 使用StackOverflowXmlParser来解析InputStream。StackOverflowXmlParser会用源中的数据填充entries这个List。
- 处理List,并将源数据和HTML标记向结合。
- 返回HTML字符串,由AsyncTask的onPostExecute()方法将它显示在主Activity UI上的。
// Uploads XML from stackoverflow.com, parses it, and combines it with
// HTML markup. Returns HTML string.
private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException {
InputStream stream = null;
// Instantiate the parser
StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser();
List<Entry> entries = null;
String title = null;
String url = null;
String summary = null;
Calendar rightNow = Calendar.getInstance();
DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa"); // Checks whether the user set the preference to include summary text
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean pref = sharedPrefs.getBoolean("summaryPref", false); StringBuilder htmlString = new StringBuilder();
htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>");
htmlString.append("<em>" + getResources().getString(R.string.updated) + " " +
formatter.format(rightNow.getTime()) + "</em>"); try {
stream = downloadUrl(urlString);
entries = stackOverflowXmlParser.parse(stream);
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (stream != null) {
stream.close();
}
} // StackOverflowXmlParser returns a List (called "entries") of Entry objects.
// Each Entry object represents a single post in the XML feed.
// This section processes the entries list to combine each entry with HTML markup.
// Each entry is displayed in the UI as a link that optionally includes
// a text summary.
for (Entry entry : entries) {
htmlString.append("<p><a href='");
htmlString.append(entry.link);
htmlString.append("'>" + entry.title + "</a></p>");
// If the user set the preference to include summary text,
// adds it to the display.
if (pref) {
htmlString.append(entry.summary);
}
}
return htmlString.toString();
} // Given a string representation of a URL, sets up a connection and gets
// an input stream.
private InputStream downloadUrl(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
return conn.getInputStream();
}
【Android Developers Training】 81. 解析XML数据的更多相关文章
- 【Android Developers Training】 78. 序言:执行网络操作
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 33. 接收来自其它应用的简单数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 32. 向其它应用发送简单数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- Android网络之数据解析----SAX方式解析XML数据
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- 【Android Developers Training】 100. 使用Intent修改联系人数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 97. 序言:访问通讯录数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 31. 序言:共享简单数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 26. 在SQL数据库中保存数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 23. 序言:保存数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
随机推荐
- 纯真IP数据库格式详解
纯真版IP数据库,优点是记录多,查询速度快,它只用一个文件QQWry.dat就包含了所有记录,方便嵌入到其他程序中,也方便升级.缺点是你想要编辑它却是比较麻烦的,由于其文件格式的限制,你要直接添加IP ...
- 区别client、offset、scroll系列以及event的几个距离属性
element元素结点属性 一. offset系列 1.offsetWidth 和offsetHeight element.offsetWidth是一个只读属性,它包括了: css width + p ...
- Maven学习-项目对象模型
POM包含了四类描述和配置: 项目总体信息:它包含了一个项目的名称,项目的URL,发起组织,以及项目的开发者贡献者列表和许可证. 构建设置:在这一部分,我们自定义Maven构建的默认行为.我们可以更改 ...
- python 冒泡排序,递归
冒泡排序:li = [33, 55, 58, 66, 58, 555,20000000000000000000000, 5555,5555, 5, 6, 62,1]for i in range(1,l ...
- js实用方法记录-简单cookie操作
js实用方法记录-简单cookie操作 设置cookie:setCookie(名称,值,保存时间,保存域); 获取cookie:setCookie(名称); 移除cookie:setCookie(名称 ...
- 我总结的常用sql语句
建表: Set sql_mode='strict_trans_tables': 存储引擎启用严格模式,非法数据值被拒绝 Create table t3(id int(4) primary key a ...
- 求一个整数数组最大子数组之和,时间复杂度为N
#include<iostream.h> int main () { ]={-,-,-,-,-,-,-,-,-,-}; ],sum=; ;i<;i++) { ) { sum=b[i] ...
- linux下载安装phpmyadmin
phpmyadmin下载: https://www.phpmyadmin.net/downloads/ 1.解压缩 tar -zxvf phpMyAdmin-4.7.1-all-languages.t ...
- JSP/Servlet Web 学习笔记 DayTwo
JSP指令 a)page指令 定义JSP文件中的全局属性.一个JSP页面可以包含多个page指令.除了Import以外,其他page指令定义的属性/值只能出现一次. 其详细语法为: <%page ...
- 关于echarts使用的常见问题总结
关于echarts使用的问题总结1.legend图例不显示的问题: 在legend中的data为一个数组项,数组项通常为一个字符串,每一项需要对应一个系列的 name,如果数组项的值与name不相符则 ...