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类。对于这点,不妨看看现在关于这两种方式在源码中的实现方式:
Xml.newPullParser()方式其实内部是new了一个KXmlParser对象并返回,所以 Xml.newPullParser()方式得到的是KXmlParser。而对于XmlPullParserFactory.newPullParser(),就Android
5.1来讲,是通过XmlPullParserFactory.newInstance()来得到KXmlParser,源码实现如下:
从源码也可以看到不管是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内容:
结合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()方法:
在Android Ice Cream Sandwich版本中,删除了ExpatPullParser类来修复这个bug,不幸的是,app在Android4.0版本下使用它可能会导致应用crash:
解决方法是,仅当当前位置不是结束标记(END_TAG)时,在调用了nextText()方法以后调用nextTag()方法。
在所有版本上, 上面的代码将正确的解析XML,如果app广泛使用nextText()方法,在调用nextText()的地方考虑使用下面的辅助方法:
源码链接:http://pan.baidu.com/s/1iEZuQ 密码:1jdq 。(AndroidNetWork.zip)
PS:测试代码不仅包括这里的测试代码,还包括Android-HttpClient连接网络中写的测试代码,和Android官方文档里提到的simple-loadPage
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
相关文章推荐
- 《Android开发》——1.Activity之间的参数传递
- 最近的一些感想(关于移动客户端开发android,ios)
- android中的MVP模式
- Android的SQlite先天不足:删除 插入后主键不能自动排序 解决(附:SQlite开发的完整demo)
- 解决Android中多次点击(快速点击多次 )启动多个相同界面的问题
- 【Android】Android sdk content loader 0%
- 安卓星级控件
- android 中this与supe的区别
- 关于Android中的内存泄漏之异步操作
- Android性能调优
- Android Studio简单设置
- Android Studio 快捷键整理分享
- 自定义view实现android5.0 ripple效果
- Android 组合控件
- Windows环境下Android Studio v1.0安装教程
- android中Serializable 和 Parcelable 的区别
- Android创建前台运行的Service(不会被系统杀死)
- Android之混淆代码总结
- Android中Xml工具类的封装
- android图片等比例缩放 填充屏幕