Android开发里,解析xml是很常见的场景了,其中SAX解析与PULL解析是两种比较常用的解析方式。
PULL方式解析xml的文章可点这里:Android XML解析之PULL解析 简单示例讲解
除此之外还有DOM解析,这个不太常用,因为它需一次性把xml文件加载到内存里,如果是大文件的话,很占内存,影响性能。
而SAX(Simple API for XML)解析是事件驱动的流式解析方式,并不是把xml全部加载到内存,而是一边读取一边解析,不可暂停或者倒退,直到结束。在解析过程中,会判断当前的节点及内容,而触发不同的方法被调用。我们需要重写这些回调方法,实现自己的处理。
SAX解析的优点:占用内存少,速度快,如果对性能要求比较高,推荐使用。
缺点:不像DOM解析一样将文档长期驻留在内存中,数据不是持久的。如果事件过后没有保存数据,数据就会丢失。
本篇文章,会用一个简单的例子示例下SAX解析的用法。
路径:res/raw/person_data.xml:
<data>
<person id="5">
<name>Jackname>
<age>25age>
person>
<person id="20">
<name>Rosename>
<age>80age>
person>
data>
我们的目标是把上面的xml数据(两个person)转换成一个Person列表。
Person类如下,和xml里的属性一一对应:
public class Person {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
下面先看看如何用SAX方式解析上面的xml:
我们需要继承自org.xml.sax.helpers.DefaultHandler类,实现一个解析处理器类。这个DefaultHandler类里比较重要的方法是下面几个,可以选择性的重写:
startDocument() —— 开始解析的时候被调用,可以在此方法里做一些初始化的准备工作。
startElement(String uri, String localName, String qName, Attributes attributes) —— 此方法在每次解析到一个开始节点的时候被调用。其中uri表示元素的命名空间;localName表示元素的本地名称(不带前缀);qName表示元素的限定名(带前缀);attrs表示元素的属性集合。
characters(char[] ch, int start, int length) —— 接收字符数据的通知。改方法用来处理在XML文件中读到的内容,第一个参数用来存放文件的内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度。使用newSreing(ch,start,length)就可以获取内容。这个方法被调用的次数最多,包括换行符也会被当做内容而传进这个方法里。
endElement(String uri, String localName, String qName) —— 在每次遇到结束节点的时候被调用。其中uri表示元素的命名空间;localName表示元素的本地名称(不带前缀);name表示元素的限定名(带前缀)。
endDocument() —— 解析结束的时候被调用。
下面看代码:
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;
public class MyContentHandler extends DefaultHandler {
private static final String TAG = "TAG";
private String currentNodeName; //当前的节点名称
private List<Person> personList;
private Person person; // 当前正在解析的Person
// 这里定义这个方法是为了把解析结果提供出去
public List<Person> getPersonList() {
return personList;
}
@Override
public void startDocument() throws SAXException {
Log.d(TAG, "startDocument() called");
personList = new ArrayList<>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
Log.d(TAG, "startElement() called with: uri = [" + uri + "], localName = [" + localName + "], qName = [" + qName + "]");
// 记录当前节点名称
currentNodeName = localName;
// 每次开始节点为“person”的话,就要开始解析一个新的person对象了
if (localName.equals("person")) {
person = new Person();
// 获取person节点的id属性
person.setId(Integer.parseInt(attributes.getValue("id")));
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
Log.d(TAG, "characters() called with: ch = [" + String.valueOf(ch) + "], start = [" + start + "], length = [" + length + "]");
if (currentNodeName != null) {
String data = new String(ch, start, length);
if (currentNodeName.equals("name")) {
person.setName(data);
} else if (currentNodeName.equals("age")) {
person.setAge(Integer.parseInt(data));
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
Log.d(TAG, "endElement() called with: uri = [" + uri + "], localName = [" + localName + "], qName = [" + qName + "]");
// 每次结束节点为“person”的话,则一个person对象的解析结束了
if (localName.equals("person")) {
personList.add(person);
person = null;
}
// 把当前节点重置
currentNodeName = null;
}
@Override
public void endDocument() throws SAXException {
Log.d(TAG, "endDocument() called");
}
}
接下来,我们就可以使用这个解析处理器进行解析了。
怎么用呢?如下面的方法:
public static void saxParseXml(Context context) {
try {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
XMLReader reader = saxParser.getXMLReader();
// 这里用到了我们自定义的解析执行器
MyContentHandler contentHandler = new MyContentHandler();
reader.setContentHandler(contentHandler);
// 读取xml文件,执行解析
reader.parse(new InputSource(context.getResources().openRawResource(R.raw.person_data)));
// 得到解析结果
List<Person> personList = contentHandler.getPersonList();
Log.d("TAG", "parse result: " + personList);
} catch (SAXException e){
} catch (ParserConfigurationException e) {
} catch (IOException e) {
}
}
调用上面的方法即可解析res/raw/person_data.xml文件并得到Person类列表了。
为了更直观地查看解析的流程及解析类的几个方法的参数,我都加上了log。
我是在某个按钮的点击事件里调用这个方法的,运行后打印如下。可对照看到各个方法的执行顺序。
D/TAG: startDocument() called
D/TAG: startElement() called with: uri = [], localName = [data], qName = [data]
characters() called with: ch = [
], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [4]
D/TAG: startElement() called with: uri = [], localName = [person], qName = [person]
characters() called with: ch = [
], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [8]
startElement() called with: uri = [], localName = [name], qName = [name]
characters() called with: ch = [Jack ], start = [0], length = [4]
D/TAG: endElement() called with: uri = [], localName = [name], qName = [name]
characters() called with: ch = [
ack ], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [8]
startElement() called with: uri = [], localName = [age], qName = [age]
characters() called with: ch = [25 ], start = [0], length = [2]
D/TAG: endElement() called with: uri = [], localName = [age], qName = [age]
characters() called with: ch = [
5 ], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [4]
endElement() called with: uri = [], localName = [person], qName = [person]
characters() called with: ch = [
], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [4]
startElement() called with: uri = [], localName = [person], qName = [person]
D/TAG: characters() called with: ch = [
], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [8]
startElement() called with: uri = [], localName = [name], qName = [name]
characters() called with: ch = [Rose ], start = [0], length = [4]
endElement() called with: uri = [], localName = [name], qName = [name]
characters() called with: ch = [
ose ], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [8]
startElement() called with: uri = [], localName = [age], qName = [age]
characters() called with: ch = [80 ], start = [0], length = [2]
endElement() called with: uri = [], localName = [age], qName = [age]
D/TAG: characters() called with: ch = [
0 ], start = [0], length = [1]
characters() called with: ch = [ ], start = [0], length = [4]
endElement() called with: uri = [], localName = [person], qName = [person]
characters() called with: ch = [
], start = [0], length = [1]
endElement() called with: uri = [], localName = [data], qName = [data]
D/TAG: endDocument() called
当然最后也得到了Person列表,打印如下:
parse result: [Person{id=5, name='Jack', age=25}, Person{id=20, name='Rose', age=80}]
这是PULL方式解析xml的文章链接,可以对照查看:
Android XML解析之PULL解析 简单示例讲解