本讲内容:使用 SAX 和 pull 解析器解析XML
(说明:本讲写的比较晚所以采用了Android2.3.3版本,其他的也一样,我会尽量在课件里使用最新版本的API。)
在Android中解析XML常用的有三种方法:SAX、DOM 和 pull ,三种方法各有优劣。本讲将用一个google天气预报的实例来和大家一起学习如何使用SAX和pull的方式XML解析。
一、Google天气预报API介绍
我们上一讲的时候使用过Google Weather API,这里要说明的是Google Weather API 并不是官方提供的,是非公开的API,你可以拿来用,但是不能保证准确和及时。
首先我们可以根据经纬度来获取天气信息。
上面网址查询的结果如下所示:
01 | <? xml version = "1.0" ?> |
02 | < XML_API_REPLY version = "1" > |
03 | < WEATHER module_id = "0" tab_id = "0" mobile_row = "0" mobile_zipped = "1" row = "0" section = "0" > |
04 | < FORECAST_INFORMATION > |
05 | < CITY data = "" /> |
06 | < POSTAL_CODE data = "" /> |
07 | < LATITUDE_E6 data = "34720001" /> |
08 | < LONGITUDE_E6 data = "113650001" /> |
09 | < FORECAST_DATE data = "2011-03-08" /> |
10 | < CURRENT_DATE_TIME data = "2011-03-08 14:00:00 +0000" /> |
11 | < UNIT_SYSTEM data = "SI" /> |
12 | </ FORECAST_INFORMATION > |
13 | < CURRENT_CONDITIONS > |
14 | < CONDITION data = "晴" /> |
15 | < TEMP_F data = "" /> |
16 | < TEMP_C data = "" /> |
17 | < HUMIDITY data = "湿度: 61%" /> |
18 | < ICON data = "/ig/images/weather/sunny.gif" /> |
19 | < WIND_CONDITION data = "风向: 北、风速:0 米/秒" /> |
20 | </ CURRENT_CONDITIONS > |
21 | < FORECAST_CONDITIONS > |
22 | < DAY_OF_WEEK data = "周二" /> |
23 | < LOW data = "3" /> |
24 | < HIGH data = "16" /> |
25 | < ICON data = "/ig/images/weather/sunny.gif" /> |
26 | < CONDITION data = "晴" /> |
27 | </ FORECAST_CONDITIONS > |
28 | < FORECAST_CONDITIONS > |
29 | < DAY_OF_WEEK data = "周三" /> |
30 | < LOW data = "2" /> |
31 | < HIGH data = "12" /> |
32 | < ICON data = "/ig/images/weather/cn_cloudy.gif" /> |
33 | < CONDITION data = "多云" /> |
34 | </ FORECAST_CONDITIONS > |
35 | < FORECAST_CONDITIONS > |
36 | < DAY_OF_WEEK data = "周四" /> |
37 | < LOW data = "2" /> |
38 | < HIGH data = "15" /> |
39 | < ICON data = "/ig/images/weather/sunny.gif" /> |
40 | < CONDITION data = "晴" /> |
41 | </ FORECAST_CONDITIONS > |
42 | </ WEATHER > |
43 | </ XML_API_REPLY > |
其次我们可以根据城市名称的汉语拼音来获取天气信息。
上面网址的查询结果如下所示:
01 | <?xml version= "1.0" ?> |
02 | <XML_API_REPLY version= "1" > |
03 | <WEATHER module_id= "0" tab_id= "0" mobile_row= "0" mobile_zipped= "1" row= "0" section= "0" > |
04 | <FORECAST_INFORMATION> |
05 | <CITY data= "Zhengzhou, Henan" /> |
06 | <POSTAL_CODE data= "zhengzhou" /> |
07 | <LATITUDE_E6 data= "" /> |
08 | <LONGITUDE_E6 data= "" /> |
09 | <FORECAST_DATE data= "2011-03-08" /> |
10 | <CURRENT_DATE_TIME data= "2011-03-08 16:00:00 +0000" /> |
11 | <UNIT_SYSTEM data= "SI" /> |
12 | </FORECAST_INFORMATION> |
13 | <CURRENT_CONDITIONS> |
14 | <CONDITION data= "雾霾" /> |
15 | <TEMP_F data= "50" /> |
16 | <TEMP_C data= "10" /> |
17 | <HUMIDITY data= "湿度: 43%" /> |
18 | <ICON data= "/ig/images/weather/haze.gif" /> |
19 | <WIND_CONDITION data= "风向: 北、风速:2 米/秒" /> |
20 | </CURRENT_CONDITIONS> |
21 | <FORECAST_CONDITIONS> |
22 | <DAY_OF_WEEK data= "周二" /> |
23 | <LOW data= "4" /> |
24 | <HIGH data= "14" /> |
25 | <ICON data= "/ig/images/weather/mostly_sunny.gif" /> |
26 | <CONDITION data= "晴间多云" /> |
27 | </FORECAST_CONDITIONS> |
28 | <FORECAST_CONDITIONS> |
29 | <DAY_OF_WEEK data= "周三" /> |
30 | <LOW data= "1" /> |
31 | <HIGH data= "11" /> |
32 | <ICON data= "/ig/images/weather/sunny.gif" /> |
33 | <CONDITION data= "晴" /> |
34 | </FORECAST_CONDITIONS> |
35 | <FORECAST_CONDITIONS> |
36 | <DAY_OF_WEEK data= "周四" /> |
37 | <LOW data= "3" /> |
38 | <HIGH data= "15" /> |
39 | <ICON data= "/ig/images/weather/sunny.gif" /> |
40 | <CONDITION data= "晴" /> |
41 | </FORECAST_CONDITIONS> |
42 | <FORECAST_CONDITIONS> |
43 | <DAY_OF_WEEK data= "周五" /> |
44 | <LOW data= "7" /> |
45 | <HIGH data= "19" /> |
46 | <ICON data= "/ig/images/weather/mostly_sunny.gif" /> |
47 | <CONDITION data= "以晴为主" /> |
48 | </FORECAST_CONDITIONS> |
49 | </WEATHER> |
50 | </XML_API_REPLY> |
顺便说一下,我们通过 http://www.google.com/ig/cities?output=xml&hl=zh-cn&country=cn 查到郑州的经纬度是(经度113650001,纬度34720001),那么也就是说通过查询经度113650001,纬度34720001处的天气和查找郑州的天气应该是一致的了,实际上你也看到了,上面两次查询的结果并不相同。好在我们出于学习目的这点小误差不是我们考虑的问题。
简单分析一下上述XML文件,会发现第二个forecast_conditions标签里面就是我们需要的明日天气预报信息,包括有最高、最低气温、天气情况描述和天气描述图片。
二、使用SAX解析Google Weather
DOM解析在Android开发里一般是不被推荐的,因为DOM需要把整个XML文件都读到内存里,才能组装成一个树形结构,虽然这样的树形结构我们用起来很舒服,可是它的内存开销在很多时候是难以承受的。
而SAX(Simple API for XML)则提供了一种基于事件的处理思路,他不需要装载、遍历整个XML文件,只要发现你所关心的标签或者数据,就可以随时停止解析。这在资源比较紧缺的智能手机领域里,还是显得非常有价值的。废话不说,我们还是用一个例子来展示如何使用SAX来解析XML文件,我会同样把讲解写在文档的注释里。如果同学们看着还是辛苦的话,建议找些SAX的相关知识先期补习一下。
1、新建一个项目 Lesson31_XmlSaxParser
2、在MainActivit.java的代码如下:
01 | package basic.android.xml.sax; |
02 | |
03 | import android.app.Activity; |
04 | import android.os.Bundle; |
05 | import android.view.View; |
06 | import android.widget.Button; |
07 | import android.widget.TextView; |
08 | |
09 | public class MainActivity extends Activity { |
10 | |
11 | @Override |
12 | public void onCreate(Bundle savedInstanceState) { |
13 | super .onCreate(savedInstanceState); |
14 | setContentView(R.layout.main); |
15 | |
16 | //定义UI组件 |
17 | Button b1 = (Button) findViewById(R.id.button1); |
18 | final TextView tv1 = (TextView) findViewById(R.id.textView1); |
19 | |
20 | //为按钮绑定监听器 |
21 | b1.setOnClickListener( new View.OnClickListener() { |
22 | @Override |
23 | public void onClick(View arg0) { |
24 | //定义一个查询Google天气的字符串,后面的经纬度我写死成郑州的坐标了,你懂的 |
25 | String googleWeatherUrl = ",,,34720001,113650001" ; |
26 | //定义了一个HttpClientConnector工具类用来把google天气预报返回的XML信息存储在一个字符串里,这里可能会有聪明的同学说,你已经把整个xml都读回来了,还扯什么读一半就可以退出的话,这里要说明的是google Weather API很蛋疼,直接用sax解析会出错,所以只能先完整读回来 |
27 | String googleWeatherString = HttpClientConnector.getStringByUrl(googleWeatherUrl); |
28 | //定义一个SAX Parse对象把xml的字符串解析成我们要的 明日天气信息Bean |
29 | TomorrowWeatherVO tomorrowWeatherVO = TomorrowWeatherParse.parse(googleWeatherString); |
30 | //显示天气信息 |
31 | if (tomorrowWeatherVO!= null ){ |
32 | tv1.setText( "明日天气情况:" + tomorrowWeatherVO.getCondition() + " 最高气温:" + tomorrowWeatherVO.getHigh() |
33 | + " 最低气温:" + tomorrowWeatherVO.getLow()); |
34 | } |
35 | } |
36 | }); |
37 | } |
38 | |
39 | } |
3、上面使用的HttpClientConnector工具类代码如下:
01 | package basic.android.xml.sax; |
02 | |
03 | import org.apache.http.client.ResponseHandler; |
04 | import org.apache.http.client.methods.HttpGet; |
05 | import org.apache.http.impl.client.BasicResponseHandler; |
06 | import org.apache.http.impl.client.DefaultHttpClient; |
07 | |
08 | import android.util.Log; |
09 | |
10 | public class HttpClientConnector { |
11 | |
12 | static String getStringByUrl(String url) { |
13 | |
14 | String outputString = "" ; |
15 | |
16 | // DefaultHttpClient |
17 | DefaultHttpClient httpclient = new DefaultHttpClient(); |
18 | // HttpGet |
19 | HttpGet httpget = new HttpGet(url); |
20 | // ResponseHandler |
21 | ResponseHandler<STRING> responseHandler = new BasicResponseHandler(); |
22 | |
23 | try { |
24 | outputString = httpclient.execute(httpget, responseHandler); |
25 | Log.i( "yao" , "连接成功" ); |
26 | } catch (Exception e) { |
27 | Log.i( "yao" , "连接失败" ); |
28 | e.printStackTrace(); |
29 | } |
30 | httpclient.getConnectionManager().shutdown(); |
31 | return outputString; |
32 | |
33 | } |
34 | |
35 | }</STRING> |
4、SAX解析器 TomorrowWeatherParse.java的代码如下:
01 | package basic.android.xml.sax; |
02 | |
03 | import java.io.IOException; |
04 | import java.io.StringReader; |
05 | |
06 | import javax.xml.parsers.ParserConfigurationException; |
07 | import javax.xml.parsers.SAXParserFactory; |
08 | |
09 | import org.xml.sax.InputSource; |
10 | import org.xml.sax.SAXException; |
11 | import org.xml.sax.XMLReader; |
12 | |
13 | public class TomorrowWeatherParse { |
14 | |
15 | // 解析天气预报字符串成一个天气信息对象 |
16 | public static TomorrowWeatherVO parse(String googleWeatherString) { |
17 | |
18 | SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); |
19 | |
20 | TomorrowWeatherVO tomorrowWeatherVO = new TomorrowWeatherVO(); |
21 | |
22 | try { |
23 | XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader(); |
24 | WeatherXMLHandler handler = new WeatherXMLHandler(tomorrowWeatherVO); |
25 | xmlReader.setContentHandler(handler); |
26 | |
27 | xmlReader.parse( new InputSource( new StringReader(googleWeatherString))); |
28 | |
29 | } catch (SAXException e) { |
30 | e.printStackTrace(); |
31 | } catch (ParserConfigurationException e) { |
32 | e.printStackTrace(); |
33 | } catch (IOException e) { |
34 | e.printStackTrace(); |
35 | } |
36 | |
37 | return tomorrowWeatherVO; |
38 | |
39 | } |
40 | |
41 | } |
5、TomorrowWeatherParse.java 中使用到的内容处理器 WeatherXMLHandler.java的代码如下:
01 | package basic.android.xml.sax; |
02 | |
03 | import org.xml.sax.Attributes; |
04 | import org.xml.sax.SAXException; |
05 | import org.xml.sax.helpers.DefaultHandler; |
06 | import android.util.Log; |
07 | |
08 | public class WeatherXMLHandler extends DefaultHandler { |
09 | |
10 | // 明日天气预报Bean |
11 | TomorrowWeatherVO tomorrowWeatherVO; |
12 | |
13 | // 记录出现次数 |
14 | int findCount = 0 ; |
15 | |
16 | // 默认构造方法 |
17 | public WeatherXMLHandler() { |
18 | super (); |
19 | } |
20 | |
21 | // 构造方法 |
22 | public WeatherXMLHandler(TomorrowWeatherVO tomorrowWeatherVO) { |
23 | this .tomorrowWeatherVO = tomorrowWeatherVO; |
24 | } |
25 | |
26 | /* |
27 | * 文档结束时触发 |
28 | */ |
29 | @Override |
30 | public void endDocument() throws SAXException { |
31 | Log.i("yao", "文档解析结束"); |
32 | super.endDocument(); |
33 | } |
34 | |
35 | /* |
36 | * 文档开始时触发 |
37 | */ |
38 | @Override |
39 | public void startDocument() throws SAXException { |
40 | Log.i("yao", "文档解析开始"); |
41 | super.startDocument(); |
42 | } |
43 | |
44 | /* |
45 | * 元素开始时触发 |
46 | */ |
47 | @Override |
48 | public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
49 | Log.i("yao", qName); |
50 | if (qName.equals("forecast_conditions")) { |
51 | findCount++; |
52 | } |
53 | Log.i("yao", "" + findCount); |
54 | if (findCount == 2) { |
55 | if (qName.equals("low")) { |
56 | tomorrowWeatherVO.setLow(attributes.getValue("data")); |
57 | } |
58 | if (qName.equals("high")) { |
59 | tomorrowWeatherVO.setHigh(attributes.getValue("data")); |
60 | } |
61 | if (qName.equals("icon")) { |
62 | tomorrowWeatherVO.setIcon(attributes.getValue("data")); |
63 | } |
64 | if (qName.equals("condition")) { |
65 | tomorrowWeatherVO.setCondition(attributes.getValue("data")); |
66 | } |
67 | } |
68 | super.startElement(uri, localName, qName, attributes); |
69 | } |
70 | |
71 | /* |
72 | * 元素结束时触发 |
73 | */ |
74 | @Override |
75 | public void endElement(String uri, String localName, String qName) throws SAXException { |
76 | Log.i("yao", "元素解析结束"); |
77 | super.endElement(uri, localName, qName); |
78 | } |
79 | |
80 | /* |
81 | * 读取元素内容 |
82 | */ |
83 | @Override |
84 | public void characters( char [] ch, int start, int length) throws SAXException { |
85 | super .characters(ch, start, length); |
86 | } |
87 | |
88 | } |
上面的代码里有好多空方法,是为了让你了解默认的内容处理器DefaultHandler中的常用方法,其中因为google天气xml的特殊结构,让我们没有机会使用一个更常用的方法characters,很是遗憾,大家自己找资料学习吧。
6、最后还有一个,存储明日天气信息的Bean:TomorrowWeatherVO.java
01 | package basic.android.xml.sax; |
02 | |
03 | public class TomorrowWeatherVO { |
04 | |
05 | String low; |
06 | String high; |
07 | String icon; |
08 | String condition; |
09 | |
10 | public String getLow() { |
11 | return low; |
12 | } |
13 | public void setLow(String low) { |
14 | this .low = low; |
15 | } |
16 | public String getHigh() { |
17 | return high; |
18 | } |
19 | public void setHigh(String high) { |
20 | this .high = high; |
21 | } |
22 | public String getIcon() { |
23 | return icon; |
24 | } |
25 | public void setIcon(String icon) { |
26 | this .icon = icon; |
27 | } |
28 | public String getCondition() { |
29 | return condition; |
30 | } |
31 | public void setCondition(String condition) { |
32 | this .condition = condition; |
33 | } |
34 | |
35 | public TomorrowWeatherVO(String low, String high, String icon, |
36 | String condition) { |
37 | super (); |
38 | this .low = low; |
39 | this .high = high; |
40 | this .icon = icon; |
41 | this .condition = condition; |
42 | } |
43 | |
44 | public TomorrowWeatherVO() { |
45 | |
46 | } |
47 | } |
7、照例还是把简陋的布局文件贴出来main.xml
1 | <?xml version= "1.0" encoding= "utf-8" ?> |
2 | <LINEARLAYOUT xmlns:android= "" android:orientation= "vertical" android:layout_width= "fill_parent" android:layout_height= "fill_parent" > |
3 | <BUTTON type=submit android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:text= "获取明天天气情况" android:id= "@+id/button1" > |
4 | </BUTTON> |
5 | <TEXTVIEW android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:text= "" android:id= "@+id/textView1" > |
6 | </TEXTVIEW> |
7 | </LINEARLAYOUT> |
8、最后不要忘了在AndroidManifest.xml中加入 访问互联网的权限:
1 | <USES android:name= "android.permission.INTERNET" -permission></USES> |
好,我们可以编译并运行程序,查看结果了:
点击按钮:
OK,我们发现和QQ的天气预报信息还是满切合的,是不是有那么一点点成就感?
三、使用pull解析Google Weather
pull解析XML的方式和SAX比较相近,它的官网是 ,Android中集成了pull解析方式,因此你不必自己找支持库文件。废话不说我们直接上实例。
1、新建一个项目 Lesson31_XmlPullParser
2、重用上面项目的大部分内容,只在解析上替换一下,因此我就把解析器代码贴出来就可以了,TomorrowWeatherPullParse.java的代码如下:
01 | package basic.android.lesson31; |
02 | |
03 | import java.io.IOException; |
04 | import java.io.StringReader; |
05 | |
06 | import org.xmlpull.v1.XmlPullParser; |
07 | import org.xmlpull.v1.XmlPullParserException; |
08 | import org.xmlpull.v1.XmlPullParserFactory; |
09 | |
10 | import android.util.Log; |
11 | |
12 | public class TomorrowWeatherPullParse { |
13 | |
14 | // 解析天气预报字符串成一个天气信息对象 |
15 | public static TomorrowWeatherVO parse(String googleWeatherString) { |
16 | |
17 | Log.i( "yao" , "TomorrowWeatherPullParse.parse" ); |
18 | |
19 | // 记录出现次数 |
20 | int findCount = 0 ; |
21 | |
22 | // 明日天气预报Bean |
23 | TomorrowWeatherVO tomorrowWeatherVO = new TomorrowWeatherVO(); |
24 | |
25 | try { |
26 | |
27 | //定义工厂 XmlPullParserFactory |
28 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
29 | |
30 | //定义解析器 XmlPullParser |
31 | XmlPullParser parser = factory.newPullParser(); |
32 | |
33 | //获取xml输入数据 |
34 | parser.setInput( new StringReader(googleWeatherString)); |
35 | |
36 | //开始解析事件 |
37 | int eventType = parser.getEventType(); |
38 | |
39 | //处理事件,不碰到文档结束就一直处理 |
40 | while (eventType != XmlPullParser.END_DOCUMENT) { |
41 | //因为定义了一堆静态常量,所以这里可以用switch |
42 | switch (eventType) { |
43 | case XmlPullParser.START_DOCUMENT: |
44 | break ; |
45 | |
46 | case XmlPullParser.START_TAG: |
47 | //给当前标签起个名字 |
48 | String tagName = parser.getName(); |
49 | //看到感兴趣的标签个计数 |
50 | if (tagName.equals( "forecast_conditions" )) { |
51 | findCount++; |
52 | } |
53 | //看到要处理的标签,就处理 |
54 | if (findCount == 2 ) { |
55 | if (tagName.equals( "low" )) { |
56 | //XML中的属性可以用下面的方法获取,其中0是序号,代表第一个属性 |
57 | tomorrowWeatherVO.setLow(parser.getAttributeValue( 0 )); |
58 | } |
59 | if (tagName.equals( "high" )) { |
60 | tomorrowWeatherVO.setHigh(parser.getAttributeValue( 0 )); |
61 | } |
62 | if (tagName.equals( "icon" )) { |
63 | tomorrowWeatherVO.setIcon(parser.getAttributeValue( 0 )); |
64 | } |
65 | if (tagName.equals( "condition" )) { |
66 | Log.i( "yao" , "condition=" + parser.getAttributeValue( 0 )); |
67 | tomorrowWeatherVO.setCondition(parser.getAttributeValue( 0 )); |
68 | } |
69 | } |
70 | break ; |
71 | case XmlPullParser.END_TAG: |
72 | break ; |
73 | case XmlPullParser.END_DOCUMENT: |
74 | break ; |
75 | } |
76 | |
77 | //别忘了用next方法处理下一个事件,忘了的结果就成死循环#_# |
78 | eventType = parser.next(); |
79 | } |
80 | |
81 | } catch (XmlPullParserException e) { |
82 | e.printStackTrace(); |
83 | } catch (IOException e) { |
84 | e.printStackTrace(); |
85 | } |
86 | |
87 | return tomorrowWeatherVO; |
88 | } |
89 | } |
编译和运行结果和上面的项目一模一样,我也就不上图了。我们可以看到pull解析方式更简单直接些,代码也少一些,至少省了一个handler文件,不是吗。好了本讲就到这里,祝愉快。