目前的绝大多数移动端的应用都需要访问网络,既然需要访问网络就必须有一个自己的服务器,应用可以向服务器提交数据也可以从服务器上获取数据。这个时候就会出现一个问题,这些数据到是是以什么样的格式与服务器进行交流呢?随随便便的一段文本肯定不行,因此,一般我们会在网络上传输一些有着一定的结构和语义的的数据,当一方接收到这种数据后就可以按照相应的结构和语义进行解析,读取到相应的内容。
在网络上传输数据最常用的格式主要有两种:XML和JSON。最近公司在做项目的过程中需要大量的调用WebService接口,在调用接口后就需要对返回XML格式的数据进行解析,因此我在这里做一个数据解析的总结(此篇文章仅仅总结解析XML格式数据的方法,JSON的数据解析将在下一篇文章中总结),使自己加强印象的同时也希望能够帮助到看到这篇文章程序员朋友们。
(1)XML格式
XML(Extensible Markup Language)扩展标记语言,是用于标记电子文件使其具有结构性的标记语言,可以用来标记数据和定义数据类型,有着格式统一,可以跨平台容易与其它系统进行远程交互,数据共享方便等优点。但是XML文件庞大,文件格式复杂,传输占用带宽较多,在服务端和客户端都需要花费大量的代码来解析XML,导致在服务端和客户端的代码变得异常复杂且不容易维护,所以在开发移动端的应用是不建议使用XML格式的数据进行传输。
(2)JSON格式
JSON(JavaScript Object Notation)与XML格式相比较是一种轻量级的数据交换格式,JSON采用兼容性很高,完全独立于语言文本格式,可以在不同的平台之间进行数据交换。JSON数据格式比较简单,易于读写,格式都是压缩的,在传输的过程中占用带宽小并且它还支持多种语言,便于服务端和客户端的解析同时因为JSON格式能够直接为服务器端代码使用,大大简化了服务端和客户端的代码并且易于维护。
上面提到过JSON相对与XML来说是轻量级的数据,那么XML相对与JSON的重量级体现在哪呢?应该体现在解析上,XML目前比较常用的有三种解析方式:Pull解析,SAX解析和DOM解析。
(1)SAX解析
SAX解析非常适用于在移动设备的开发,它是树形结构解析,有着解析速度快并且占用内存少,但是它在解析的过程在会预加载整个文档,适用于文档较小的情况(SAX支持已经内置道JDK1.5中,不需要添加任何的jar文件)。SAX解析XML格式的数据采用的是事件驱动的方式,也就是说它不需要解析完整个文档,在被解析的文档中是按内容顺序进行解析的,在解析的过程中SAX会判断当前读到的字段是否合法,如果合法就会触发相应的事件。所谓的这些被触发的事件其实就是一些回调方法(callback),这些方法定义在ContentHandler接口中,下面是一些接口常用的方法。
startDocument()
这个方法是在开始XML解析的时候调用的,可以在其中做一些预处理工作。
startElement(String uri, String localName, String qName, Attributes attributes)
这个方法会在开始解析某个节点的时候调用,当读到一个开始节点的时候会触发这个方法,这个方法中一共有4个参数,uri参数是命名空间,localName参数记录着当前节点的名字,qName参数记录着带有命名空间节点的名字,通过attributes参数可以得到所有的属性名和相应的值。
characters(char[] ch, int start, int length)
这个方法用来处理在XML文件中读到的内容,这个方法一共有3个参数,ch参数为文件的字符串的内容,start参数是读到的字符串在这个数组中的起始位置,length参数是读到的字符串在这个数组中的长度,使用new String(ch, start, length)就可以获取内容。
endElement(String uri, String localName, String qName)
这个方法与startElement()方法相对应,在遇到结束节点标记的时候会调用这个方法。
endDocument()
这个方法与startDocument()方法相对应,在文档结束的时候调用这个方法,可以在其中做一些善后工作。
其中要特别注意的是SAX的一个重要的特点就是他是流处理模式的,当遇到一个节点的时候,它并不会记录下以前的所遇到的节点,也就是说在startElement()方法中你所知道的的信息就是当前节点的名字和属性,至于节点的嵌套结构,上层节点的名字,是否含有子节点等等其它与结构相关的信息,我们都是无法得知的,都需要在程序中去完成,这样就使得SAX解析在编程处理上没有DOM解析来的那么方便
SAX是基于事件驱动的,android的事件机制是基于回调函数的,在使用SAX解析XML文档的时候,在读取到文档开始和结束节点的时候就会回调一个事件,在读取到其它节点与内容的时候也会回调一个事件。在编写程序的过程中只要为SAX提供实现ContentHandler接口的类,那么该类就会拥有解析XML文档一系列的回调方法,因为ContentHandler是一个接口,再使用的时候会有些不方便,因此,SAX还为其制定了一个DefaultHandler类,它实现了ContentHandler的接口,但是里面所有的方法体都为空,在实现的过程中我们只需要继承这个类并且重写相对应的方法即可。
要进行解析的xml文件如下
解析数据的主要代码如下
package com.example.administrator.xmlparser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
/**
* Created by ChuPeng on 2016/9/12.
*/
public class ParserXMLWithSAX
{
private List goodsList;
public static List getXmlContentForSAX(String xml)
{
try
{
//创建一个解析XML的工厂对象
SAXParserFactory factory = SAXParserFactory.newInstance();
//创建一个解析XML的对象
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
//创建一个解析助手类
ParserXMLWithSAXHandler handler = new ParserXMLWithSAXHandler();
//将ParserXMLWithSAXHandler的实例设置到XMLReader中
xmlReader.setContentHandler(handler);
//开始解析
xmlReader.parse(new InputSource(new StringReader(xml)));
return handler.getList();
} catch (SAXException e)
{
e.printStackTrace();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
}
return null;
}
}
package com.example.administrator.xmlparser;
import android.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
/**
* Created by ChuPeng on 2016/9/19.
*/
public class ParserXMLWithSAXHandler extends DefaultHandler
{
//储存所有解析的元素
private List goodsList;
//储存正在解析的元素数据
private Goods goods;
private String nodeName;
public List getList()
{
return goodsList;
}
public void startDocument() throws SAXException
{
//初始化
goodsList = new ArrayList();
Log.d("SAX", "--startDocument()--");
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
nodeName = localName;
if("task".equals(nodeName))
{
goods = new Goods();
}
Log.d("SAX", "--startElement()--"+qName);
}
public void characters(char[] ch, int start, int length) throws SAXException
{
if("name".equals(nodeName))
{
goods.setName(String.copyValueOf(ch, start, length));
Log.d("SAX", "--characters()--" + nodeName + " " + goods.getName());
}
else if("refercode".equals(nodeName))
{
goods.setRefercode(String.copyValueOf(ch, start, length));
Log.d("SAX", "--characters()--" + nodeName + " " + goods.getRefercode());
}
else if("status".equals(nodeName))
{
goods.setStatus(String.copyValueOf(ch, start, length));
Log.d("SAX", "--characters()--" + nodeName + " " + goods.getStatus());
}
}
public void endElement(String uri, String localName, String qName) throws SAXException
{
if("task".equals(localName))
{
goodsList.add(goods);
Log.d("SAX", "--endElement()--" + localName +"将数据存储到集合中");
}
else
{
Log.d("SAX", "--endElement()--" + localName);
}
}
public void endDocument() throws SAXException
{
Log.d("SAX", "--endDocument()--");
}
}
在解析数据的过程中在每一步都加入了日志的打印,这样通过日志我们就可以更清楚的了解到SAX解析的顺序
(2)Pull解析
除了使用SAX解析XML文件,大家还可以使用Android内置的Pull解析器解析XML文件,Pull解析器的运行方式与SAX解析器相似,都是基于流操作文件,然后根据节点事件回调开发者编写的处理程序。因为是基于流的处理,因此Pull解析和SAX解析都比较节约内存资源,不会向DOM那样要把所有节点以对象树的形式展现在内存中,但是Pull解析比SAX解析更简明,而且不需要扫描完整个流。
虽然Pull解析与SAX解析都是基于流操作文件,但是二者解析器的工作方式有着较大的区别。它们之间的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能主动的控制事件的处理结束;而Pull解析器的工作方式则是允许你的应用程序代码主动从解析器中获取事件,正因为是主动的获取事件,因此可以通过在满足条件后不再获取事件来主动结束解析,这是它们主要的区别。
在使用场景方面,如果在一个XML文档中我们只需要前一部分数据,但是使用SAX解析或者DOM解析对整个文档进行解析,中间不能终止解析,尽管后一部分的数据其实不需要解析,这样就造成了资源的浪费,在这种场景上使用Pull解析正合适,在进行移动开发的过程中要在每一个方面斤斤计较,想办法少做无用功,这样才能给用户更好的体验。
Pull解析器也提供了类似于SAX解析的回调事件,开始文档START_DOCUMENT和结束文档END_DOCUMENT,开始节点START_TAG和结束节点END_TAG,遇到节点内容时需要调用nextText()方法进行提取内容。在使用Pull解析的过程中经常需要使用的类:
XmlPullParserFactory 工厂类,用来创建pull解析器
XmlPullParser Pull解析器
XmlPullParserException 异常
XmlSerializer 该类用来将指定的对象转化为XML文档
要进行解析的xml文件如下
public class Goods
{
private String name;
private String refercode;
private String status;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getRefercode()
{
return refercode;
}
public void setRefercode(String refercode)
{
this.refercode = refercode;
}
public String getStatus()
{
return status;
}
public void setStatus(String status)
{
this.status = status;
}
}
解析数据的方法
package com.example.administrator.xmlparser;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by ChuPeng on 2016/9/12.
*/
public class ParserXMLWithPull
{
private static List goodsList = null;
private static Goods goods = null;
private static InputStream is;
public static List getXmlContentForPull(String xml)
{
try
{
is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(is, "UTF-8");
int eventType = xmlPullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT)
{
String nodeName = xmlPullParser.getName();
switch(eventType)
{
case XmlPullParser.START_DOCUMENT:
{
//开始解析时的初始化
goodsList = new ArrayList();
Log.d("PULL", "开始解析");
}
break;
case XmlPullParser.START_TAG:
{
if("task".equals(nodeName))
{
goods = new Goods();
}
else if("name".equals(nodeName))
{
goods.setName(xmlPullParser.nextText());
Log.d("PULL", "--START_TAG()--" + "name");
}
else if("refercode".equals(nodeName))
{
goods.setRefercode(xmlPullParser.nextText());
Log.d("PULL", "--START_TAG()--" + "refercode");
}
else if("status".equals(nodeName))
{
goods.setStatus(xmlPullParser.nextText());
Log.d("PULL", "--START_TAG()--" + "status");
}
else
{
Log.d("PULL", "--START_TAG()");
}
}
break;
case XmlPullParser.END_TAG:
{
if("task".equals(nodeName))
{
goodsList.add(goods);
Log.d("PULL", "--END_TAG()--" + "status");
}
else
{
Log.d("PULL", "--END_TAG()");
}
}
break;
case XmlPullParser.END_DOCUMENT:
{
Log.d("PULL", "解析完成");
}
break;
default:
break;
}
eventType = xmlPullParser.next();
}
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
Log.d("PULL", "UnsupportedEncodingException");
}
catch (XmlPullParserException e)
{
e.printStackTrace();
Log.d("PULL", "XmlPullParserException");
}
catch (IOException e)
{
e.printStackTrace();
Log.d("PULL", "IOException");
}
return goodsList;
}
}
(3)DOM解析
最后一种常用的XML解析方法就是DOM解析,DOM解析和以上两种解析方法有所不同,它在解析XML数据的过程中是把整个XML文档当成一个对象来处理,会先把整个文档读入到内存中。由于是是基于树的结构,通常需要加载整个文档和构造DOM树,然后再开始工作。
由于它是基于信息层次的,因而DOM被认为是基于树或者基于对象的,DOM以及广义的基于树的处理具有以上两种解析方法所不具备的灵活性。由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构做出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理,在使用上DOM解析用起来也要简单的多。但是,在使用DOM解析XML数据的过程中,整个文档必须一次性的解析完成,由于整个文档都需要载入内存,相对于SAX解析和Pull解析来说占用内存的资源较多,不适合在移动开发中使用。
使用DOM解析的步骤
导入相关的类:
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder = factory.newDocumentBuilder();
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
Document doc = builder.parse(is);
Element rootElement = doc.getDocumentElement();
最后再进行节点的处理
解析数据的方法
package com.example.administrator.xmlparser;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Created by ChuPeng on 2016/9/12.
*/
public class ParserXMLWithDOM
{
public static List getXmlContentForDOM(String xml)
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
InputStream is = null;
Document doc = null;
NodeList goodsinfoProperties = null;
String resultMessage = null;
List goodsList = new ArrayList();
try
{
builder = factory.newDocumentBuilder();
is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
doc = builder.parse(is);
Element rootElement = doc.getDocumentElement();
NodeList properties = rootElement.getChildNodes();
int res = 0;
for (int j = 0; j < properties.getLength(); j++)
{
Node property = properties.item(j);
String nodeName = property.getNodeName();
if (nodeName.equals("rc"))
{
res = Integer.parseInt(property.getFirstChild().getNodeValue());
Log.d("DOM", nodeName + " " + res);
}
else if (nodeName.equals("rm"))
{
resultMessage = property.getFirstChild().getNodeValue();
Log.d("DOM", nodeName + " " + resultMessage);
}
else if(nodeName.equals("tasks"))
{
goodsinfoProperties = property.getChildNodes();
Log.d("DOM", nodeName);
}
}
if(res == 0 && goodsinfoProperties != null)
{
for(int i = 0; i < goodsinfoProperties.getLength(); i++ )
{
Element personElement = (Element) goodsinfoProperties.item(i);
NodeList childNodes = personElement.getChildNodes();
Goods goods = new Goods();
for(int j = 0; j < childNodes.getLength(); j++)
{
Node property = childNodes.item(j);
String nodeName = property.getNodeName();
if(nodeName.equals("name"))
{
if(property.getFirstChild() == null)
{
goods.setName("");
Log.d("DOM", nodeName + " " + goods.getName());
}
else
{
goods.setName(property.getFirstChild().getNodeValue());
Log.d("DOM", nodeName + " " + goods.getName());
}
}
else if(nodeName.equals("refercode"))
{
if(property.getFirstChild() == null)
{
goods.setRefercode("");
Log.d("DOM", nodeName + " " + goods.getRefercode());
}
else
{
goods.setRefercode(property.getFirstChild().getNodeValue());
Log.d("DOM", nodeName + " " + goods.getRefercode());
}
}
else if(nodeName.equals("status"))
{
if(property.getFirstChild() == null)
{
goods.setStatus("");
Log.d("DOM", nodeName + " " + goods.getStatus());
}
else
{
goods.setStatus(property.getFirstChild().getNodeValue());
Log.d("DOM", nodeName + " " + goods.getStatus());
}
}
}
goodsList.add(goods);
}
}
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return goodsList;
}
}
对于以上的三种解析方法各有优缺点,在我日常的使用习惯上比较倾向于PULL解析,因为SAX解析器操作起来过于笨重,而DOM不适合文档较大的情况下使用,我通常都是进行移动端的开发,更不适合使用占用内存较大的解析方法,唯有PULL解析使用起来轻巧灵活,速度快,占用内存小。其实在解析数据的过程中需要选择适合本项目的解析方法即可。
以上Demo的源代码地址:点击打开链接