Android 中 XML 資料解析詳解

Joey_Leaf發表於2016-09-09

XML初步

今天我們來學習另一種非常重要的資料交換格式-XML。XML(Extensible Markup Language的縮寫,意為可擴充套件的標記語言),它是一種元標記語言,即定義了用於定義其他特定領域有關語義的、結構化的標記語言,這些標記語言將文件分成許多部件並對這些部件加以標識。XML 文件定義方式有:文件型別定義(DTD)和XML Schema。DTD定義了文件的整體結構以及文件的語法,應用廣泛並有豐富工具支援。XML Schema用於定義管理資訊等更強大、更豐富的特徵。XML能夠更精確地宣告內容,方便跨越多種平臺的更有意義的搜尋結果。它提供了一種描述結構資料的格式,簡化了網路中資料交換和表示,使得程式碼、資料和表示分離,並作為資料交換的標準格式,因此它常被稱為智慧資料文件。

由於XML具有很強的擴充套件性,致使它需要很強的基礎規則來支援擴充套件,所以在編寫XML檔案時,我們應該嚴格遵守XML的語法規則,一般XML語法有如下規則:(1)起始和結束的標籤相匹配;(2)巢狀標籤不能相互巢狀;(3)區分大小寫。下面是給出了一個編寫錯誤的XML檔案以及對其的錯誤說明,如下:

本文只是對XML做了個簡單的介紹,要想學習更多有關XML知識,可以訪問如下網站:http://bbs.xml.org.cn/index.asp
XML在實際應用中比較廣泛,Android也不例外,作為承載資料的重要角色,如何讀寫XML稱為Android開發中一項重要的技能。

Android中XML資料解析使用例項

在Android開發中,較為常用的XML解析器有SAX解析器、DOM解析器和PULL解析器,下面我們將會一一學習如何使用這些XML解析器。那在介紹這幾種XML解析器過程中,我們依然是要通過一個例項來學習它們的實際開發方法,下面是我們Demo例項的程式列表清單,如下:

 圖1-1 客戶端

圖1-2 伺服器端

來整理下我們實現的Demo例項思路:客戶端通過網路請求讀取伺服器端的person.xml,person.xml檔案中的內容如下:

<?xml version="1.0" encoding="UTF-8"?>  
<persons>  
    <person id="1">  
        <name>Transformers First</name>  
        <height>7</height>  
        <imageurl>imagefile/transformers_one.png</imageurl>  
    </person>  
    <person id="2">  
        <name>Transformers second</name>  
        <height>4</height>  
        <imageurl>imagefile/transformers_two.png</imageurl>  
    </person>  
     <person id="3">  
        <name>Transformers third</name>  
        <height>8.5</height>  
        <imageurl>imagefile/transformers_three.png</imageurl>  
    </person>  
    <person id="4">  
        <name>Transformers fourth</name>  
        <height>14.5</height>  
        <imageurl>imagefile/transformers_four.png</imageurl>  
    </person>  
    <person id="5">  
        <name>Transformers fifth</name>  
        <height>27.5</height>  
        <imageurl>imagefile/transformers_five.png</imageurl>  
    </person>  
    <person id="6">  
        <name>Transformers Sixth</name>  
        <height>8.5</height>  
        <imageurl>imagefile/transformers_six.png</imageurl>  
    </person>  
    <person id="7">  
        <name>Transformers Seventh</name>  
        <height>5</height>  
        <imageurl>imagefile/transformers_seven.png</imageurl>  
    </person>  
    <person id="8">  
        <name>Transformers Eighth</name>  
        <height>12.5</height>  
        <imageurl>imagefile/transformers_eight.png</imageurl>  
    </person>  
</persons>

接著將獲取到的person.xml的檔案流資訊分別使用SAX、DOM和PULL解析方式解析成Java物件,然後將解析後獲取到的Java物件資訊以列表的形式展現在客戶端,思路很簡單吧。

好了,基本瞭解了Demo例項的整體思路後,接下來我們將學習如何具體實現它們。

SAX解析XML檔案例項

SAX(Simple For XML)是一種基於事件的解析器,它的核心是事件處理模式,它主要是圍繞事件源和事件處理器來工作的。當事件源產生事件後,會呼叫事件處理器中相應的方法來處理一個事件,那在事件源呼叫對應的方法時也會向對應的事件傳遞一些狀態資訊,以便我們根據其狀態資訊來決定自己的行為。

接下來我們將具體地學習一下SAX解析工作的主要原理:在讀取XML文件內容時,事件源順序地對文件進行掃描,當掃描到文件的開始與結束(Document)標籤、節點元素的開始與結束(Element)標籤時,直接呼叫對應的方法,並將狀態資訊以引數的形式傳遞到方法中,然後我們可以依據狀態資訊來執行相關的自定義操作。為了更好的理解SAX解析的工作原理,我們結合具體的程式碼來更深入的理解下,程式碼如下:

/** 
 * SAX解析類 
 * @author AndroidLeaf 
 */  
public class MyHandler extends DefaultHandler {  

    //當開始讀取文件標籤時呼叫該方法  
    @Override  
    public void startDocument() throws SAXException {  
        // TODO Auto-generated method stub  
        super.startDocument();  
    }  

    //當開始讀取節點元素標籤時呼叫該方法  
    @Override  
    public void startElement(String uri, String localName, String qName,  
            Attributes attributes) throws SAXException {  
        // TODO Auto-generated method stub  
        super.startElement(uri, localName, qName, attributes);  
        //do something  
    }  

    //當讀取節點元素的子類資訊時呼叫該方法  
    @Override  
    public void characters(char[] ch, int start, int length)  
            throws SAXException {  
        // TODO Auto-generated method stub  
        super.characters(ch, start, length);  
        //do something  
    }  

    //當結束讀取節點元素標籤時呼叫該方法  
    @Override  
    public void endElement(String uri, String localName, String qName)  
            throws SAXException {  
        // TODO Auto-generated method stub  
        super.endElement(uri, localName, qName);  
        //do something  
    }  

    //當結束讀取文件標籤時呼叫該方法  
    @Override  
    public void endDocument() throws SAXException {  
        // TODO Auto-generated method stub  
        super.endDocument();  
        //do something  
    }  

}

首先我們先認識一個重要的類–DefaultHandler,該類是XML解析介面(EntityResolver, DTDHandler, ContentHandler, ErrorHandler)的預設實現,在通常情況下,為應用程式擴充套件DefaultHandler並覆蓋相關的方法要比直接實現這些介面更容易。接著重寫startDocument(),startElement(),characters(),endElement和endDocument()五個方法,這些方法會在事件源(在org.xml.sax包中的XMLReader,通過parser()產生事件)讀取到不同的XML標籤所產生事件時呼叫。那我們開發時只要在這些方法中實現我們的自定義操作即可。下面總結羅列了一些使用SAX解析時常用的介面、類和方法:

事件處理器名稱 事件處理器處理的事件
ContentHandler

XML文件的開始與結束;

XML文件節點元素的開始與結束,接收字元資料,跳過實體,接收元素內容中可忽略的空白等。

DTDHandler

處理DTD解析時產生的相應事件

ErrorHandler

處理XML文件時產生的錯誤

EntityResolver

處理外部實體

方法名稱

方法說明

startDocument()

用於處理文件解析開始時間

startElement(String uri,String localName,String qNameAttributes attributes)

處理元素開始時間,從引數中可以獲取元素所在空間的URL,元素名稱,屬性列表等資訊。

characters(char[] ch,int start,int length)

處理元素的字元內容,從引數中可以獲得內容

endElement(String uri,String localName,String qName)

處理元素結束時間,從引數中可以獲取元素所在空間的URL,元素名稱等資訊。

endDocument()

用於處理文件解析的結束事件

基本瞭解完SAX解析工作原理及開發時用到的常用介面和類後,接下來我們來學習一下使用SAX解析XML的程式設計步驟,一般分為5個步驟,如下:

1、獲取建立一個SAX解析工廠例項;
2、呼叫工廠例項中的newSAXParser()方法建立SAXParser解析物件;
3、例項化CustomHandler(DefaultHandler的子類);
4、連線事件源物件XMLReader到事件處理類DefaultHandler中;
5、通過DefaultHandler返回我們需要的資料集合。

接著,我們按照這5個步驟來完成Demo例項解析person.xml的工作(person.xml的內容上面已經列出),解析的關鍵程式碼是在Demo例項工程中的XmlTools類中,具體程式碼如下:

/**--------------SAX解析XML-------------------*/  
    /** 
     * @param mInputStream 需要解析的person.xml的檔案流物件 
     * @param nodeName 節點名稱 
     * @return mList Person物件集合 
     */  
    public static ArrayList<Person> saxAnalysis(InputStream mInputStream,String nodeName){  
        //1、獲取建立一個SAX解析工廠例項  
        SAXParserFactory mSaxParserFactory = SAXParserFactory.newInstance();  
        try {  
            //2、呼叫工廠例項中的newSAXParser()方法建立SAXParser解析物件  
            SAXParser mSaxParser = mSaxParserFactory.newSAXParser();  
            //3、例項化CustomHandler(DefaultHandler的子類)  
            CustomHandler mHandler = new CustomHandler(nodeName);  
            /** 
             * 4、連線事件源物件XMLReader到事件處理類DefaultHandler中 
             * 檢視parse(InputStream is, DefaultHandler dh)方法原始碼如下: 
             * public void parse(InputSource is, DefaultHandler dh) 
             *      throws SAXException, IOException { 
             *      if (is == null) { 
             *           throw new IllegalArgumentException("InputSource cannot be null"); 
             *       } 
             *     // 獲取事件源XMLReader,並通過相應事件處理器註冊方法setXXXX()來完成的與ContentHander、DTDHander、ErrorHandler, 
             *     // 以及EntityResolver這4個介面的連線。 
             *       XMLReader reader = this.getXMLReader(); 
             *       if (dh != null) { 
             *           reader.setContentHandler(dh); 
             *           reader.setEntityResolver(dh); 
             *           reader.setErrorHandler(dh); 
             *           reader.setDTDHandler(dh); 
             *       } 
             *       reader.parse(is); 
             *   } 
             */  
            mSaxParser.parse(mInputStream, mHandler);  
            //5、通過DefaultHandler返回我們需要的資料集合  
            return mHandler.getList();  
        } catch (ParserConfigurationException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (SAXException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        return null;  
    }

事件處理器類CustomerHandler中的具體程式碼如下:

/** 
 * SAX解析類 
 * @author AndroidLeaf 
 */  
public class CustomHandler extends DefaultHandler {  

    //裝載所有解析完成的內容  
    List<HashMap<String, String>> mListMaps = null;  
    //裝載解析單個person節點的內容  
    HashMap<String, String> map = null;  
    //節點名稱  
    private String nodeName;  
    //當前解析的節點標記  
    private String currentTag;  
    //當前解析的節點值  
    private String currentValue;  

    public ArrayList<Person> getList(){  
        ArrayList<Person> mList = new ArrayList<Person>();  
        if(mListMaps != null && mListMaps.size() > 0){  
            for(int i = 0;i < mListMaps.size();i++){  
                Person mPerson = new Person();  
                HashMap<String, String> mHashMap = mListMaps.get(i);  
                mPerson.setId(Integer.parseInt(mHashMap.get("id")));  
                mPerson.setUserName(mHashMap.get("name"));  
                mPerson.setHeight(Float.parseFloat(mHashMap.get("height")));  
                mPerson.setImageUrl(mHashMap.get("imageurl"));  
                mList.add(mPerson);  
            }  
        }  
        return mList;  
    }  

    public CustomHandler(String nodeName){  
        this.nodeName = nodeName;  
    }  
    @Override  
    public void startDocument() throws SAXException {  
        // TODO Auto-generated method stub  
        super.startDocument();  
        mListMaps = new ArrayList<HashMap<String, String>>();  
    }  

    @Override  
    public void startElement(String uri, String localName, String qName,  
            Attributes attributes) throws SAXException {  
        // TODO Auto-generated method stub  
        if (qName.equals(nodeName)) {  
            map = new HashMap<String, String>();  
        }  
        if (map != null && attributes != null) {  
            for (int i = 0; i < attributes.getLength(); i++) {  
                map.put(attributes.getQName(i), attributes.getValue(i));  
            }  
        }  
        // 當前的解析的節點名稱  
        currentTag = qName;  
    }  

    @Override  
    public void characters(char[] ch, int start, int length)  
            throws SAXException {  
        // TODO Auto-generated method stub  
        if (map != null && currentTag != null) {  
            currentValue = new String(ch, start, length);  
            if (currentValue != null && !currentValue.equals("")  
                    && !currentValue.equals("\n")) {  
                map.put(currentTag, currentValue);  
            }  
        }  
        currentTag = null;  
        currentValue = null;  
        super.characters(ch, start, length);  
    }  

    @Override  
    public void endElement(String uri, String localName, String qName)  
            throws SAXException {  
        // TODO Auto-generated method stub  
        if (qName.equals(nodeName)) {  
            mListMaps.add(map);  
            map = null;  
        }  
        super.endElement(uri, localName, qName);  
    }  

    @Override  
    public void endDocument() throws SAXException {  
        // TODO Auto-generated method stub  
        super.endDocument();  

    }  
}

CustomerHandler通過不斷接收事件源傳遞過來的事件,進而執行相關解析工作並呼叫對應的方法,然後以引數的形式接收解析結果。為了更好的讓讀者理解CustomerHandler的解析過程,下面有一張展示解析person.xml檔案的流程圖,如下:

DOM解析XML檔案例項

DOM是基於樹形結構的的節點或資訊片段的集合,允許開發人員使用DOM API遍歷XML樹、檢索所需資料。分析該結構通常需要載入整個文件和構造樹形結構,然後才可以檢索和更新節點資訊。Android完全支援DOM 解析。利用DOM中的物件,可以對XML文件進行讀取、搜尋、修改、新增和刪除等操作。

DOM的工作原理:使用DOM對XML檔案進行操作時,首先要解析檔案,將檔案分為獨立的元素、屬性和註釋等,然後以節點樹的形式在記憶體中對XML檔案進行表示,就可以通過節點樹訪問文件的內容,並根據需要修改文件——這就是DOM的工作原理。DOM實現時首先為XML文件的解析定義一組介面,解析器讀入整個文件,然後構造一個駐留記憶體的樹結構,這樣程式碼就可以使用DOM介面來操作整個樹結構。由於DOM在記憶體中以樹形結構存放,因此檢索和更新效率會更高。但是對於特別大的文件,解析和載入整個文件將會很耗資源。 當然,如果XML檔案的內容比較小,採用DOM是可行的。下面羅列了一些使用DOM解析時常用的介面和類,如下:

介面或類名稱 介面或類說明
Document 該介面定義分析並建立DOM文件的一系列方法,它是文件樹的根,是操作DOM的基礎。
Element 該介面繼承Node介面,提供了獲取、修改XML元素名字和屬性的方法
Node 該介面提供處理並獲取節點和子節點值的方法
NodeList 提供獲得子節點個數和當前節點的方法。這樣就可以迭代地訪問各個節點
DOMParser 該類是Apache的Xerces中的DOM解析器類,可直接解析XML。

接下來我們學習一下使用DOM解析XML的程式設計步驟,一般分為6個步驟,如下:

1、建立文件物件工廠例項;
2、呼叫DocumentBuilderFactory中的newDocumentBuilder()方法建立文件物件構造器;
3、將檔案流解析成XML文件物件;
4、使用mDocument文件物件得到文件根節點;
5、根據名稱獲取根節點中的子節點列表;
6 、獲取子節點列表中需要讀取的節點資訊。

然後,我們按照這6個步驟來完成Demo例項解析person.xml的工作(person.xml的內容上面已經列出),解析的關鍵程式碼是在Demo例項工程中的XmlTools類中,具體程式碼如下:

/**--------------DOM解析XML-------------------*/  
    /** 
     * @param mInputStream 需要解析的person.xml的檔案流物件 
     * @return mList Person物件集合 
     */  
    public static ArrayList<Person> domAnalysis(InputStream mInputStream){  
        ArrayList<Person> mList = new ArrayList<Person>();  

        //1、建立文件物件工廠例項  
        DocumentBuilderFactory mDocumentBuilderFactory = DocumentBuilderFactory.newInstance();  
        try {  
            //2、呼叫DocumentBuilderFactory中的newDocumentBuilder()方法建立文件物件構造器  
            DocumentBuilder mDocumentBuilder = mDocumentBuilderFactory.newDocumentBuilder();  
            //3、將檔案流解析成XML文件物件  
            Document mDocument = mDocumentBuilder.parse(mInputStream);  
            //4、使用mDocument文件物件得到文件根節點  
            Element mElement = mDocument.getDocumentElement();  
            //5、根據名稱獲取根節點中的子節點列表  
            NodeList mNodeList =  mElement.getElementsByTagName("person");  
            //6 、獲取子節點列表中需要讀取的節點資訊  
            for(int i = 0 ;i < mNodeList.getLength();i++){  
                Person mPerson = new Person();  
                Element personElement = (Element) mNodeList.item(i);  
                //獲取person節點中的屬性  
                if(personElement.hasAttributes()){  
                    mPerson.setId(Integer.parseInt(personElement.getAttribute("id")));  
                }  
                if(personElement.hasChildNodes()){  
                //獲取person節點的子節點列表  
                 NodeList mNodeList2 = personElement.getChildNodes();  
                 //遍歷子節點列表並賦值  
                 for(int j = 0;j < mNodeList2.getLength();j++){  
                        Node mNodeChild = mNodeList2.item(j);  
                        if(mNodeChild.getNodeType() == Node.ELEMENT_NODE){  
                            if("name".equals(mNodeChild.getNodeName())){  
                                mPerson.setUserName(mNodeChild.getFirstChild().getNodeValue());  
                            }else if("height".equals(mNodeChild.getNodeName())){  
                                mPerson.setHeight(Float.parseFloat(mNodeChild.getFirstChild().getNodeValue()));  
                            }else if("imageurl".equals(mNodeChild.getNodeName())){  
                                mPerson.setImageUrl(mNodeChild.getFirstChild().getNodeValue());  
                            }  
                        }  
                    }  
                }  
                mList.add(mPerson);  
            }  

        } catch (ParserConfigurationException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (SAXException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        return mList;  
    }

PULL解析XML檔案例項

PULL的解析方式與SAX解析類似,都是基於事件的模式。不同的是,在PULL解析過程中返回的是數字,且我們需要自己獲取產生的事件然後做相應的操作,而不像SAX那樣由處理器觸發一種事件的方法,執行我們的程式碼。PULL 的工作原理:XML pull提供了開始元素和結束元素。當某個元素開始時,我們可以呼叫parser.nextText從XML文件中提取所有字元資料。當解釋到一個文件結束時,自動生成EndDocument事件。下面羅列了一些使用PULL解析時常用的介面、類和方法:

介面和類名稱 介面和類說明
XmlPullParser XML Pull解析介面,該介面定義瞭解析功能
XmlSerializer 它是一個介面,定義了XML資訊集的序列
XmlPullParserFactory XML PULL解析工廠類,用於建立XML Pull解析器
XmlPullParserException 丟擲單一的XML pull解析器相關的錯誤
方法名 方法說明
getEventType() 該方法用於獲取當前解析到的事件型別
nextText() 提取當前節點元素的字元資料
next() 獲取下一個節點元素的型別
getName() 獲取當前節點元素的名稱
getAttributeCount() 獲取當前節點屬性的數量
XmlPullParser.START_DOCUMENT 文件開始解析型別
XmlPullParser.END_DOCUMENT 文件結束解析型別
XmlPullParser.START_TAG 節點開始解析型別
XmlPullParser.END_TAG 節點結束解析型別
XmlPullParser.TEXT 文字解析型別

接下來我們將學習使用PULL解析XML的程式設計步驟,一般分為5個步驟,如下:

1、獲取PULL解析工廠例項物件;
2、使用XmlPullParserFactory的newPullParser()方法例項化PULL解析例項物件;
3、設定需解析的XML檔案流和字元編碼;
4、獲取事件解析型別;
5、迴圈遍歷解析,當文件解析結束時結束迴圈;

然後,我們按照這5個步驟來完成Demo例項解析person.xml的工作(person.xml的內容上面已經列出),解析的關鍵程式碼是在Demo例項工程中的XmlTools類中,具體程式碼如下:

/**--------------PULL解析XML-------------------*/  
    /** 
     * @param mInputStream 需要解析的person.xml的檔案流物件 
     * @param encode 設定字元編碼 
     * @return mList Person物件集合 
     */  
    public static ArrayList<Person> PullAnalysis(InputStream mInputStream,String encode){  
        ArrayList<Person> mList = null;  
        Person mPerson = null;  
        try {  
            //1、獲取PULL解析工廠例項物件  
            XmlPullParserFactory mXmlPullParserFactory = XmlPullParserFactory.newInstance();  
            //2、使用XmlPullParserFactory的newPullParser()方法例項化PULL解析例項物件  
            XmlPullParser mXmlPullParser = mXmlPullParserFactory.newPullParser();  
            //3、設定需解析的XML檔案流和字元編碼  
            mXmlPullParser.setInput(mInputStream, encode);  
            //4、獲取事件解析型別  
            int eventType = mXmlPullParser.getEventType();  
            //5、迴圈遍歷解析,當文件解析結束時結束迴圈  
            while(eventType != XmlPullParser.END_DOCUMENT){  
                switch (eventType) {  
                //開始解析文件  
                case XmlPullParser.START_DOCUMENT:  
                    mList = new ArrayList<Person>();  
                    break;  
                //開始解析節點  
                case XmlPullParser.START_TAG:  
                    if("person".equals(mXmlPullParser.getName())){  
                        mPerson = new Person();  
                        //獲取該節點中的屬性的數量  
                        int attributeNumber = mXmlPullParser.getAttributeCount();  
                        if(attributeNumber > 0){  
                            //獲取屬性值  
                            mPerson.setId(Integer.parseInt(mXmlPullParser.getAttributeValue(0)));  
                        }  
                    }else if("name".equals(mXmlPullParser.getName())){  
                        //獲取該節點的內容  
                        mPerson.setUserName(mXmlPullParser.nextText());  
                    }else if("height".equals(mXmlPullParser.getName())){  
                        mPerson.setHeight(Float.parseFloat(mXmlPullParser.nextText()));  
                    }else if("imageurl".equals(mXmlPullParser.getName())){  
                        mPerson.setImageUrl(mXmlPullParser.nextText());  
                    }  
                    break;  
                //解析節點結束  
                case XmlPullParser.END_TAG:  
                    if("person".equals(mXmlPullParser.getName())){  
                        mList.add(mPerson);  
                        mPerson = null;  
                    }  
                    break;  
                default:  
                    break;  
                }  
                eventType = mXmlPullParser.next();  
            }  
        } catch (XmlPullParserException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        return mList;  
    }

現在我麼已經分別使用SAX、DOM和PULL解析器解析person.xml的檔案流後,結果返回Person物件的集合,我們的解析工作便完成了,接下來需要做的就是將解析結果以列表的方式展現在客戶端介面上。那首先我們先來實現一下從網路服務端獲取資料的網路訪問程式碼,在工程中主要包含HttpRequest和MyAsynctask兩個類,前者主要功能是執行網路請求,後者是進行非同步請求的幫助類。首先看HttpRequest類的程式碼,如下:

/** 
 * 網路訪問類 
 * @author AndroidLeaf 
 */  
public class HttpRequest {  
    /** 
     * @param urlStr 請求的Url 
     * @return InputStream 返回請求的流資料 
     */  
    public static InputStream getInputStreamFromNetWork(String urlStr)  
    {  
        URL mUrl = null;  
        HttpURLConnection  mConnection= null;  
        InputStream mInputStream = null;  
        try {  
            mUrl = new URL(urlStr);  
            mConnection = (HttpURLConnection)mUrl.openConnection();  
            mConnection.setDoOutput(true);  
            mConnection.setDoInput(true);  
            mConnection.setReadTimeout(15 * 1000);  
            mConnection.setConnectTimeout(15 * 1000);  
            mConnection.setRequestMethod("GET");  
            int responseCode = mConnection.getResponseCode();  
            if(responseCode == HttpURLConnection.HTTP_OK){  
                //獲取下載資源的大小  
                //contentLength = mConnection.getContentLength();  
                mInputStream = mConnection.getInputStream();  
                return mInputStream;  
            }         
        } catch (IOException e) {  
            // TODO: handle exception  
        }  
        return null;  
    }  

    /** 
     * 得到圖片位元組流 陣列大小 
     * */  
    public static byte[] readStream(InputStream mInputStream){   
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();        
        byte[] buffer = new byte[2048];        
        int len = 0;        
        try {  
            while((len = mInputStream.read(buffer)) != -1){        
                outStream.write(buffer, 0, len);        
            }  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }finally{  
            try {  
                if(outStream != null){  
                    outStream.close();  
                }  
                if(mInputStream != null){  
                    mInputStream.close();  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }        
        }        
        return outStream.toByteArray();        
    }

接著實現MyAsynctask類的程式碼,如下:

/** 
 * 非同步請求工具類 
 * @author AndroidLeaf 
 */  
public class MyAsynctask extends AsyncTask<Object, Void, Object>  {  

    private ImageView mImageView;  
    private ImageCallBack mImageCallBack;  
    //請求型別,分為XML檔案請求和圖片下載請求  
    private int typeId;  
    //使用的XML解析型別ID  
    private int requestId;  

    /** 
      * 定義一個回撥,用於監聽網路請求,當請求結束,返回訪問結果 
      */  
     public HttpDownloadedListener mHttpDownloadedListener;  

     public interface HttpDownloadedListener{  
        public void onDownloaded(String result,int requestId);  
     }  

     public void setOnHttpDownloadedListener(HttpDownloadedListener mHttpDownloadedListener){  
         this.mHttpDownloadedListener = mHttpDownloadedListener;  
     }  

    public MyAsynctask(ImageView mImageView,ImageCallBack mImageCallBack,int requestId){  
        this.mImageView = mImageView;  
        this.mImageCallBack = mImageCallBack;  
        this.requestId = requestId;  
    }  

    @Override  
    protected void onPreExecute() {  
        // TODO Auto-generated method stub  
        super.onPreExecute();  
    }  

    @Override  
    protected Object doInBackground(Object... params) {  
        // TODO Auto-generated method stub  
        InputStream mInputStream = HttpRequest.getInputStreamFromNetWork((String)params[0]);  
        if(mInputStream != null){  
            switch ((int)params[1]) {  
            case Constants.TYPE_STR:  
                typeId = Constants.TYPE_STR;  
                return WriteIntoFile(mInputStream);  
            case Constants.TYPE_STREAM:  
                typeId = Constants.TYPE_STREAM;  
                return getBitmap(HttpRequest.readStream(mInputStream),  
                        200, 200);  
            default:  
                break;  
            }  
        }  
        return null;  
    }  

    @Override  
    protected void onPostExecute(Object result) {  
        // TODO Auto-generated method stub  
        if(result != null){  
            switch (typeId) {  
            case Constants.TYPE_STR:  
                mHttpDownloadedListener.onDownloaded((String)result,requestId);  
                break;  
            case Constants.TYPE_STREAM:  
                mImageCallBack.resultImage(mImageView,(Bitmap)result);  
                break;  
            default:  
                break;  
            }  
            typeId = -1;  
        }  
        super.onPostExecute(result);  
    }  

    public Bitmap getBitmap(byte[] bytes,int width,int height){  
        //獲取螢幕的寬和高    
        /**  
         * 為了計算縮放的比例,我們需要獲取整個圖片的尺寸,而不是圖片  
         * BitmapFactory.Options類中有一個布林型變數inJustDecodeBounds,將其設定為true  
         * 這樣,我們獲取到的就是圖片的尺寸,而不用載入圖片了。  
         * 當我們設定這個值的時候,我們接著就可以從BitmapFactory.Options的outWidth和outHeight中獲取到值  
         */    
        BitmapFactory.Options op = new BitmapFactory.Options();    
        op.inJustDecodeBounds = true;    
        Bitmap pic = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);  

        int wRatio = (int) Math.ceil(op.outWidth / (float) width); //計算寬度比例    
        int hRatio = (int) Math.ceil(op.outHeight / (float) height); //計算高度比例  

        /**  
         * 接下來,我們就需要判斷是否需要縮放以及到底對寬還是高進行縮放。  
         * 如果高和寬不是全都超出了螢幕,那麼無需縮放。  
         * 如果高和寬都超出了螢幕大小,則如何選擇縮放呢》  
         * 這需要判斷wRatio和hRatio的大小  
         * 大的一個將被縮放,因為縮放大的時,小的應該自動進行同比率縮放。  
         * 縮放使用的還是inSampleSize變數  
         */    
        if (wRatio > 1 && hRatio > 1) {    
            if (wRatio > hRatio) {    
                op.inSampleSize = wRatio;    
            } else {    
                op.inSampleSize = hRatio;    
            }    
        }    
        op.inJustDecodeBounds = false; //注意這裡,一定要設定為false,因為上面我們將其設定為true來獲取圖片尺寸了    
        pic = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);  
        return pic;  
    }  

    /** 
     * 將下載的XML檔案流儲存到手機指定的SDcard目錄下 
     * @param mInputStream 需要讀入的流 
     * @return String 返回儲存的XML檔案的路徑 
     */  
    public String WriteIntoFile(InputStream mInputStream){  
        if(isSDcard()){  
            try {  
                FileOutputStream mOutputStream = new FileOutputStream(new File(getFileName()));  
                int len = -1;  
                byte[] bytes = new byte[2048];  
                try {  
                    while((len = mInputStream.read(bytes)) != -1){  
                        mOutputStream.write(bytes, 0, len);  
                    }  
                } catch (IOException e) {  
                    // TODO Auto-generated catch block  
                    e.printStackTrace();  
                }finally{  
                    try {  
                        if(mOutputStream != null){  
                            mOutputStream.close();  
                        }  
                        if(mInputStream != null){  
                            mInputStream.close();  
                        }  
                    } catch (IOException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            } catch (FileNotFoundException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            return getFileName();  
        }  
        return null;  
    }  

    /** 
     * 檢測SDcard是否可用 
     * @return 
     */  
    public boolean isSDcard(){  
         if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  
             return true;  
         }else{  
             return false;  
         }  
    }  

    /** 
     * 獲取需要儲存的XML檔案的路徑 
     * @return String 路徑 
     */  
    public String getFileName(){  
        String path = Environment.getExternalStorageDirectory().getPath() +"/XMLFiles";  
        File mFile = new File(path);  
        if(!mFile.exists()){  
            mFile.mkdirs();  
        }  
        return mFile.getPath() + "/xmlfile.xml";  
    }

實現完網路請求的功能後,我們就可以從服務端獲取到person.xml的檔案流,然後再分別用SAX、DOM和PULL解析器將檔案流解析(在上面介紹這幾種解析器時已經實現瞭解析的程式碼,在工程中的XmlTools類中)成對應的Java物件集合,最後將Java物件集合以列表的形式展現在客戶端介面上,那接下來我們將實現該功能。在工程中主要包含MainActivity、MyAdapter和ImageCallBack類,首先實現MainActivity的程式碼,如下:

public class MainActivity extends ListActivity implements HttpDownloadedListener{  

    private ProgressDialog mProgressDialog;  
    //需要解析的節點名稱  
    private String nodeName = "person";  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_list);  
        //初始化資料  
        initData();  
    }  

    public void initData(){  
        mProgressDialog = new ProgressDialog(this);  
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);  
        mProgressDialog.setTitle("正在載入中.....");  
        mProgressDialog.show();  

        //首次進入介面是,預設使用SAX解析資料  
        downloadData(this,Constants.XML_PATH,Constants.REQUEST_PULL_TYPE,null,null,Constants.TYPE_STR);  
    }  

    @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        // TOdO Auto-generated method stub  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  

    @Override  
    public boolean onOptionsItemSelected(MenuItem item) {  
        // TOdO Auto-generated method stub  
        int requestId = -1;  
        switch (item.getItemId()) {  
        case R.id.sax:  
            requestId = Constants.REQUEST_SAX_TYPE;  
            break;  
        case R.id.dom:  
            requestId = Constants.REQUEST_DOM_TYPE;  
            break;  
        case R.id.pull:  
            requestId = Constants.REQUEST_PULL_TYPE;  
            break;  
        default:  
            break;  
        }  
        downloadData(this, Constants.XML_PATH, requestId, null, null, Constants.TYPE_STR);  
        mProgressDialog.show();  
        return super.onOptionsItemSelected(item);  
    }  

    @Override  
    public void onDownloaded(String result,int requestId) {  
        // TODO Auto-generated method stub  
        FileInputStream mFileInputStream = null;  
        try {  
             mFileInputStream = new FileInputStream(new File(result));  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        ArrayList<Person> mList = null;  
        switch (requestId) {  
        case Constants.REQUEST_SAX_TYPE:  
            mList = XmlTools.saxAnalysis(mFileInputStream,nodeName);  
            break;  
        case Constants.REQUEST_DOM_TYPE:  
            mList = XmlTools.domAnalysis(mFileInputStream);  
            break;  
        case Constants.REQUEST_PULL_TYPE:  
            mList = XmlTools.PullAnalysis(mFileInputStream,"UTF-8");  
            break;  
        default:  
            break;  
        }  

        MyAdapter myAdapter = new MyAdapter(this, mList);  
        setListAdapter(myAdapter);  

        if(mProgressDialog.isShowing()){  
            mProgressDialog.dismiss();  
        }  
    }  

    //執行網路下載程式碼  
    public static void downloadData(HttpDownloadedListener mDownloadedListener,String url,int requestId,ImageView mImageView,ImageCallBack mImageCallBack,int typeId){  
        MyAsynctask mAsynctask = new MyAsynctask(mImageView,mImageCallBack,requestId);  
        mAsynctask.setOnHttpDownloadedListener(mDownloadedListener);  
        mAsynctask.execute(url,typeId);  
    }  

}

然後再實現介面的介面卡類–MyAdapter,具體程式碼如下:

/** 
 * 資料介面展示的介面卡類 
 * @author AndroidLeaf 
 */  
public class MyAdapter extends BaseAdapter {  

    private Context mContext;  
    private ArrayList<Person> mList;  
    private Bitmap[] mBitmaps;  

    public MyAdapter(Context mContext,ArrayList<Person> mList){  
        this.mContext = mContext;  
        this.mList = mList;  
        mBitmaps = new Bitmap[mList.size()];  
    }  
    @Override  
    public int getCount() {  
        // TODO Auto-generated method stub  
        return mList.size();  
    }  

    @Override  
    public Object getItem(int position) {  
        // TODO Auto-generated method stub  
        return mList.get(position);  
    }  

    @Override  
    public long getItemId(int position) {  
        // TODO Auto-generated method stub  
        return position;  
    }  

    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        // TODO Auto-generated method stub  
        ViewHolder mHolder;  
        Person mPerson = mList.get(position);  
        if(convertView == null){  
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, null);  
            mHolder = new ViewHolder();  
            mHolder.mTextView_id = (TextView)convertView.findViewById(R.id.item_id);  
            mHolder.mTextView_name = (TextView)convertView.findViewById(R.id.item_name);  
            mHolder.mTextView_height = (TextView)convertView.findViewById(R.id.item_height);  
            mHolder.mImageView_image = (ImageView)convertView.findViewById(R.id.item_image);  
            //為Imageview設定TAG,作為每一個ImageView的唯一標識  
            mHolder.mImageView_image.setTag(mPerson.getImageUrl());  
            convertView.setTag(mHolder);  
        }else{  
            mHolder = (ViewHolder)convertView.getTag();  
        }  
        mHolder.mTextView_id.setText(String.valueOf(mPerson.getId()));  
        mHolder.mTextView_name.setText(mPerson.getUserName());  
        mHolder.mTextView_height.setText(String.valueOf(mPerson.getHeight()));  
        /** 
         * 解決非同步載入過程中Listview列表中圖片顯示錯位問題 
         */  
        //判斷當前位置的ImageView和是否為上次執行載入操作的ImageView,若false則重置上次載入的那個Imageview中的圖片資源  
        if(!mPerson.getImageUrl().equals(String.valueOf(mHolder.mImageView_image.getTag()))){  
            mHolder.mImageView_image.setImageResource(R.drawable.ic_launcher);  
        }  
        //重新為ImageView例項設定TAG  
        mHolder.mImageView_image.setTag(mPerson.getImageUrl());  
        if(mBitmaps[position] == null){  
            //執行非同步載入圖片操作  
            MainActivity.downloadData((HttpDownloadedListener)mContext, Constants.BASE_PATH + mPerson.getImageUrl(), -1,   
                    mHolder.mImageView_image, new MyImageCallBack(position,mPerson.getImageUrl()), Constants.TYPE_STREAM);  
        }else{  
            mHolder.mImageView_image.setImageBitmap(mBitmaps[position]);  
        }  

        return convertView;  
    }  

    class ViewHolder{  
        TextView mTextView_id;  
        TextView mTextView_name;  
        TextView mTextView_height;  
        ImageView mImageView_image;  
    }  

    class MyImageCallBack implements ImageCallBack{  
        int index = -1;  
        String imageUrl = null;  
        public MyImageCallBack(int index,String imageUrl){  
            this.index = index;  
            this.imageUrl = imageUrl;  
        }  

        @Override  
        public void resultImage(ImageView mImageView, Bitmap mBitmap) {  
            // TODO Auto-generated method stub  
            //判斷當前顯示的ImageView的URL是否與需要下載的圖片ImageView的URL相同  
            if(imageUrl.equals(String.valueOf(mImageView.getTag()))){  
                mBitmaps[index] = mBitmap;  
                mImageView.setImageBitmap(mBitmap);  
            }  
        }  
    }  
}

觀察上面的程式碼,在實現介面卡類時,由於我們需要非同步下載圖片,因此在圖片繫結和顯示時由於列表項焦點的不斷變換和圖片資料載入的延遲會導致ListView中的圖片顯示錯位的問題,為了解決該問題,我們採取對ImageView設定TAG來解決了圖片錯位問題,那要明白其中的原理,就必須對Listview載入item view列表項的實現機制比較清楚,由於該問題不是本文的重點,因此在此不便細講,有興趣的讀者可以學習本部落格的另一篇文章《Android非同步載入資料時ListView中圖片錯位問題解析》,希望對你有所幫助。在實現圖片非同步載入時,程式中還使用到了一個非常有用的介面–ImageCallBack,該介面主要作用是將非同步下載的圖片設定到對應的Imageview控制元件中,該介面的具體程式碼如下:

public interface ImageCallBack {  
    public void resultImage(ImageView mImageView,Bitmap mBitmap);  
}

當然,還有常量Constants類和Entity物件Person類。Constants類的具體的程式碼:

/** 
 * 網路請求的Url地址及一些常量 
 * @author AndroidLeaf 
 */  
public class Constants {  

    //基路徑  
    public static final String BASE_PATH = "http://10.0.2.2:8080/09_Android_XMLServer_Blog/";  
    //person.xml網路請求路徑  
    public static final String XML_PATH = BASE_PATH + "xmlfile/person.xml";  

    //使用SAX解析的標籤型別  
    public static final int REQUEST_SAX_TYPE = 0;  
    //使用DOM解析的標籤型別  
    public static final int REQUEST_DOM_TYPE = 1;  
    //使用PULL解析的標籤型別  
    public static final int REQUEST_PULL_TYPE = 2;  

    //請求person.xml檔案標籤  
    public static final int TYPE_STR = 1;  
    //請求圖片檔案標籤  
    public static final int TYPE_STREAM = 2;  

}

Person類的程式碼如下:

public class Person {  

    private int id;  
    private String userName;  
    private float height;  
    private String imageUrl;  

    public int getId() {  
        return id;  
    }  
    public void setId(int id) {  
        this.id = id;  
    }  
    public String getUserName() {  
        return userName;  
    }  
    public void setUserName(String userName) {  
        this.userName = userName;  
    }  
    public float getHeight() {  
        return height;  
    }  
    public void setHeight(float height) {  
        this.height = height;  
    }  
    public String getImageUrl() {  
        return imageUrl;  
    }  
    public void setImageUrl(String imageUrl) {  
        this.imageUrl = imageUrl;  
    }  

    @Override  
    public String toString() {  
        return "Person [id=" + id + ", userName=" + userName + ", height="  
                + height + ", imageUrl=" + imageUrl + "]";  
    }  
}

全部的編碼都已經完成,最後我們再Android模擬器上執行我們的Demo例項工程,執行及操作的效果圖如下:

SAX、DOM和PULL解析器的比較

SAX解析器的特點:SAX解析器解析速度快、高效,佔用記憶體少。但它的缺點是編碼實現比其它解析方式更復雜,對於只需解析較少數量的XML檔案時,使用SAX解析顯得實現程式碼比較臃腫。

DOM解析器的特點:由於DOM在記憶體中是以樹形結構存放的,那雖然檢索和更新效率比較高,但對於使用DOM來解析較大資料的XML檔案,將會消耗很大記憶體資源,這對於記憶體資源比較有限的手機裝置來講,是不太適合的。

PULL解析器的特點:PULL解析器小巧輕便,解析速度快,簡單易用,非常適合在Android移動裝置中使用,Android系統內部在解析各種XML時也是用PULL解析器,Android官方推薦開發者們使用Pull解析技術。Pull解析技術是第三方開發的開源技術,它同樣可以應用於JavaSE開發。

根據上面介紹的這些解析器的特點我們可在不同的開發情況下選擇不同的解析方式,比如說,當XML檔案資料較小時,可以選擇DOM解析,因為它將XML資料以樹形結構存放在記憶體中,在佔用不多的記憶體資源情況下檢索和更新效率比較高,當XML檔案資料較大時,可以選擇SAX和PULL解析,因為它們不需要將所有的XML檔案都載入到記憶體中,這樣對有限的Android記憶體更有效。SAX和PULL解析的不同之處是,PULL解析並不像SAX解析那樣監聽元素的結束,而是在開始處完成了大部分處理。這有利於提早讀取XML檔案,可以極大的減少解析時間,這種優化對於連線速度較漫的移動裝置而言尤為重要。對於XML文件資料較大但只需要文件的一部分時,XML PULL解析器則是更為有效的方法。

總結:本文學習了在Android開發中解析XML檔案的幾種常用解析方式,這幾種解析方式各有優缺點,我們應根據不同的開發需求選擇合適的解析方式。最後總結一下在Android解析XML所需掌握的主要知識點:(1)XML的特點以及結構組成形式,掌握如何編寫XML檔案;(2)瞭解SAX、PULL和DOM解析器的特點,並掌握在Android中使用這三種解析方式的使用;(3)比較三種解析器的特點,學會在不同情況下選擇合適的解析方式。

原始碼下載,請戳下面:GITHUB下載    CSDN下載

相關文章