前言
前面已經介紹了Android平台下兩種解析XML的方法,SAX和PULL,這兩個均為事件驅動,以流的形式解析XML文檔。現在介紹一種新的方式DOM方式解析XML。
DOM是一種用於XML文檔對象模型,可用於直接訪問XML文檔的各個部位,在DOM中文檔被模擬成樹狀,其中XML語法的每一組成部分都表示一個節點,DOM允許用戶遍歷文檔樹,從父節點移動到子節點和兄弟節點。並利用某節點類型特有的屬性(元素具有屬性,文本節點具有文本數據)。
對於DOM而言,XML文檔中每一個成分都是一個節點。
DOM是這樣規定的:
- 整個文檔是一個文檔節點。
- 每一個XML標簽是一個元素節點。
- 包含在XML元素中的文本是一個文本節點。
- 每一個XML屬性是一個屬性節點。
DOM解析XML
DOM解析XML也是需要一個工廠類DocumentBuilderFactory,這一點和SAX、PULL類似。工廠類也是單例模式,沒有提供共有的構造函數,需要使用靜態的newInstance()方法獲得,並且需要工廠類來獲取DOM解析器實例,使用DocumentBuilderFactory.newDocumentBuilder()獲得的DocumentBuilder對象。
當獲得Document之后,就可以使用parse()解析XML文檔,parser多個重載,可以適用於不同的輸入。
下面介紹一下在DOM解析XML過程中,會碰上幾個對象,Element、NodeList、Node。說也不太好說清楚,畫個圖解釋一下更直觀。
從圖上可以看出,Element為一個元素,可以通過這個元素獲取到該元素的屬性值(Attribute),以及它的子節點的集合NodeList。而NodeList作為一個裝載平級節點的集合,可以通過NodeList獲得它內裝載的所有平級節點,可以通過索引獲取。對於Node,表示最終的節點,根據圖示說的,其實Jack文本也是一個文本節點(Node),Node可以獲取其節點名稱、其值、其屬性。所以它們三個是可以相互嵌套的,也不存在說誰一定要在誰的外側或是內層。
示例程序
既然已經說了那么多了,現在通過一個示例程序展示一下DOM解析XML。這是一個Android應用程序,為了模擬真實的環境,通過網絡讀取IIS上的一個靜態XML文件內容。直接上代碼,注釋已經寫的很清楚了。
IIS上的靜態XML文檔內容:
1 <?xml version="1.0" encoding="utf-8" ?> 2 - <persons> 3 - <person id="23"> 4 <name>Jack</name> 5 <age>21</age> 6 </person> 7 - <person id="20"> 8 <name>Dick</name> 9 <age>23</age> 10 </person> 11 </persons>
DomService,解析網絡傳輸來的XML文檔流:
package cn.bgxt.service; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import cn.bgxt.domain.Person; public class DomService { public DomService() { // TODO Auto-generated constructor stub } public static List<Person> getPersons(InputStream inputStream) throws Exception { List<Person> list=new ArrayList<Person>(); //獲取工廠對象,以及通過DOM工廠對象獲取DOMBuilder對象 DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=factory.newDocumentBuilder(); //解析XML輸入流,得到Document對象,表示一個XML文檔 Document document=builder.parse(inputStream); //獲得文檔中的次以及節點,persons Element element=document.getDocumentElement(); // 獲取Element下一級的person節點集合,以NodeList的形式存放。 NodeList personNodes=element.getElementsByTagName("person"); for(int i=0;i<personNodes.getLength();i++) { //循環獲取索引為i的person節點 Element personElement=(Element) personNodes.item(i); Person person=new Person(); //通過屬性名,獲取節點的屬性id person.setId(Integer.parseInt(personElement.getAttribute("id"))); //獲取索引i的person節點下的子節點集合 NodeList childNodes=personElement.getChildNodes(); for(int j=0;j<childNodes.getLength();j++) { //循環遍歷每個person下的子節點,如果判斷節點類型是ELEMENT_NODE,就可以依據節點名稱給予解析 if(childNodes.item(j).getNodeType()==Node.ELEMENT_NODE) { if("name".equals(childNodes.item(j).getNodeName())) { //因為文本也是一個文本節點, //所以這里讀取到name節點的時候, //通過getFirstChild()可以直接獲得name節點的下的第一個節點,就是name節點后的文本節點 //取其value值,就是文本的內容 person.setName(childNodes.item(j).getFirstChild().getNodeValue()); } else if("age".equals(childNodes.item(j).getNodeName())) { person.setAge(Integer.parseInt(childNodes.item(j).getFirstChild().getNodeValue())); } } } //把解析的person對象加入的list集合中 list.add(person); } return list; } }
從IIS服務器上獲取XML的方式,在另外一篇博客:HTTP協議。中已經介紹了,如果不明白可以去看看,這里就不介紹了。
Activity的布局就是一個按鈕,用於點擊出發解析事件,因為是Android4.0+的環境,需要無法在主線程中訪問網絡,需要使用到多線程的技術,並且不要網了給Android應用增加訪問網絡的權限。
1 package cn.bgxt.xmlfordom; 2 3 import java.io.InputStream; 4 import java.util.List; 5 import cn.bgxt.domain.Person; 6 import cn.bgxt.http.HttpUtils; 7 import cn.bgxt.service.DomService; 8 import android.os.Bundle; 9 import android.app.Activity; 10 import android.util.Log; 11 import android.view.Menu; 12 import android.view.View; 13 import android.widget.Button; 14 15 public class MainActivity extends Activity { 16 17 private Button button; 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 24 button=(Button)findViewById(R.id.btn); 25 button.setOnClickListener(new View.OnClickListener() { 26 27 @Override 28 public void onClick(View v) { 29 // Android 4.0+不能在主線程中訪問網絡 30 Thread thread=new Thread(new Runnable() { 31 @Override 32 public void run() { 33 try { 34 String path="http://192.168.1.107:1231/persons.xml"; 35 InputStream inputStream=HttpUtils.getXML(path); 36 List<Person> list=DomService.getPersons(inputStream); 37 for(Person person:list) 38 { 39 //以日志的形式打印對象內容 40 Log.i("DOM", person.toString()); 41 } 42 } catch (Exception e) { 43 // TODO: handle exception 44 } 45 } 46 47 }); 48 thread.start(); 49 } 50 51 }); 52 53 54 } 55 56 }
如果解析成功,可以在LogCat中查看到日志。
總結
現在已經講解的常用的Android應用中解析XML的方法,DOM和PULL、SAX不一樣,是文檔模型形式的,在解析的時候會把整個XML的內容都讀取到內存中,這樣對於移動設備而言,是很消耗內存的。而PULL以及SAX都是事件驅動,逐行去解析XML的內容,相對而言保證了解析速度又不會很損耗內存。所以Android應用中一般不推薦使用DOM解析XML,還是偏向於使用SAX、PULL。但是DOM也有它的優點,正因為它是把整個文檔都讀取到內存中了,可以指定需要查找的數據而無需遍歷所有的節點,對於內容比較少的XML而言,還是很方便的。所以解析XML的方法有很多,無法絕對的說明誰好誰壞,主要還是看需求設定的環境來取舍的。
請支持原創,尊重原創,轉載請注明出處。謝謝。