一.简单介绍
XML(eXtensible Markup Language),即可扩展标记语言,是一种简单的数据存储语言,使用一系列简单的标记描述数据。XML 经常用作 Internet 上的一种数据格式,因此如果您希望通过 Internet 访问数据,则数据很有可能是 XML 格式,或者如果您希望发送数据给 Web 服务,那么您可能也需要发送 XML。简而言之,如果您的 OPhone/Android 应用程序将利用 Internet,那么您可能需要使用 XML。幸运的是,您可以采用多种方法在 OPhone/Android上使用 XML。这个学习系列就和大家一起学习一下在OPhone/Android平台上读写XML数据的多种方式。
而最近用业余时间做了一个《地震及时通》,其中就需要从网络上读取实时的XML形式的地震数据,因此我们在学习的同时将会完成读取XML形式的地震数据的Demo例子。
二.基础知识
2.1 整体介绍
OPhone/Android上对XML解析的支持是相当强大的,我们可以先来看一下OPhone/Android中和XML解析相关的包:
1. android.sax
这是OPhone/Android SDK提供的sax解析的包,因为可以对具体的Element设置监听进行处理,因此有更好鲁棒性。
2. android.util.Xml
这是android.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等。
以上就是OPhone/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),还有解析错误的回调函数error(SAXParseException exception)等。
在OPhone/Android系统中,提供了两种SAX解析的包,一种是原来Java SDK就有的用于XML处理的API(称为JAXP:Java API for XML Processing,包含了SAX和DOM两者相关的API),相关内容在包javax.xml.parsers中。还有一种是经过了OPhone/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数据地址为:
http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml
http://earthquake.usgs.gov/earthquakes/catalogs/7day-M2.5.xml
http://earthquake.usgs.gov/earthquakes/catalogs/7day-M5.xml
分别为1天以内2.5级以上、7天内2.5级以上和7天内5级以上地震数据。
Xml数据的格式如下所示:
- <?xml version="1.0"?>
- <feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss">
- <updated>2010-09-15T04:41:18Z</updated>
- <title>USGS M2.5+ Earthquakes</title>
- <subtitle>Real-time, worldwide earthquake list for the past day</subtitle>
- <link rel="self" href="http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"/>
- <link href="http://earthquake.usgs.gov/earthquakes/"/>
- <author><name>U.S. Geological Survey</name></author>
- <id>http://earthquake.usgs.gov/</id>
- <icon>/favicon.ico</icon>
- <entry>
- <id>urn:earthquake-usgs-gov:ak:10078833</id>
- <title>M 2.9, Southern Alaska</title>
- <updated>2010-09-15T04:14:03Z</updated>
- <link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10078833.php"/>
- <summary type="html">
- <![CDATA[<img src="http://earthquake.usgs.gov/images/globes/60_-155.jpg" alt="59.909°N 153.124°W" align="left" hspace="20" /><p>Wednesday, September 15, 2010 04:14:03 UTC<br>Tuesday, September 14, 2010 08:14:03 PM at epicenter</p><p><strong>Depth</strong>: 98.90 km (61.45 mi)</p>]]>
- </summary>
- <georss:point>59.9094 -153.1241</georss:point>
- <georss:elev>-98900</georss:elev>
- <category label="Age" term="Past hour"/>
- </entry>
- <entry>
- <!-- 还有entry条目,省略-->
- </entry>
- </feed>
下面我们就来完成用Java SAX的方式解析以上XML形式的USGS地震数据的OPhone/Android例子。
我们要完成的效果图如下图1所示:
图1 ListView列表显示的地震数据
解析完地震数据后用ListView列表的方式显示每条地震的震级和地名信息。
新建一个OPhone工程OPhoneXMLDemoSax。
首先新建添加一个类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;
- }
- //set方法
- 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;
- }
- //get方法
- 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{
- //xml解析用到的Tag
- private String kEntryElementName = "entry";
- private String kLinkElementName = "link";
- private String kTitleElementName = "title";
- private String kUpdatedElementName = "updated";
- private String kGeoRSSPointElementName = "point";
- private String kGeoRSSElevElementName = "elev";
- //用于保存xml解析获取的结果
- private ArrayList<EarthquakeEntry> earthquakeEntryList;
- private EarthquakeEntry earthquakeEntry;
- private StringBuilder currentDataBuilder;
- private Boolean startEntryElementFlag = false;
- //获取解析的地震列表
- public ArrayList<EarthquakeEntry> getEarthquakeEntryList()
- {
- return this.earthquakeEntryList;
- }
- //具体的xml解析回调函数
- @Override
- public void startDocument() throws SAXException {
- super.startDocument();
- //在开始解析时先创建实例
- earthquakeEntryList = new ArrayList<EarthquakeEntry>();
- currentDataBuilder = new StringBuilder();
- }
- @Override
- public void endDocument() throws SAXException {
- // TODO Auto-generated method stub
- Log.v("Sax", "End");
- }
- //解析每一个标签Tag
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes attributes) throws SAXException {
- super.startElement(uri, localName, qName, attributes);
- // Log.v("Sax_StartElement", localName);
- 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;
- //因为USGS数据有可能会输错,比如为"--10000",多了一个"-"号
- try {
- evel = Double.parseDouble(currentData);
- } catch (Exception e) {
- // TODO: handle exception
- 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<EarthquakeEntry> 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="http://schemas.android.com/apk/res/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控件。
接着添加OPhoneXMLDemoSax.java文件的内容。
先添加获取xml数据源的方法:
- private InputStream readEarthquakeDataFromInternet()
- {
- //从网络上获取实时地震数据
- URL infoUrl = null;
- InputStream inStream = null;
- try {
- infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml");
- URLConnection connection = infoUrl.openConnection();
- HttpURLConnection httpConnection = (HttpURLConnection)connection;
- int responseCode = httpConnection.getResponseCode();
- if(responseCode == HttpURLConnection.HTTP_OK)
- {
- inStream = httpConnection.getInputStream();
- }
- } catch (MalformedURLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- 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) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return inStream;
- }
有了XML数据,就可以接下来进行解析了。
- //获取地震数据流
- InputStream earthquakeStream = readEarthquakeDataFromFile();
- //Sax方式进行xml解析
- SAXParserFactory factory = SAXParserFactory.newInstance();
- try {
- SAXParser parser = factory.newSAXParser();
- SaxEarthquakeHandler saxHandler = new SaxEarthquakeHandler();
- parser.parse(earthquakeStream, saxHandler);
- //获取解析后的列表数据
- earthquakeEntryList = saxHandler.getEarthquakeEntryList();
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
最后添加定义相关变量,用把解析的数据用ListView显示:
- //定义显示的List相关变量
- ListView list;
- ArrayAdapter<EarthquakeEntry> adapter;
- ArrayList<EarthquakeEntry> earthquakeEntryList;
- //用ListView进行显示
- list = (ListView)this.findViewById(R.id.list);
- adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList);
- list.setAdapter(adapter);
完成了,可以保存运行看下效果。
以上使用的是javax.xml.parsers包中的SAXParser来实现,SAXParser包装了底层的XMLReader,实现起来更加方便。但是我们也可以使用XMLReader来实现解析,下面就看下使用具体的XMLReader的方式,实现代码如下所示:
- //使用org.xml.sax包中的XMLReader进行xml解析
- XMLReader xmlReader = null;
- //获取XMLReader的方式有两种
- //方式一:使用javax.xml.parsers.SAXParser的getXMLReader()方法
- // try {
- // xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
- // } catch (Exception e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // }
- //方式二:使用org.xml.sax.helpers.XMLReaderFactory的createXMLReader()方法
- try {
- //设置系统的"org.xml.sax.driver",XMLReaderFactory创建createXMLReader()时要用到
- System.setProperty("org.xml.sax.driver","org.xmlpull.v1.sax2.Driver");
- xmlReader = XMLReaderFactory.createXMLReader();
- } catch (SAXException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- SaxEarthquakeHandler saxHandler = new SaxEarthquakeHandler();
- xmlReader.setContentHandler(saxHandler);
- //XMLReader的输入为InputSource
- InputSource inSource = new InputSource(earthquakeStream);
- try {
- xmlReader.parse(inSource);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- 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解析。
四.总结
在这部分中我们首先学习了OPhone/Android上和XML解析相关的各个包的简单介绍,并且从有这么多个相关的包我们可以知道OPhone/Android为XML的读写提供了相当大的支持。
然后具体学习了OPhone/Android上使用SAX方式解析XML的基本知识,使用javax.xml.parsers包中的SAXParser进行解析,及使用org.xml.sax包中的XMLReader进行解析两种方式分别的步骤,最后用解析USGS地震数据的Demo例子来实现介绍的内容。
这部分介绍的SAX方式是属于原来Java就有的XML处理方式,同时,OPhone/Android平台为了使解析XML还能更加方便和更加健壮,提供了android.sax包来进行SAX进行XML,这部分内容我们以后我们继续接着学习。
注:
参考资料:http://www.ibm.com/developerworks/cn/xml/x-saxhandle/