Android XML解析之SAX解析 简单示例讲解

Android开发里,解析xml是很常见的场景了,其中SAX解析与PULL解析是两种比较常用的解析方式。

PULL方式解析xml的文章可点这里:Android XML解析之PULL解析 简单示例讲解

除此之外还有DOM解析,这个不太常用,因为它需一次性把xml文件加载到内存里,如果是大文件的话,很占内存,影响性能。

而SAX(Simple API for XML)解析是事件驱动的流式解析方式,并不是把xml全部加载到内存,而是一边读取一边解析,不可暂停或者倒退,直到结束。在解析过程中,会判断当前的节点及内容,而触发不同的方法被调用。我们需要重写这些回调方法,实现自己的处理。

SAX解析的优点:占用内存少,速度快,如果对性能要求比较高,推荐使用。
缺点:不像DOM解析一样将文档长期驻留在内存中,数据不是持久的。如果事件过后没有保存数据,数据就会丢失。

本篇文章,会用一个简单的例子示例下SAX解析的用法。

一、先准备一个简单的xml文件。

路径:res/raw/person_data.xml:

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <person id="5">
        <name>Jack</name>
        <age>25</age>
    </person>
    <person id="20">
        <name>Rose</name>
        <age>80</age>
    </person>
</data>

二、新建一个Person类。

我们的目标是把上面的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");
    }
}

接下来,我们就可以使用这个解析处理器进行解析了。

四、使用解析处理器进行xml解析。

怎么用呢?如下面的方法:

    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解析 简单示例讲解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章