您的位置:首页 > 移动开发 > Android开发

Androidd-XmlPullParser解析XML

2015-11-14 22:48 351 查看
在上一篇文章Android-HttpClient连接网络获取数据中,简单使用了HttpURLConnection来获取网络数据,然而并没有对获取的数据做任何操作,比如解析从网络得到的数据。这篇文章,就来练习一下使用XmlPullParser解析XML。
XmlPullParser在Android源码中是使用的最多的一种XML解析器,当然还有其他的解析方式,比如:SAX解析器。对于这些解析器的优缺点,不妨参阅一下其他朋友的文章:【Android】实现XML解析的几种技术 , 这篇文章对几种解析XML方式分析的蛮清楚。

关于XmlPullParser解析器,在Android的历史上,Android有该接口的两个实现:

- KXmlParser 通过 XmlPullParserFactory.newPullParser().
- ExpatPullParser 通过 Xml.newPullParser().

但是大概在Android4.0的时候,Android改变了这种情况,Android让通过Xml.newPullParser()获得实例也返回KXmlParser,同时也删除了ExpatPullParser类。对于这点,不妨看看现在关于这两种方式在源码中的实现方式:

/**
* Returns a new pull parser with namespace support.
*/
public static XmlPullParser newPullParser() {
try {
KXmlParser parser = new KXmlParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
} catch (XmlPullParserException e) {
throw new AssertionError();
}
}

Xml.newPullParser()方式其实内部是new了一个KXmlParser对象并返回,所以 Xml.newPullParser()方式得到的是KXmlParser。而对于XmlPullParserFactory.newPullParser(),就Android
5.1来讲,是通过XmlPullParserFactory.newInstance()来得到KXmlParser,源码实现如下:
/**
* Creates a new instance of a PullParserFactory that can be used
* to create XML pull parsers. The factory will always return instances
* of {@link KXmlParser} and {@link KXmlSerializer}.
*/
public static XmlPullParserFactory newInstance () throws XmlPullParserException {
return new XmlPullParserFactory();
}
/**
* Protected constructor to be called by factory implementations.
*/
protected XmlPullParserFactory() {
parserClasses = new ArrayList<String>();
serializerClasses = new ArrayList<String>();
try {
parserClasses.add(Class.forName("org.kxml2.io.KXmlParser"));
serializerClasses.add(Class.forName("org.kxml2.io.KXmlSerializer"));
} catch (ClassNotFoundException e) {
throw new AssertionError();
}
}

从源码也可以看到不管是XmlPullParserFactory.newInstance(),还是XmlPullParserFactory.newPullParser(),最终返回的都是KXmlParser。
XmlPullParser是如何来解析XML中的内容呢?要真正理解XmlPullParser的工作原理,我觉的跟一下KXmlParser.java中被调用的方法,更能帮助自己理解解析的整个过程。
而我自己的理解是:XML中的各个元素节点都是配对的,XmlPullParser解析时先在XML开始元素设置一个TAG,然后开始逐个遍历,解析完一个XML元素节点,就通过next()方法跳到下一个,直到查询结束生成End事件。
下面以新浪天气API接口为例,获取北京当日的天气,并通过XmlPullParser解析返回的XML,代码虽然写的简单,有点繁琐,但是可以比较直观的看到如何解析XML中所需要的元素节点的内容。

url = "http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0";
返回的XML内容:
<?xml version="1.0" encoding="UTF-8"?>
<!-- published at 2015-11-12 14:02:03 -->
<Profiles>
<Weather>
<city>北京</city>
<status1>霾</status1>
<status2>小雨</status2>
<figure1>mai</figure1>
<figure2>xiaoyu</figure2>
<direction1>无持续风向</direction1>
<direction2>无持续风向</direction2>
<power1>≤3</power1>
<power2>≤3</power2>
<temperature1>9</temperature1>
<temperature2>6</temperature2>
<ssd>0</ssd>
<tgd1>10</tgd1>
<tgd2>10</tgd2>
<zwx>1</zwx>
<ktk>7</ktk>
<pollution>3</pollution>
<xcz>5</xcz>
<zho></zho>
<diy></diy>
<fas></fas>
<chy>5</chy>
<zho_shuoming>暂无</zho_shuoming>
<diy_shuoming>暂无</diy_shuoming>
<fas_shuoming>暂无</fas_shuoming>
<chy_shuoming>风衣、大衣、夹大衣、外套、毛衣、毛套装、西服套装、薄棉外套</chy_shuoming>
<pollution_l>轻度</pollution_l>
<zwx_l>最弱</zwx_l>
<ssd_l>较凉</ssd_l>
<fas_l>暂无</fas_l>
<zho_l>暂无</zho_l>
<chy_l>毛衣类</chy_l>
<ktk_l>建议开启(制热)</ktk_l>
<xcz_l>不适宜</xcz_l>
<diy_l>暂无</diy_l>
<pollution_s>对空气污染物扩散无明显影响</pollution_s>
<zwx_s>紫外线最弱</zwx_s>
<ssd_s>老年、幼儿、体弱者外出需要带上薄围巾、薄手套。</ssd_s>
<ktk_s>建议开启空调</ktk_s>
<xcz_s>洗车后当日有降水、大风或沙尘天气,不适宜洗车</xcz_s>
<gm>2</gm>
<gm_l>易发期</gm_l>
<gm_s>天气很凉,季节转换的气候,慎重增加衣服;较易引起感冒;</gm_s>
<yd>5</yd>
<yd_l>不适宜</yd_l>
<yd_s>天气阴冷,不适宜户外运动;</yd_s>
<savedate_weather>2015-11-12</savedate_weather>
<savedate_life>2015-11-12</savedate_life>
<savedate_zhishu>2015-11-12</savedate_zhishu>
<udatetime>2015-11-12 08:10:00</udatetime>
</Weather>
</Profiles>

package com.conway.network;
import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
public class ParseWeatherXml {
private static final String TAG = "ParseWeatherXml";
public void parseWeather(InputStream in) throws XmlPullParserException, IOException {
try {
XmlPullParser xmlPullParser = Xml.newPullParser();
//XmlPullParserFactory xxxpullparser = XmlPullParserFactory.newInstance();
xmlPullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
xmlPullParser.setInput(in, null);
xmlPullParser.nextTag();
getWeatherInfo(xmlPullParser);
} finally {
// in.close();
}
}
/**
* @author conway
* @description 解析Profiles。
* @throws IOException
* @throws XmlPullParserException
*/
private void getWeatherInfo(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
xmlPullParser.require(XmlPullParser.START_TAG, null, "Profiles");
while (xmlPullParser.next() != XmlPullParser.END_TAG) {
if (xmlPullParser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
setWeatherInfo(xmlPullParser);
}
}
/**
* @author conway
* @throws IOException
* @throws XmlPullParserException
*/
private void setWeatherInfo(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
// 城市
String mCity;
// 霾
String status_one;
// 晴,雨,多云,阴天
String status_two;
// 风向
String direction_one;
// 温度
String temperature_one;
String temperature_two;
// 温馨提示
String chy_shuoming;
String noteName = xmlPullParser.getName();
if (noteName.equals("city")) {
mCity = getWeatherCityText(xmlPullParser);
Log.i(TAG, "mCity = " + mCity);
} else if (noteName.equals("status1")) {
status_one = getWeatherStatusOneText(xmlPullParser);
Log.i(TAG, "status_one = " + status_one);
} else if (noteName.equals("status2")) {
status_two = getWeatherStatusTwoText(xmlPullParser);
Log.i(TAG, "status_two = " + status_two);
} else if (noteName.equals("direction1")) {
direction_one = getWeatherdirectionOneText(xmlPullParser);
Log.i(TAG, "direction_one = " + direction_one);
} else if (noteName.equals("temperature1")) {
temperature_one = getWeatherTempOneText(xmlPullParser);
Log.i(TAG, "temperature_one = " + temperature_one);
} else if (noteName.equals("temperature2")) {
temperature_two = getWeatherTempTwoText(xmlPullParser);
Log.i(TAG, "temperature_two = " + temperature_two);
} else if (noteName.equals("chy_shuoming")) {
chy_shuoming = getWeatherPromptText(xmlPullParser);
Log.i(TAG, "chy_shuoming = " + chy_shuoming);
} else {
//Weather元素类似Profiles元素,会在getWeatherInfo做处理,所以在这里不做处理。
if (!noteName.equals("Weather")) {
skipIfNotNeed(xmlPullParser);
}
}
}
/**
* @author conway
* @description 如果所解析的元素不是需要解析的内容,就跳到下一个元素继续解析。
* @throws IOException
* @throws XmlPullParserException
*/
private void skipIfNotNeed(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
if (xmlPullParser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (xmlPullParser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
/**
* @author conway
* @description 解析city标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherCityText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String mCity = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "city");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
mCity = xmlPullParser.getText();
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "city");
return mCity;
}
/**
* @author conway
* @description 解析status1标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherStatusOneText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String status_one = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "status1");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
status_one = xmlPullParser.getText();
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "status1");
return status_one;
}
/**
* @author conway
* @description 解析status2标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherStatusTwoText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String status_two = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "status2");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
status_two = xmlPullParser.getText();
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "status2");
return status_two;
}
/**
* @author conway
* @description 解析direction1标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherdirectionOneText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String direction_one = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "direction1");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
direction_one = xmlPullParser.getText();
Log.i(TAG, "direction_one = " + direction_one);
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "direction1");
return direction_one;
}
/**
* @author conway
* @description 解析temperature1标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherTempOneText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String temperature_one = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "temperature1");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
temperature_one = xmlPullParser.getText();
Log.i(TAG, "temperature_one = " + temperature_one);
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "temperature1");
return temperature_one;
}
/**
* @author conway
* @description 解析temperature2标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherTempTwoText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String temperature_two = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "temperature2");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
temperature_two = xmlPullParser.getText();
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "temperature2");
return temperature_two;
}
/**
* @author conway
* @description 解析chy_shuoming标签下的内容。
* @throws IOException
* @throws XmlPullParserException
*/
private String getWeatherPromptText(XmlPullParser xmlPullParser)
throws XmlPullParserException, IOException {
String chy_shuoming = "";
xmlPullParser.require(XmlPullParser.START_TAG, null, "chy_shuoming");
if (xmlPullParser.next() == XmlPullParser.TEXT) {
chy_shuoming = xmlPullParser.getText();
xmlPullParser.nextTag();
}
xmlPullParser.require(XmlPullParser.END_TAG, null, "chy_shuoming");
return chy_shuoming;
}
}

结合Android-HttpClient连接网络 中的代码,就可以在DDMS中看到输出结果:
I/ParseWeatherXml(10696): mCity = 北京
I/ParseWeatherXml(10696): status_one = 阴
I/ParseWeatherXml(10696): status_two = 多云
I/ParseWeatherXml(10696): direction_one = 无持续风向
I/ParseWeatherXml(10696): direction_one = 无持续风向
I/ParseWeatherXml(10696): temperature_one = 9
I/ParseWeatherXml(10696): temperature_one = 9
I/ParseWeatherXml(10696): temperature_two = 6
I/ParseWeatherXml(10696): chy_shuoming = 风衣、大衣、夹大衣、外套、毛衣、毛套装、西服套装、薄棉外套

此外,顺带提一下Android developers Blog一篇关于XmlPullParser解析XML的文章:Watch out for XmlPullParser.nextText(),其大致内容如下:
通过Xml.newPullParser()获得的解析器可能会有一个bug:调用nextText()并不总是前进到END_TAG,一些app可能围绕着这个问题,额外的调用next()或nextTag()方法:

publicvoid parseXml(Reader reader)
throwsXmlPullParserException,IOException{
XmlPullParser parser =Xml.newPullParser();
parser.setInput(reader);

parser.nextTag();
parser.require(XmlPullParser.START_TAG,null,"menu");
while(parser.nextTag()==XmlPullParser.START_TAG){
parser.require(XmlPullParser.START_TAG,null,"item");
String itemText = parser.nextText();
parser.nextTag();// this call shouldn’t be necessary!
parser.require(XmlPullParser.END_TAG,null,"item");
System.out.println("menu option: "+ itemText);
}
parser.require(XmlPullParser.END_TAG,null,"menu");
}

publicstaticvoid main(String[] args)throwsException{
newMenu().parseXml(newStringReader("<?xml version='1.0'?>"
+"<menu>"
+"  <item>Waffles</item>"
+"  <item>Coffee</item>"
+"</menu>"));
}

在Android Ice Cream Sandwich版本中,删除了ExpatPullParser类来修复这个bug,不幸的是,app在Android4.0版本下使用它可能会导致应用crash:

org.xmlpull.v1.XmlPullParserException: expected: END_TAG {null}item (position:START_TAG <item>@1:37in java.io.StringReader@40442fa8)
at org.kxml2.io.KXmlParser.require(KXmlParser.java:2046)
at com.publicobject.waffles.Menu.parseXml(Menu.java:25)
at com.publicobject.waffles.Menu.main(Menu.java:32)

解决方法是,仅当当前位置不是结束标记(END_TAG)时,在调用了nextText()方法以后调用nextTag()方法。

while(parser.nextTag()==XmlPullParser.START_TAG){
parser.require(XmlPullParser.START_TAG,null,"item");
String itemText = parser.nextText();
if(parser.getEventType()!=XmlPullParser.END_TAG){
parser.nextTag();
}
parser.require(XmlPullParser.END_TAG,null,"item");
System.out.println("menu option: "+ itemText);
}

在所有版本上, 上面的代码将正确的解析XML,如果app广泛使用nextText()方法,在调用nextText()的地方考虑使用下面的辅助方法:

privateString safeNextText(XmlPullParser parser)
throwsXmlPullParserException,IOException{
String result = parser.nextText();
if(parser.getEventType()!=XmlPullParser.END_TAG){
parser.nextTag();
}
return result;
}


源码链接:http://pan.baidu.com/s/1iEZuQ 密码:1jdq 。(AndroidNetWork.zip)

PS:测试代码不仅包括这里的测试代码,还包括Android-HttpClient连接网络中写的测试代码,和Android官方文档里提到的simple-loadPage
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: