上一篇中说过了天气预报的一个整体流程,以及可以划分 的各个模块.
接下来我们要做的天气预报的第一部分,是从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文档来说,例如
第三步: 执行characters()方法,读取XML节点中的文本,进行文本的拼装.
第四步:当读取到文档的结束标签的时候,例如
第五步:当整个文档解析完成的时候,触发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:[email protected]
* 关于:一条致力于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;
}
}
package com.yongchun.weather.model;
/**
* 作者:肥鱼 QQ群:104780991 Email:[email protected]
* 关于:一条致力于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;
}
}
package com.yongchun.weather.model;
/**
* 作者:肥鱼 QQ群:104780991 Email:[email protected]
* 关于:一条致力于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;
}
}
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:[email protected]
* 关于:一条致力于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 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;
}
@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
cityInfo = new CityInfo();
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
if (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);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
if (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;
}
@Override
public void startDocument() throws SAXException {
currInfo = new CurrInfo();
super.startDocument();
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
@Override
public 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);
}
@Override
public 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 foreInfos;
private boolean isForeInfo = false;
public List getForeInfos() {
return foreInfos;
}
public void setForeInfos(List foreInfos) {
this.foreInfos = foreInfos;
}
@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
foreInfos = new ArrayList();
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
if (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);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
if (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:[email protected]
* 关于:一条致力于Android开源事业的鱼,还是肥的.吃得多赚的少还不会暖床,求包养.
*/
/*
* 这是ANdroid的Junit单元测试框架. 需要注意的是,需要在AndroidManifest.xml文件中注册.
* 标签中注册
* 标签中注册: 其中
* 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 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");
}
}
}
顺便顺便说一下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单元测试 错误的解决方案