一个项目搞定Android开发,SAX解析XML文件获取数据.

来源:互联网 发布:单片机与传感器 编辑:程序博客网 时间:2024/06/03 17:08

        上一篇中说过了天气预报的一个整体流程,以及可以划分 的各个模块.

        接下来我们要做的天气预报的第一部分,是从XML文档中将我们需要的数据截取出来.也就是所谓的XML解析.

        在这里我们采用的是SAX的方式进行XML解析.具体的XML解析过程肥鱼就不在这里说了,代码是最好的说明.

        我们重点说一下SAX解析数据跟DOM解析数据的区别以及SAX进行解析的原理.

         那么,什么是SAX解析呢?

         SAX的全称应该是:Simple Adapter for XML,既指的是一个接口,又指相关的软件包.其优点是逐行扫描,可以再任意时刻停止下来,而其缺点则是操作较复杂,添加和删除内容比较麻烦.

        DOM的方式解析XML数据,其特点是先把整个XML文档读取到内存中,生成一个标签树,虽然标签树对于开发人员来说非常的方便,但这样做对内存的占用比较大.对于内存受限的手持设备来说,这种方法还是不怎么推荐的.

        而SAX的方式采用的是基于事件驱动的方式来解析,也就是说,文档时一行一行的读,一行一行的解析,整个XML文档并没有完全读取到内存中.

        SAX的工作原理是对文档进行顺序扫描,当扫描到文档开始与结束、元素开始与结束、文档结束等地方时通知事件处理函数,由事件处理函数执行相应动作,然后继续同样的扫描,直至文档结束。

下图所示是SAX解析的一个事件模型:


而SAX 解析XML文件的一个过程则如下图所示:


       

那么 SAX解析的一个流程是怎么样呢?

第一步: 当读取文档开始的时候,触发startDocument()方法,这里可以做一些初始化的工作,一般是初始化值对象等.

第二部: ,当读取到标签,对于google天气的XML文档来说,例如<weather>标签时,触发startElement()方法,整个标签的解析工作主要是在这里完成.

第三步: 执行characters()方法,读取XML节点中的文本,进行文本的拼装.

第四步:当读取到文档的结束标签的时候,例如<weather/>标签,触发endElement()方法,结束对该标签的解析.

第五步:当整个文档解析完成的时候,触发endDocument()方法,返回解析结果.


OK,从现在开始呢,我们的天气预报的设计要进入到模块代码的设计阶段了.

我们先新建一个名称为Weather的Android项目.下图所示是我的一个项目的结构:


肥鱼创建了四个包来存放不同的类.

  其中model包负责存放数据的,也就是使用值对象保存XML解析出来的数据的.

         test包是用来存放单元测试工具的.(目前test包下是对XML解析进行单元测试的.)

         ui包是用来存放Activity的,也就是界面的.

         xmlhandler包是用来存放XML解析的一些工具类的.

src目录下的weather.xml就是我们要解析的xml文件,我们放到src目录下方便读取.

我们来看下model包里面的类:

CityInfo.java   CurrInfo.java    ForeInfo.java 这三个值对象是分别存放 当前城市信息  当前城市的当前天气  当前城市的预报天气信息的.这三个值对象早在分析XML文件结构的时候就创建了,但是此处又做了一些调整,所以重新贴一次源码:

CityInfo.java

package com.yongchun.weather.model;/** * 作者:肥鱼 QQ群:104780991 Email:zhaoyongchun2011@gmail.com * 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养. */public class CityInfo {private String city_name;private String postal_code;private String latitude;private String longitude;private String forecast_time;private String current_time;private String unit_syetem;public String getCity_name() {return city_name;}public void setCity_name(String city_name) {this.city_name = city_name;}public String getPostal_code() {return postal_code;}public void setPostal_code(String postal_code) {this.postal_code = postal_code;}public String getLatitude() {return latitude;}public void setLatitude(String latitude) {this.latitude = latitude;}public String getLongitude() {return longitude;}public void setLongitude(String longitude) {this.longitude = longitude;}public String getForecast_time() {return forecast_time;}public void setForecast_time(String forecast_time) {this.forecast_time = forecast_time;}public String getCurrent_time() {return current_time;}public void setCurrent_time(String current_time) {this.current_time = current_time;}public String getUnit_syetem() {return unit_syetem;}public void setUnit_syetem(String unit_syetem) {this.unit_syetem = unit_syetem;}}

CurrInfo.java

package com.yongchun.weather.model;/** * 作者:肥鱼 QQ群:104780991 Email:zhaoyongchun2011@gmail.com * 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养. */public class CurrInfo {private String condition;private String temp_f;private String temp_c;private String humidity;private String icon;private String wind_condition;public String getCondition() {return condition;}public void setCondition(String condition) {this.condition = condition;}public String getTemp_f() {return temp_f;}public void setTemp_f(String temp_f) {this.temp_f = temp_f;}public String getTemp_c() {return temp_c;}public void setTemp_c(String temp_c) {this.temp_c = temp_c;}public String getHumidity() {return humidity;}public void setHumidity(String humidity) {this.humidity = humidity;}public String getIcon() {return icon;}public void setIcon(String icon) {this.icon = icon;}public String getWind_condition() {return wind_condition;}public void setWind_condition(String wind_condition) {this.wind_condition = wind_condition;}}

ForeInfo.java

package com.yongchun.weather.model;/** * 作者:肥鱼 QQ群:104780991 Email:zhaoyongchun2011@gmail.com * 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养. */public class ForeInfo {private String day_of_week;private String low;private String high;private String icon;private String condition;public String getDay_of_week() {return day_of_week;}public void setDay_of_week(String day_of_week) {this.day_of_week = day_of_week;}public String getLow() {return low;}public void setLow(String low) {this.low = low;}public String getHigh() {return high;}public void setHigh(String high) {this.high = high;}public String getIcon() {return icon;}public void setIcon(String icon) {this.icon = icon;}public String getCondition() {return condition;}public void setCondition(String condition) {this.condition = condition;}}

接下来我们在xmlhandler包下创建一个类WeatherService.java.

WeatherService.java 里面有三个内部类,这三个内部类分别解析出当前城市的信息,当前城市的当前天气信息,当前城市的预报信息.三个内部类对应三个方法,分别返回解析出来的三部分数据.

WeatherService.java

package com.yongchun.weather.xmlhandler;/*  SAX是一个解析速度快并且占用内存少的xml解析器,非常适合用于Android等移动设备。  SAX解析XML文件采用的是事件驱动,也就是说,它并不需要解析完整个文档,在按内容顺序解析文档的过程中, SAX会判断当前读到的字符是否合法XML语法中的某部分,如果符合就会触发事件。 所谓事件,其实就是一些回调(callback)方法,这些方法(事件)定义在ContentHandler接口。 下面是一些ContentHandler接口常用的方法:  startDocument()  当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。  endDocument()  和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。   startElement(String namespaceURI, String localName, String qName, Attributes atts)   当读到一个开始标签的时候,会触发这个方法。 namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。 通过atts可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理, 当遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中, 所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息, 都是不得而知的,都需要你的程序来完成。 这使得SAX在编程处理上没有DOM来得那么方便。  endElement(String uri, String localName, String name)  这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。  characters(char[] ch, int start, int length)   这个方法用来处理在XML文件中读到的内容,第一个参数为文件的字符串内容, 后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就可以获取内容。  */import java.io.InputStream;import java.util.ArrayList;import java.util.List;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import android.os.Bundle;import android.os.Message;import android.util.Log;import com.yongchun.weather.model.CityInfo;import com.yongchun.weather.model.CurrInfo;import com.yongchun.weather.model.ForeInfo;/** * 作者:肥鱼 QQ群:104780991 Email:zhaoyongchun2011@gmail.com * 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养. */public class WeatherService extends DefaultHandler {/** * 获取到当前预报的城市信息. * */public CityInfo getCityInfo(InputStream stream) throws Throwable {SAXParserFactory factory = SAXParserFactory.newInstance();SAXParser parser = factory.newSAXParser();CityInfoHandler handler = new CityInfoHandler();parser.parse(stream, handler);return handler.getCityInfo();}/** * 获取到当前地区的天气信息 * */public CurrInfo getCurrInfo(InputStream stream) throws Throwable {SAXParserFactory factory = SAXParserFactory.newInstance();SAXParser parser = factory.newSAXParser();CurrInfoHandler handler = new CurrInfoHandler();parser.parse(stream, handler);return handler.getCurrInfo();}/** * 获取到当前地区的预报信息 * */public List<ForeInfo> getForeInfos(InputStream stream) throws Throwable {SAXParserFactory factory = SAXParserFactory.newInstance();SAXParser parser = factory.newSAXParser();ForecastHandler handler = new ForecastHandler();parser.parse(stream, handler);return handler.getForeInfos();}private class CityInfoHandler extends DefaultHandler {private CityInfo cityInfo;private boolean isCityInfo = false;public CityInfo getCityInfo() {return cityInfo;}public void setCityInfo(CityInfo cityInfo) {this.cityInfo = cityInfo;}@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubcityInfo = new CityInfo();super.startDocument();}@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubif (localName.equals("forecast_information")) {isCityInfo = true;}if (isCityInfo) {String data = attributes.getValue("data");if (localName.equals("city")) {cityInfo.setCity_name(data);} else if (localName.equals("postal_code")) {cityInfo.setPostal_code(data);} else if (localName.equals("latitude_e6")) {cityInfo.setLatitude(data);} else if (localName.equals("longitude_e6")) {cityInfo.setLongitude(data);} else if (localName.equals("forecast_date")) {cityInfo.setForecast_time(data);} else if (localName.equals("current_date_time")) {cityInfo.setCurrent_time(data);} else if (localName.equals("unit_system")) {cityInfo.setUnit_syetem(data);}}super.startElement(uri, localName, qName, attributes);}@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stubif (localName.equals("forecast_information")) {isCityInfo = false;}super.endElement(uri, localName, qName);}}private class CurrInfoHandler extends DefaultHandler {private CurrInfo currInfo;private boolean isCurrInfo = false;public CurrInfo getCurrInfo() {return currInfo;}@Overridepublic void startDocument() throws SAXException {currInfo = new CurrInfo();super.startDocument();}@Overridepublic void endDocument() throws SAXException {super.endDocument();}@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {if (localName.equals("current_conditions")) {isCurrInfo = true;}if (isCurrInfo) {String data = attributes.getValue("data");if (localName.equals("condition")) {currInfo.setCondition(data);} else if (localName.equals("temp_f")) {currInfo.setTemp_f(data);} else if (localName.equals("temp_c")) {currInfo.setTemp_c(data);} else if (localName.equals("humidity")) {currInfo.setHumidity(data);} else if (localName.equals("icon")) {currInfo.setIcon(data);} else if (localName.equals("wind_condition")) {currInfo.setWind_condition(data);}}super.startElement(uri, localName, qName, attributes);}@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {if (localName.equals("current_conditions")) {isCurrInfo = false;}super.endElement(uri, localName, qName);}}private class ForecastHandler extends DefaultHandler {private ForeInfo foreInfo;private List<ForeInfo> foreInfos;private boolean isForeInfo = false;public List<ForeInfo> getForeInfos() {return foreInfos;}public void setForeInfos(List<ForeInfo> foreInfos) {this.foreInfos = foreInfos;}@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubforeInfos = new ArrayList<ForeInfo>();super.startDocument();}@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubif (localName.equals("forecast_conditions")) {foreInfo = new ForeInfo();isForeInfo = true;Log.v("isForeInfo", Boolean.toString(isForeInfo));}if (isForeInfo) {String data = attributes.getValue("data");if (localName.equals("day_of_week")) {foreInfo.setDay_of_week(data);} else if (localName.equals("low")) {foreInfo.setLow(data);} else if (localName.equals("high")) {foreInfo.setHigh(data);} else if (localName.equals("icon")) {foreInfo.setIcon(data);} else if (localName.equals("condition")) {foreInfo.setCondition(data);}}super.startElement(uri, localName, qName, attributes);}@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stubif (localName.equals("forecast_conditions")) {Log.v("isForeInfo", Boolean.toString(isForeInfo));foreInfos.add(foreInfo);isForeInfo = false;foreInfo = null;}super.endElement(uri, localName, qName);}}}


为了检测我们的XML解析是否是成功的,肥鱼在test包下创建了WeatherServiceTest.java对WeatherService.java中的三个方法进行测试.

WeatherServiceTest.java

package com.yongchun.weather.test;import java.io.InputStream;import java.util.ArrayList;import java.util.List;import com.yongchun.weather.model.CityInfo;import com.yongchun.weather.model.CurrInfo;import com.yongchun.weather.model.ForeInfo;import com.yongchun.weather.xmlhandler.WeatherService;import android.test.AndroidTestCase;import android.util.Log;/** * 作者:肥鱼 QQ群:104780991 Email:zhaoyongchun2011@gmail.com * 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养. *//* * 这是ANdroid的Junit单元测试框架. 需要注意的是,需要在AndroidManifest.xml文件中注册. * <application>标签中注册<uses-library android:name="android.test.runner" /> * <manifest>标签中注册: <instrumentation * android:name="android.test.InstrumentationTestRunner" * android:targetPackage="com.yongchun.weather.ui" /> <uses-permission * android:name="android.permission.INTERNET" /> 其中 * targetPackage的指向为主Activity的路径,此处很奇怪为什么不是Test类所在的路径. */public class WeatherServiceTest extends AndroidTestCase {private String TAG = "####WeatherServiceTest####";/** * 测试WeatherService中getCityInfo的方法. * */public void test_getCityInfo() throws Throwable {WeatherService service = new WeatherService();InputStream stream = this.getClass().getClassLoader().getResourceAsStream("weather.xml");CityInfo cityInfo = service.getCityInfo(stream);Log.v(TAG, cityInfo.toString());}/** * 测试WeatherService中getCurrInfo的方法. * */public void test_getCurrInfo() throws Throwable {WeatherService service = new WeatherService();InputStream stream = this.getClass().getClassLoader().getResourceAsStream("weather.xml");CurrInfo currInfo = new CurrInfo();currInfo = service.getCurrInfo(stream);Log.v(TAG, currInfo.getCondition() + "&&" + currInfo.getHumidity()+ "&&" + currInfo.getWind_condition());}/** * 测试WeatherService中getForeInfos的方法. * */public void test_getForeInfos() throws Throwable {WeatherService service = new WeatherService();InputStream stream = this.getClass().getClassLoader().getResourceAsStream("weather.xml");List<ForeInfo> foreInfos = service.getForeInfos(stream);for (ForeInfo foreInfo : foreInfos) {Log.v(TAG, foreInfo.getCondition() + "#### Condition");Log.v(TAG, foreInfo.getDay_of_week() + "#### Day of Week");Log.v(TAG, foreInfo.getHigh() + "#### High");Log.v(TAG, foreInfo.getIcon() + "#### Icon");Log.v(TAG, foreInfo.getLow() + "#### Low");}}}

OK,我们看一下Junit的运行结果.

顺便顺便说一下Junit的运行  他跟将application部署到虚拟机是不一样的.

选中Junit测试类,即WeatherServiceTest.java,右击 Run as -----  Android Junit Test .




这里附上单元测试常见的两个错误的解决方案:

  Test run failed: Unable to find instrumentation target package Android Junit单元测试报错的解决方案

等待运行结果呢,顺便插播一下广告:我们的QQ群:104780991 这里有一群热衷于Android开源事业的朋友,这里也有一群热衷于技术分享的朋友.我们欢迎一切热衷于开源 一切热衷于分享的朋友的加入

  XML is not configured correctly for running test.Android Junit单元测试 错误的解决方案



原创粉丝点击