(From: http://blog.csdn.net/ichliebephone/article/details/5965826)
一.
简单介绍
XML(
eX
tensible Markup Language)
,
即可扩展标记语言
,
是一种简单的数据存储语言,使用一系列简单的标记描述数据
。
XML 经常用作
Internet
上的一种数据格式
,因此
如果您希望通过 Internet
访问数据,则数据很有可能是
XML
格式
,或者
如果您希望发送数据给 Web
服务,那么您可能也需要发送
XML
。简而言之,如果您的
Android
应用程序将利用
Internet
,那么您可能需要使用
XML
。幸运的是,您可以采用多种方法在
Android
上使用
XML
。这个学习系列就和大家一起学习一下在
Android平台上读写
XML
数据的多种方式。
而最近用业余时间做了一个《地震及时通》,其中就需要从网络上读取实时的XML
形式的地震数据,因此我们在学习的同时将会完成读取
XML
形式的地震数据的
Demo
例子。
二.
基础知识
2.1 整体介绍
Android上对
XML
解析的支持是相当强大的,我们可以先来看一下
Android
中和
XML
解析相关的包:
1.
a
ndroid.sax
这是
Android
SDK
提供的sax
解析的包,因为可以对具体的
Element
设置监听进行处理,因此有更好鲁棒性。
2.
a
ndroid.util
.Xml
这是
a
ndroid.util
包中的其中一个类,提供
XML相关的实用方法,而且都是
public static
形式的类方法,即可以直接以类名调用。
3.
javax.xml.parsers
这是使用原来Java
SDK
用于xml
处理的
API
,即
JAXP(
Java API for XML Processing
),主要提供了
SAX
和
DOM
方式解析
XML
的工厂方法。
4.
org.w3c.dom
提供具体的和DOM
方式解析
XML
相关的接口,如
Document
、
Element
等。
5.
org.xml.sax
提供具体的和SAX
方式解析
XML
相关的接口,如
XMLReader
及
4
个处理用的
Handler
等。
6.
org.xml.sax.helpers
提供SAX
的帮助类,以便更方便的用来解析,比如实现了
SAX
的
4
个处理用的
Handler
接口的
DefaultHandler
,用来更方便使用
XML
过滤器
XMLFilter
的
XMLFilterImpl
,和用于更方便创建
XMLReader的
XMLReaderFactory
等。
7.
org.xmlpull.v1
提供Pull
方式解析
XML
的接口
XmlPullParser
和用于写
XML
的
XmlSerializer
等。
以上就是Android
提供的和
XML
读写相关的一些包,在这个学习系列中我们将对这些包的功能进行具体的介绍,并依次使用这些
SAX
解析的方式完成读取
XML
地震数据的
Demo
例子。
2.2 SAX方式介绍
SAX
(
Simple API for XML
)
是
基于
事件驱动的 XML
处理
模式,
主要是围绕着事件源以及事件处理器(或者叫监听器)来工作的。一个可以产生事件的对象被称为事件源,而可以针对事件产生响应的对象就被叫做事件处理器。事件源和事件处理器是通过在事件源中的事件处理器注册方法连接的。这样当事件源产生事件后
(比如碰到
XML元素的开始和结束等
)
,调用事件处理器
(由许多回调函数组成)
相应的处理方法,一个事件就获得了处理。当然在事件源调用事件处理器中特定方法的时候,会传递给事件处理器相应事件的状态信息
(即回调函数中的参数)
,这样事件处理器才能够根据事件信息来决定自己的行为。
其中常用的事件处理回调函数有用于文档处理的文档开始:
startDocument()
,文档结束:
endDocument
()
,
XML元素开始:
startElement(String uri, String localName, String qName,
Attributes attributes)
,
XML元素内容:
characters(
char
[] ch,
int
start,
int
length)
,
XML元素结束:
endElement(String uri, String localName, String qName)
,还有解析错误的回调函数
(
exception)
等。
在Android
系统中,提供了两种
SAX
解析的包,一种是原来
Java
SDK
就有的用于XML
处理的
API
(称为
JAXP:
Java API for XML Processing
,包含了
SAX和
DOM
两者相关的
API
),相关内容在包
javax.xml.parsers
中。还有一种是经过了
Android
SDK
包装了之后的sax
包,相关内容在包
android.sax
中。
这部分我们先来学习原来Java
SDK
就有的用SAX
方式处理
XML
的相关方法。在
javax.xml.parsers
包中,和
SAX
相关的为两个类:
SAX
解析器工厂
SAXParserFactory
和
SAX解析器
SAXParser
。
SAXParserFactory
有
set方法和
get
方法
可以设置和获取一些配置选项,其中最重要的是调用
newSAXParser()
创建解析器
SAXParser
类的实例。
SAXParser
类包装了底层的
SAX解析器(
org.xml.sax.XMLReader 的实例
),即
SAXParser
实例调用
parse方法进行
XML
解析时,实际上会调用底层具体的
org.xml.sax
包中的
XMLReader。
SAXParser
实例也可以通过调用
getXMLReader()方法获得底层的
XMLReader
实例,一旦获得该实例,就可以按
XMLReader
方式使用更一般和具体的
SAX
方法。
通过以上的介绍我们知道
org.xml.sax
包是底层具体的负责
SAX解析相关的内容,并且为上层
javax.xml.parsers包提供
SAX
解析器等相关调用。下面我们就具体介绍一下用
SAX
进行解析的步骤。
在SAX
接口中,事件源是
org.xml.sax
包中的
XMLReader
,它通过
parse()
方法来开始解析
XML
文档并根据文档内容产生事件。而事件处理器则是
org.xml.sax
包中的
ContentHandler,DTDHandler,ErrorHandler,
以及
EntityResolver
这四个接口。它们分别处理事件源在解析过程中产生的不同种类的事件(其中
主要的为
ContentHandler
,处理和文档内容相关的事件
)。 而事件源XMLReader
和这四个事件处理器的连接是通过在
XMLReader
中的相应的事件处理器注册方法
set***()
来完成的。
因此概况一下具体步骤为:
1.
实现一个或多个处理器接口(ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)
2.
创建一个XMLReader
类的实例
3.
在新的XMLReader
实例中通过大量的
set*****()
方法注册一个事件处理器的实例
4.
调用XMLReader
的
parse()
方法来处理文档
启动解析
以上部分的介绍是指使用
org.xml.sax包中提供的
SAX
解析的相关接口时的用法,但是一般常用并且比较方便的为使用
javax.xml.parsers
包提供的
SAX
工厂类
SAXParserFactory
创建
SAXParser
实例,
并且创建一个继承
org.xml.sax.helpers包中的
DefaultHandler
的类,用于实现具体的
SAX
事件的处理逻辑,
DefaultHandler
类提供了
SAX
中
ContentHandler,DTDHandler,ErrorHandler,以及
EntityResolver
这四个接口
的所有回调方法默认的空实现,因此我们继承这个类后可以只覆盖我们需要的回调函数即可。然后调用
SAXParser
实例的
parse方法进行解析,用来解析的
xml
数据的形式可以为
InputStreams, Files, URLs, and SAX InputSources
等四种形式。
实现步骤和上面类似:
1.
在继承DefaultHandler
的类里面重写需要的回调函数
2.
创建
SAXParser
实例
3.
SAXParser
实例
调用parse
方法启动解析
下面我们就用上面介绍的
Java SDK
中的SAX
方式来实现解析
XML
形式的地震数据的
Demo
例子。
三.
实例开发
我们要解析的为美国地质调查局USGS
提供的地震数据,
xml
数据地址为:
分别为1
天以内
2.5
级以上、
7
天内
2.5
级以上和
7
天内
5
级以上地震数据。
Xml数据的格式如下所示:
- xml version="1.0"?>
- <feed xmlns="" xmlns:georss="">
- <updated>2010-09-15T04:41:18Zupdated>
- <title>USGS M2.5+ Earthquakestitle>
- <subtitle>Real-time, worldwide earthquake list for the past daysubtitle>
- <link rel="self" href="" mce_href=""/>
- <link href="" mce_href=""/>
- <author><name>U.S. Geological Surveyname>author>
- <id>id>
- <icon>/favicon.icoicon>
- <entry>
- <id>urn:earthquake-usgs-gov:ak:10078833id>
- <title>M 2.9, Southern Alaskatitle>
- <updated>2010-09-15T04:14:03Zupdated>
- <link rel="alternate" type="text/html" href="recenteqsww/Quakes/ak10078833.php" mce_href="recenteqsww/Quakes/ak10078833.php"/>
- <summary type="html">
-
- summary>
- <georss:point>59.9094 -153.1241georss:point>
- <georss:elev>-98900georss:elev>
- <category label="Age" term="Past hour"/>
- entry>
- <entry>
-
- entry>
- feed>
下面我们就来完成用Java SAX
的方式解析以上
XML
形式的
USGS
地震数据的
Android
例子。
我们要完成的效果图如下图1
所示:
图1 ListView
列表显示的地震数据
解析完地震数据后用ListView
列表的方式显示每条地震的震级和地名信息。
新建一个Android
工程
AndroidXMLDemoSax
。
首先新建添加一个类EarthquakeEntry
,用来保存一条地震信息,类的内容为:
- public class EarthquakeEntry {
-
- private Date date;
- private Location location;
- private String place;
- private String link;
- private double magnitude;
- private double elev;
-
- public EarthquakeEntry()
- {
-
- }
- public EarthquakeEntry(Date _date, String _place, String _link, Location _location, double _magnitude, double _elev)
- {
- this.date = _date;
- this.location = _location;
- this.place = _place;
- this.link = _link;
- this.magnitude = _magnitude;
- this.elev = _elev;
- }
-
- public void setDate(Date _date)
- {
- this.date = _date;
- }
- public void setLocation(Location _location)
- {
- this.location = _location;
- }
- public void setPlace(String _place)
- {
- this.place = _place;
- }
- public void setLink(String _link)
- {
- this.link = _link;
- }
- public void setMagnitude(double _magnitude)
- {
- this.magnitude = _magnitude;
- }
- public void setElev(double _elev)
- {
- this.elev = _elev;
- }
-
- public Date getDate()
- {
- return this.date;
- }
- public Location getLocation()
- {
- return this.location;
- }
- public String getPlace()
- {
- return this.place;
- }
- public String getLink()
- {
- return this.link;
- }
- public double getMagnitude()
- {
- return this.magnitude;
- }
- public double getElev()
- {
- return this.elev;
- }
-
- @Override
- public String toString() {
- String earthquakeString = " M" + magnitude + " " + place;
- return earthquakeString;
- }
- }
比较简单,定义和一条地震内容对应的变量,并设置set
和
get
函数,并且重写
toString
()函数在
ListView
输出时用。
接着新建添加一个类SaxEarthquakeHandler
,继承
DefaultHandler
,完成解析地震数据的具体逻辑实现,内容如下:
- public class SaxEarthquakeHandler extends DefaultHandler{
-
- private String kEntryElementName = "entry";
- private String kLinkElementName = "link";
- private String kTitleElementName = "title";
- private String kUpdatedElementName = "updated";
- private String kGeoRSSPointElementName = "point";
- private String kGeoRSSElevElementName = "elev";
-
- private ArrayList earthquakeEntryList;
- private EarthquakeEntry earthquakeEntry;
- private StringBuilder currentDataBuilder;
- private Boolean startEntryElementFlag = false;
-
-
- public ArrayList getEarthquakeEntryList()
- {
- return this.earthquakeEntryList;
- }
-
- @Override
- public void startDocument() throws SAXException {
- super.startDocument();
-
- earthquakeEntryList = new ArrayList();
- currentDataBuilder = new StringBuilder();
- }
- @Override
- public void endDocument() throws SAXException {
-
- Log.v("Sax", "End");
- }
-
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes attributes) throws SAXException {
- super.startElement(uri, localName, qName, attributes);
-
- if(localName.equalsIgnoreCase(kEntryElementName))
- {
- earthquakeEntry = new EarthquakeEntry();
- startEntryElementFlag = true;
- }
- else if ((localName.equalsIgnoreCase(kLinkElementName))&&(startEntryElementFlag == true)) {
- String relAttribute = attributes.getValue("rel");
- if(relAttribute.equalsIgnoreCase("alternate"))
- {
- String webLink = attributes.getValue("href");
- earthquakeEntry.setLink(webLink);
- }
- }
- }
- @Override
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- super.characters(ch, start, length);
- currentDataBuilder.append(ch, start, length);
- }
- @Override
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- super.endElement(uri, localName, qName);
- if(startEntryElementFlag == true)
- {
- String currentData = currentDataBuilder.toString();
- if (localName.equalsIgnoreCase(kTitleElementName)) {
-
- String magnitudeString = currentData.split(" ")[1];
- int end = magnitudeString.length()-1;
- magnitudeString = magnitudeString.substring(0, end);
- double magnitude = Double.parseDouble(magnitudeString);
- earthquakeEntry.setMagnitude(magnitude);
-
- String place = currentData.split(",")[1].trim();
- earthquakeEntry.setPlace(place);
- }
- else if (localName.equalsIgnoreCase(kUpdatedElementName)) {
-
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- Date qdate = new GregorianCalendar(0,0,0).getTime();
- try {
- qdate = sdf.parse(currentData);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- earthquakeEntry.setDate(qdate);
- }
- else if (localName.equalsIgnoreCase(kGeoRSSPointElementName)) {
-
- String[] latLongitude = currentData.split(" ");
- Location location = new Location("dummyGPS");
- location.setLatitude(Double.parseDouble(latLongitude[0]));
- location.setLongitude(Double.parseDouble(latLongitude[1]));
- earthquakeEntry.setLocation(location);
- }
- else if (localName.equalsIgnoreCase(kGeoRSSElevElementName)) {
-
- double evel;
-
- try {
- evel = Double.parseDouble(currentData);
- } catch (Exception e) {
-
- e.printStackTrace();
- evel = 0;
- }
- Log.v("Sax_Elev", String.valueOf(evel));
- earthquakeEntry.setElev(evel);
- }
- else if(localName.equalsIgnoreCase(kEntryElementName))
- {
- earthquakeEntryList.add(earthquakeEntry);
- startEntryElementFlag = false;
- }
- currentDataBuilder.setLength(0);
- }
- }
- }
首先定义了
xml解析和保存解析结果等相关的一些变量,接着定义一个
get
函数
//获取解析的地震列表
public
ArrayList getEarthquakeEntryList()
{
return
this
.
earthquakeEntryList
;
}
返回解析的地震列表数据。
然后就是具体的xml
解析回调函数的重写实现,因为继承了类
DefaultHandler
,包含了
SAX
处理相关的
4
个
Handler
的所有回调函数的空实现,因此我们只需覆盖我们需要的回调函数。这是我们只重写了和文档内容处理相关的
ContentHandler
中
startDocument
,
endDocument
,
startElement
,
characters
,和
endElement
这几个回调函数,在实际应用中你可能还需添加错误处理或者其他的回调函数,只需重写覆盖即可。
回调函数中具体的处理逻辑和你的XML
数据的内容有关,以上实现了解析
USGS
的地震数据的处理逻辑,并添加了注释,如果有兴趣你可以对照着
USGS
的
XML
数据格式来看,这就不具体的讲了。
和地震相关的存储类和SAX
处理回调函数都完成了,接下来我们就来调用
SaxEarthquakeHandler
开始解析并显示数据。
先修改res/layout
下的
main.xml
为:
- xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android=""
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <ListView
- android:id="@+id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- />
- LinearLayout>
添加了一个用于显示的ListView
控件。
接着添加AndroidXMLDemoSax.java
文件的内容。
先添加获取xml
数据源的方法:
- private InputStream readEarthquakeDataFromInternet()
- {
-
- URL infoUrl = null;
- InputStream inStream = null;
- try {
- infoUrl = new URL("");
- URLConnection connection = infoUrl.openConnection();
- HttpURLConnection httpConnection = (HttpURLConnection)connection;
- int responseCode = httpConnection.getResponseCode();
- if(responseCode == HttpURLConnection.HTTP_OK)
- {
- inStream = httpConnection.getInputStream();
- }
- } catch (MalformedURLException e) {
-
- e.printStackTrace();
- } catch (IOException e) {
-
- e.printStackTrace();
- }
- return inStream;
- }
这是从
USGS的网站上读取
XML
数据并以
InputStream
的形式返回。因为需要用到联网功能,所以还得在
manifest.xml文件中添加联网权限:
<
uses-permission
android:name
=
"android.permission.INTERNET"
/>
这是联网获取XML
数据,也可以从本地读取
XML
数据,因为校园网会打不开
USGS
的网站,因此复制了一份
USGS
的地震数据以文件的形式保存在
assets
文件夹下,并使用如下函数读取:
- private InputStream readEarthquakeDataFromFile()
- {
-
- InputStream inStream = null;
- try {
- inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml");
- } catch (IOException e) {
-
- e.printStackTrace();
- }
- return inStream;
- }
有了XML
数据,就可以接下来进行解析了。
-
- InputStream earthquakeStream = readEarthquakeDataFromFile();
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- try {
- SAXParser parser = factory.newSAXParser();
- SaxEarthquakeHandler saxHandler = new SaxEarthquakeHandler();
- parser.parse(earthquakeStream, saxHandler);
-
- earthquakeEntryList = saxHandler.getEarthquakeEntryList();
- } catch (Exception e) {
-
- e.printStackTrace();
- }
最后添加定义相关变量,用把解析的数据用ListView
显示:
-
- ListView list;
- ArrayAdapter adapter;
- ArrayList earthquakeEntryList;
-
-
- list = (ListView)this.findViewById(R.id.list);
- adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, earthquakeEntryList);
- list.setAdapter(adapter);
完成了,可以保存运行看下效果。
以上使用的是javax.xml.parsers
包中的
SAXParser
来实现,
SAXParser
包装了底层的
XMLReader
,实现起来更加方便。但是我们也可以使用
XMLReader
来实现解析,下面就看下使用具体的
XMLReader
的方式,实现代码如下所示:
-
- XMLReader xmlReader = null;
-
-
-
-
-
-
-
-
-
- try {
-
- System.setProperty("org.xml.sax.driver","org.xmlpull.v1.sax2.Driver");
- xmlReader = XMLReaderFactory.createXMLReader();
- } catch (SAXException e) {
-
- e.printStackTrace();
- }
- SaxEarthquakeHandler saxHandler = new SaxEarthquakeHandler();
- xmlReader.setContentHandler(saxHandler);
-
- InputSource inSource = new InputSource(earthquakeStream);
- try {
- xmlReader.parse(inSource);
- } catch (Exception e) {
-
- e.printStackTrace();
- }
-
- earthquakeEntryList = saxHandler.getEarthquakeEntryList();
其中获取XMLReader
的方式有两种,一种是先获取
SAXParser
,然后通过
getXMLReader()
方法来获取,
xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
另一种是直接使用XMLReader
的工厂类
XMLReaderFactory
调用
createXMLReader()
方法创建,
xmlReader = XMLReaderFactory.
createXMLReader
();
但
不管是那种方式,只有获得了XMLReader
实例,接下来的操作都是一样的,先注册文档内容的事件处理器实例,
xmlReader.setContentHandler(saxHandler);
然后调用parse方法开始解析,
xmlReader.parse(inSource);
这样就用XMLReader进行了XML解析。
四.
总结
在这部分中我们首先学习了Android
上和
XML
解析相关的各个包的简单介绍,并且从有这么多个相关的包我们可以知道
Android
为
XML
的读写提供了相当大的支持。
然后具体学习了Android
上使用
SAX
方式解析
XML
的基本知识,使用
javax.xml.parsers
包中的
SAXParser
进行解析,及使用
org.xml.sax
包中的
XMLReader
进行解析两种方式分别的步骤,最后用解析
USGS
地震数据的
Demo
例子来实现介绍的内容。
这部分介绍的SAX
方式是属于原来
Java
就有的
XML
处理方式,同时,
Android
平台为了使解析
XML
还能更加方便和更加健壮,提供了
android.sax
包来进行
SAX
进行
XML
,这部分内容我们以后我们继续接着学习。
注:
参考资料:
http://www.ibm.com/developerworks/cn/xml/x-saxhandle/