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

您能夠經過點擊 右下角 的按鈕 來對文章內容做出評價, 也能夠經過左下方的 關注按鈕 來關注個人博客的最新動態。 

若是文章內容對您有幫助, 不要忘記點擊右下角的 推薦按鈕 來支持一下哦   

若是您對文章內容有任何疑問, 能夠經過評論或發郵件的方式聯繫我: 501395377@qq.com  / lzp501395377@gmail.com

若是須要轉載,請註明出處,謝謝!!

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

1、DOM解析XMLandroid

咱們首先來看看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文檔。dom

4.獲得了整個xml的Document對象後,咱們能夠得到其下面的各個元素節點(Element),一樣每一個元素節點可能又有多個屬性(Attribute),根據每一個元素節點咱們又能夠遍歷該元素節點下面的子節點等等。ide

在這裏要說明一下,在DOM的API當中,Node這個接口表明了咱們整個的DOM對象的最初數據類型,它表明了整個document樹中的每個單一節點。全部實現了Node這個接口的對象均可以處理其孩子節點,固然,並非每一個節點都有children,例如TextNode(文本節點),經過Node的 nodeName、nodeValue、attributes這三個屬性,咱們能夠很方便的獲得每一個Node節點的節點名字、節點的值、節點屬性等,下面咱們來看看不一樣類型的Node節點其nodeName、nodeValue、attributes三個屬性分別表明的是什麼:工具

Interface nodeName nodeValue attributes
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客戶端對其進行解析ui

<?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是用的比較少的。

2、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();
    }
}

3、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文檔。

相關文章
相關標籤/搜索