注:本文翻譯自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();
}
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

