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解析 簡單示例講解

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