您可以通過點擊 右下角 的按鈕 來對文章內容作出評價, 也可以通過左下方的 關注按鈕 來關注我的博客的最新動態。
如果文章內容對您有幫助, 不要忘記點擊右下角的 推薦按鈕 來支持一下哦
如果您對文章內容有任何疑問, 可以通過評論或發郵件的方式聯系我: 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三個屬性分別代表的是什么:
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客戶端對其進行解析
<?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文檔。