关闭

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

标签: androidxmlstringattributesstreamjunit
3674人阅读 评论(3) 收藏 举报
分类:

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

        接下来我们要做的天气预报的第一部分,是从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;
		}

		@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<ForeInfo> foreInfos;
		private boolean isForeInfo = false;

		public List<ForeInfo> getForeInfos() {
			return foreInfos;
		}

		public void setForeInfos(List<ForeInfo> foreInfos) {
			this.foreInfos = foreInfos;
		}

		@Override
		public void startDocument() throws SAXException {
			// TODO Auto-generated method stub
			foreInfos = new ArrayList<ForeInfo>();
			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: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单元测试 错误的解决方案



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:90979次
    • 积分:969
    • 等级:
    • 排名:千里之外
    • 原创:18篇
    • 转载:1篇
    • 译文:0篇
    • 评论:19条
    文章存档
    最新评论