Android系列--DOM、SAX、Pull解析XML

xiaoluo501395377發表於2013-11-27
您可以通過點選 右下角 的按鈕 來對文章內容作出評價, 也可以通過左下方的 關注按鈕 來關注我的部落格的最新動態。 

如果文章內容對您有幫助, 不要忘記點選右下角的 推薦按鈕 來支援一下哦   

如果您對文章內容有任何疑問, 可以通過評論或發郵件的方式聯絡我: 501395377@qq.com  / lzp501395377@gmail.com

如果需要轉載,請註明出處,謝謝!!

本篇隨筆將詳細講解如何在Android當中解析伺服器端傳過來的XML資料,這裡將會介紹解析xml資料格式的三種方式,分別是DOM、SAX以及PULL。

一、DOM解析XML

我們首先來看看DOM(Document Object Model)這種方式解析xml,通過DOM解析xml在j2ee開發中非常的常見,它將整個xml看成是一個樹狀的結構,在解析的時候,會將整個xml檔案載入到我們的記憶體當中,然後通過DOM提供的API來對我們的xml資料進行解析,這種方式解析xml非常的方便,並且我們可以通過某個節點訪問到其兄弟或者是父類、子類節點。那麼通過DOM來解析xml的步驟是怎樣的呢?

1.首先通過DocumentBuilderFactory這個類來構建一個解析工廠類,通過newInstance()的方法可以得到一個DocumentBuilderFactory的物件。

2.通過上面的這個工廠類建立一個DocumentBuilder的物件,這個類就是用來對我們的xml文件進行解析,通過DocumentBuilderFactory的newDocumentBuilder()方法

3.通過建立好的 DocumentBuilder 物件的 parse(InputStream) 方法就可以解析我們的xml文件,然後返回的是一個Document的物件,這個Document物件代表的就是我們的整個xml文件。

4.得到了整個xml的Document物件後,我們可以獲得其下面的各個元素節點(Element),同樣每個元素節點可能又有多個屬性(Attribute),根據每個元素節點我們又可以遍歷該元素節點下面的子節點等等。

在這裡要說明一下,在DOM的API當中,Node這個介面代表了我們整個的DOM物件的最初資料型別,它代表了整個document樹中的每一個單一節點。所有實現了Node這個介面的物件都可以處理其孩子節點,當然,並不是每個節點都有children,例如TextNode(文字節點),通過Node的 nodeName、nodeValue、attributes這三個屬性,我們可以很方便的得到每個Node節點的節點名字、節點的值、節點屬性等,下面我們來看看不同型別的Node節點其nodeName、nodeValue、attributes三個屬性分別代表的是什麼:

InterfacenodeNamenodeValueattributes
Attr same as Attr.name same as Attr.value null
CDATASection "#cdata-section" same as CharacterData.data, the content of the CDATA Section null
Comment "#comment" same as CharacterData.data, the content of the comment null
Document "#document" null null
DocumentFragment "#document-fragment" null null
DocumentType same as DocumentType.name null null
Element same as Element.tagName null NamedNodeMap
Entity entity name null null
EntityReference name of entity referenced null null
Notation notation name null null
ProcessingInstruction same as ProcessingInstruction.target same as ProcessingInstruction.data null
Text "#text" same as CharacterData.data, the content of the text node null

其實我們用的最多的就是Element和Text,通過Element的nodeName屬性可以得到這個節點的標籤名,Text物件的nodeValue得到的就是元素節點的文字值內容,下面我們來看看一個通過DOM解析xml的一個程式碼案例:

首先我們構建一個xml的文件,這個文件等下會放在我們的伺服器上,通過http協議來得到這個xml文件,然後在我們的Android客戶端對其進行解析

<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id="1">
        <name>小羅</name>
        <age>21</age>
    </person>
    <person id="2">
        <name>android</name>
        <age>15</age>
    </person>
</persons>

下面我們來看看DOM解析伺服器端xml的工具類:

public class DomParserUtils
{
    public static List<Person> parserXmlByDom(InputStream inputStream) throws Exception
    {
        List<Person> persons = new ArrayList<Person>();
        //    得到一個DocumentBuilderFactory解析工廠類
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //    得到一個DocumentBuilder解析類
        DocumentBuilder builder = factory.newDocumentBuilder();
        //    接收一個xml的字串來解析xml,Document代表整個xml文件
        Document document = builder.parse(inputStream);
        //    得到xml文件的根元素節點
        Element personsElement = document.getDocumentElement();
        //    得到標籤為person的Node物件的集合NodeList
        NodeList nodeList = personsElement.getElementsByTagName("person");
        for(int i = 0; i < nodeList.getLength(); i++)
        {
            Person person = new Person();
            //    如果該Node是一個Element
            if(nodeList.item(i).getNodeType() == Document.ELEMENT_NODE)
            {
                Element personElement = (Element)nodeList.item(i);
                //    得到id的屬性值
                String id = personElement.getAttribute("id");
                person.setId(Integer.parseInt(id));
                
                //    得到person元素下的子元素
                NodeList childNodesList = personElement.getChildNodes();
                for(int j = 0; j < childNodesList.getLength(); j++)
                {
                    if(childNodesList.item(j).getNodeType() == Document.ELEMENT_NODE)
                    {
                        //    解析到了person下面的name標籤
                        if("name".equals(childNodesList.item(j).getNodeName()))
                        {
                            //    得到name標籤的文字值
                            String name = childNodesList.item(j).getFirstChild().getNodeValue();
                            person.setName(name);
                        }
                        else if("address".equals(childNodesList.item(j).getNodeName()))
                        {
                            String age = childNodesList.item(j).getFirstChild().getNodeValue();
                            person.setAge(Integer.parseInt(age));
                        }
                    }
                }
                
                persons.add(person);
                person = null;
            }
        }
        return persons;
    }
}

通過DOM解析xml的好處就是,我們可以隨時訪問到某個節點的相鄰節點,並且對xml文件的插入也非常的方便,不好的地方就是,其會將整個xml文件載入到記憶體中,這樣會大大的佔用我們的記憶體資源,對於手機來說,記憶體資源是非常非常寶貴的,所以在手機當中,通過DOM這種方式來解析xml是用的比較少的。

二、SAX解析XML

SAX(Simple API for XML),接著我們來看看另一種解析xml的方式,通過sax來對xml文件進行解析。

SAX是一個解析速度快並且佔用記憶體少的xml解析器,非常適合用於Android等移動裝置。 SAX解析XML檔案採用的是事件驅動,也就是說,它並不需要解析完整個文件,在按內容順序解析文件的過程中,SAX會判斷當前讀到的字元是否合法XML語法中的某部分,如果符合就會觸發事件。所謂事件,其實就是一些回撥(callback)方法,這些方法(事件)定義在ContentHandler介面。下面是一些ContentHandler介面常用的方法:

startDocument()
當遇到文件的開頭的時候,呼叫這個方法,可以在其中做一些預處理的工作。

endDocument()
和上面的方法相對應,當文件結束的時候,呼叫這個方法,可以在其中做一些善後的工作。

startElement(String namespaceURI, String localName, String qName, Attributes atts)
當讀到一個開始標籤的時候,會觸發這個方法。namespaceURI就是名稱空間,localName是不帶名稱空間字首的標籤名,qName是帶名稱空間字首的標籤名。通過atts可以得到所有的屬性名和相應的值。要注意的是SAX中一個重要的特點就是它的流式處理,當遇到一個標籤的時候,它並不會紀錄下以前所碰到的標籤,也就是說,在startElement()方法中,所有你所知道的資訊,就是標籤的名字和屬性,至於標籤的巢狀結構,上層標籤的名字,是否有子元屬等等其它與結構相關的資訊,都是不得而知的,都需要你的程式來完成。這使得SAX在程式設計處理上沒有DOM來得那麼方便。

endElement(String uri, String localName, String name)
這個方法和上面的方法相對應,在遇到結束標籤的時候,呼叫這個方法。

characters(char[] ch, int start, int length)
這個方法用來處理在XML檔案中讀到的內容,第一個引數用於存放檔案的內容,後面兩個引數是讀到的字串在這個陣列中的起始位置和長度,使用new String(ch,start,length)就可以獲取內容。

上面提到了重要的一點,sax解析xml是基於事件流的處理方式的,因此每解析到一個標籤,它並不會記錄這個標籤之前的資訊,而我們只會知道當前這個表情的名字和它的屬性,至於標籤裡面的巢狀,上層標籤的名字這些都是無法知道的。

sax解析xml最重要的步驟就是定義一個我們自己的Handler處理類,我們可以讓其繼承 DefaultHandler 這個類,然後在裡面重寫其回撥方法,在這些回撥方法裡來做我們的xml解析

下面我們就通過一個例項來看看如果通過SAX來解析xml,首先定義一個我們自己的Handler類:

public class MyHandler extends DefaultHandler
{
    private List<Person> persons;
    private Person person;
    //    存放當前解析到的標籤名字
    private String currentTag;
    //    存放當前解析到的標籤的文字值
    private String currentValue;
    
    public List<Person> getPersons()
    {
        return persons;
    }
    
    //    當解析到文件開始時的回撥方法
    @Override
    public void startDocument() throws SAXException
    {
        persons = new ArrayList<Person>();
    }
    
    //    當解析到xml的標籤時的回撥方法
    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes attributes) throws SAXException
    {
        if("person".equals(qName))
        {
            person = new Person();
            //    得到當前元素的屬性值
            for(int i = 0; i < attributes.getLength(); i++)
            {
                if("id".equals(attributes.getQName(i)))
                {
                    person.setId(Integer.parseInt(attributes.getValue(i)));
                }
            }
        }
        //    設定當前的標籤名
        currentTag = qName;
    }
    
    //    當解析到xml的文字內容時的回撥方法
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException
    {
        //    得到當前的文字內容
        currentValue = new String(ch,start, length);
        //    當currentValue不為null、""以及換行時
        if(currentValue != null && !"".equals(currentValue) && !"\n".equals(currentValue))
        {
            //    判斷當前的currentTag是哪個標籤
            if("name".equals(currentTag))
            {
                person.setName(currentValue);
            }
            else if("age".equals(currentTag))
            {
                person.setAge(Integer.parseInt(currentValue));
            }
        }
        //    清空currentTag和currentValue
        currentTag = null;
        currentValue = null;
    }
    
    //    當解析到標籤的結束時的回撥方法
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException
    {
        if("person".equals(qName))
        {
            persons.add(person);
            person = null;
        }
    }
}

接著看看SAX解析xml的Util類:

public class SaxParserUtils
{
    public static List<Person> parserXmlBySax(InputStream inputStream) throws Exception
    {
        //    建立一個SAXParserFactory解析工廠類
        SAXParserFactory factory = SAXParserFactory.newInstance();
        //    例項化一個SAXParser解析類
        SAXParser parser = factory.newSAXParser();
        //    例項化我們的MyHandler類
        MyHandler myHandler = new MyHandler();
        //    根據我們自定義的Handler來解析xml文件
        parser.parse(inputStream, myHandler);

        return myHandler.getPersons();
    }
}

三、PULL解析XML

最後來介紹第三種解析xml的方式,pull。pull解析和sax解析類似,都是基於事件流的方式,在Android中自帶了pull解析的jar包,所以我們不需要匯入第三方的jar包了。

Pull解析器和SAX解析器的區別

Pull解析器和SAX解析器雖有區別但也有相似性。他們的區別為:SAX解析器的工作方式是自動將事件推入註冊的事件處理器進行處理,因此你不能控制事件的處理主動結束;

而Pull解析器的工作方式為允許你的應用程式程式碼主動從解析器中獲取事件,正因為是主動獲取事件,因此可以在滿足了需要的條件後不再獲取事件,結束解析。這是他們主要的區別。

而他們的相似性在執行方式上,Pull解析器也提供了類似SAX的事件(開始文件START_DOCUMENT和結束文件END_DOCUMENT,開始元素START_TAG和結束元素END_TAG,遇到元素內容TEXT等),但需要呼叫next() 方法提取它們(主動提取事件)。

Android系統中和Pull方式相關的包為org.xmlpull.v1,在這個包中提供了Pull解析器的工廠類XmlPullParserFactory和Pull解析器XmlPullParser,XmlPullParserFactory例項呼叫newPullParser方法建立XmlPullParser解析器例項,接著XmlPullParser例項就可以呼叫getEventType()和next()等方法依次主動提取事件,並根據提取的事件型別進行相應的邏輯處理。

下面我們就來通過一個程式碼來看看pull解析xml的步驟:

public class PullParserUtils
{
    public static List<Person> parserXmlByPull(InputStream inputStream) throws Exception
    {
        List<Person> persons = null;
        Person person = null;
        
        //    建立XmlPullParserFactory解析工廠
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        //    通過XmlPullParserFactory工廠類例項化一個XmlPullParser解析類
        XmlPullParser parser = factory.newPullParser();
        //    根據指定的編碼來解析xml文件
        parser.setInput(inputStream, "utf-8");
        
        //    得到當前的事件型別
        int eventType = parser.getEventType();
        //    只要沒有解析到xml的文件結束,就一直解析
        while(eventType != XmlPullParser.END_DOCUMENT)
        {
            switch (eventType)
            {
                //    解析到文件開始的時候
                case XmlPullParser.START_DOCUMENT:
                     persons = new ArrayList<Person>();
                break;
                //    解析到xml標籤的時候
                case XmlPullParser.START_TAG:
                     if("person".equals(parser.getName()))
                     {
                         person = new Person();
                         //    得到person元素的第一個屬性,也就是ID
                         person.setId(Integer.parseInt(parser.getAttributeValue(0)));
                     }
                     else if("name".equals(parser.getName()))
                     {
                         //    如果是name元素,則通過nextText()方法得到元素的值
                         person.setName(parser.nextText());
                     }
                     else if("age".equals(parser.getName()))
                     {
                         person.setAge(Integer.parseInt(parser.nextText()));
                     }
                break;
                //    解析到xml標籤結束的時候
                case XmlPullParser.END_TAG:
                     if("person".equals(parser.getName()))
                     {
                         persons.add(person);
                         person = null;
                     }
                break;
            }
            //    通過next()方法觸發下一個事件
            eventType = parser.next();
        }
        
        return persons;
    }
}

最後我們再編寫一個HttpUtils類來訪問我們的伺服器端的xml文件:

public class HttpUtils
{
    public static InputStream httpMethod(String path, String encode)
    {
        HttpClient httpClient = new DefaultHttpClient();
        
        try
        {
            HttpPost httpPost = new HttpPost(path);
            HttpResponse httpResponse = httpClient.execute(httpPost);
            if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
            {
                HttpEntity httpEntity = httpResponse.getEntity();
                return httpEntity.getContent();
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            httpClient.getConnectionManager().shutdown();
        }
        
        return null;
    }
}

最後來看看我們的Android應用程式的佈局檔案以及Activity類的程式碼:

public class MainActivity extends Activity
{
    private Button button;
    private Button button2;
    private Button button3;
    private final String PATH = "http://172.25.152.34:8080/httptest/person.xml";
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button = (Button)findViewById(R.id.button1);
        button2 = (Button)findViewById(R.id.button2);
        button3 = (Button)findViewById(R.id.button3);
        
        ButtonOnClickListener listener = new ButtonOnClickListener();
        button.setOnClickListener(listener);
        button2.setOnClickListener(listener);
        button3.setOnClickListener(listener);
    }
    
    class ButtonOnClickListener implements OnClickListener
    {
        @Override
        public void onClick(View v)
        {
            Button button = (Button)v;
            switch (button.getId())
            {
                case R.id.button1:
                    //    啟動一個新執行緒解析xml
                    class MyThread1 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = DomParserUtils.parserXmlByDom(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("dom --->>" + persons);
                        }
                    }
                    new MyThread1().start();
                break;
                case R.id.button2:
                    //    啟動一個新執行緒解析xml
                    class MyThread2 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = SaxParserUtils.parserXmlBySax(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("sax --->>" + persons);
                        }
                    }
                    new MyThread2().start();
                break;
                case R.id.button3:
                    //    啟動一個新執行緒解析xml
                    class MyThread3 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = PullParserUtils.parserXmlByPull(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("pull: --->>" + persons);
                        }
                    }
                    new MyThread3().start();
                break;
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

最後我們來看看控制檯的輸出:

 

 

總結:dom方式解析xml,比較簡單,並可以訪問兄弟元素,但是需要將整個xml文件載入到記憶體中,對於android裝置來說,不推薦使用dom的方式解析xml。

sax和pull都是基於事件驅動的xml解析器,在解析xml時並不會載入整個的xml文件,佔用記憶體較少,因此在android開發中建議使用sax或者pull來解析xml文件。

 

相關文章