一、XML的引入:
XML,可擴展標記語言 (Extensible Markup Language) ,用於標記電子文件使其具有結構性的標記語言,可以用來標記數據、定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言,這是百度百科的解釋。而XML是一種在Internet中傳輸數據的常見格式,它與HTML一樣,都是SGML(標准通用標記語言),無論你是需要通過Internet訪問數據,或者發送數據給Web服務,都可能需要用到XML的知識。恰恰Android應用程序需要和網絡交互,否則只是一款單機的無互動的應用程序,所以在Android應用程序開發的過程中使用到XML是很有必要的。
由於XML的擴展性強,致使它需要有穩定的基礎規則來支持擴展,該語法規則需要注意的是:
開始和結束標簽匹配。
嵌套標簽不能相互嵌套。
區分大小寫。
XML的結構解析如下:
節點
元素
屬性和屬性值
格式如下:
<標記名稱 屬性名1="屬性值1" 屬性名1="屬性值1" ……>內容</標記名稱>
二、Android中的XML解析的分類:
Android平台最大的優勢在於,上層應用基本可以利用Java編程語言開發,Java平台支持通過許多不同的方式來使用XML,並且大多數與XML相關的API已經在Android系統上得到了完全的支持。但是因為Android這個移動設備的局限性,一般僅考慮使用三種方式解析XML:
DOM,Document Object Model,文檔對象模型方式,解析完的XML將生成一個樹狀結構的對象。
SAX,simple API for Xml,以事件的形式通知程序,對XML進行解析。
XML PULL,類似於SAX方式,程序以拉取的方式對XML進行解析。
三、SAX解析介紹:
SAX是一個解析速度快並且占用內存少的xml解析器,非常適合用於Android等移動設備。 SAX解析XML文件采用的是事件驅動,也就是說,它並不需要解析完整個文檔,在按內容順序解析文檔的過程中,SAX會判斷當前讀到的字符是否合法XML語法中的某部分,如果符合就會觸發事件。所謂事件,其實就是一些回調(callback)方法,這些方法(事件)定義在ContentHandler接口。
使用SAX的優點:
因為SAX的優勢是流的方式處理,當遇到一個標簽的時候,並不會記錄下之前所碰到的標簽。也就是說,在每個節點讀取會觸發的startElement()方法中,所能知道的信息,僅僅是當前的簽名的名字和屬性,至於標簽嵌套的結構,上層標簽的名字,是否有子元素與其他結構相關的信息,都是不知道的。
使用SAX解析XML的簡單步驟:
新建一個類MyHandler,繼承自DefaultHandler,並重寫DefaultHandler中的特有方法,解析XML的工作在此類中完成。
實例化一個SAX解析器的工廠對象,SAXParserFactory對象,使用SAXParserFactory.newInstance()方法獲取。
利用SAXParserFactory.newSAXParser()獲得SAX解析器對象SAXParser。
實例化MyHandler類,傳入需要解析的節點名稱。
使用SAXParser.parse()方法設置待解析的XML流和XML解析對象。
最後從MyHandler對象中獲得解析結果。
現在詳細講解一下上面提到的類的作用:
DefaultHandler類是SAX2事件處理程序的默認基類。它繼承了EntityResolver、DTDHandler、ContentHandler和ErrorHandler這四個接口。包含這四個接口的所有方法,所以我們在編寫事件處理程序時,可以不用直接實現這四個接口,而繼承該類,然後重寫我們需要的方法。
而在繼承DefaultHandler的類中,需要重寫以下五個方法:
復制代碼
public void startDocument()
當遇到文檔的開頭的時候,調用這個方法,可以在其中做一些預處理的工作。
public void startElement(String namespaceURI, String localName, String qName, Attributes attributes)
當讀到一個開始標簽的時候,會觸發這個方法,再次獲得元素的屬性。namespaceURI就是命名空間,localName是不帶命名空間前綴的標簽名,qName是帶命名空間前綴的標簽名。通過attributes可以得到所有的屬性名和相應的值。要注意的是SAX中一個重要的特點就是它的流式處理,當遇到一個標簽的時候,它並不會紀錄下以前所碰到的標簽,也就是說,在startElement()方法中,所有你所知道的信息,就是標簽的名字和屬性,至於標簽的嵌套結構,上層標簽的名字,是否有子元屬等等其它與結構相關的信息,都是不得而知的,都需要你的程序來完成。這使得SAX在編程處理上沒有DOM來得那麼方便。
public void characters(char[] ch, int start, int length)
這個方法用來處理在XML文件中讀到的內容,第一個參數用於存放文件的內容,後面兩個參數是讀到的字符串在這個數組中的起始位置和長度,使用new String(ch,start,length)就可以獲取內容。
public void endElement(String uri, String localName, String name)
和startElement()方法相對應,在遇到結束標簽的時候,調用這個方法。
public void endDocument()
和startDocument()方法相對應。當文檔結束的時候,調用這個方法,可以在其中做一些善後的工作。
復制代碼
我們通過一個XML文件來講解一下上面的五個方法在什麼時候被執行:
<?xml version="1.0" encoding="utf-8"?> startDocument
<persons> startElement
<person id="01"> startElement
<name nameid="1"> startElement
smyh characters
</name> endElement
<age> startElement
22 characters
</age> endElement
</person> endElement
</persons> endElement
SAXParserFactory類,定義了一個工廠API,使應用程序能夠配置和獲得基於SAX的解析器以解析XML文檔。它只有一個protected的構造方法(單例模式),所以需要使用靜態的newInstance()方法來回的SAXParserFactory()對象。使用SAXParserFactory可以通過調用.newSAXParser()方法獲得一個SAXParser,通過SAXParser對象可以執行parser()方法,通過傳遞的參數設定XML流和解析器類。
四、SAX解析XML的步驟:
現在通過一個示例程序來講解一下SAX是怎麼解析XML文件的,這個示例程序是運行在Android平台上的,為了模擬真實情況,在tomcat服務器上放置了一個靜態的XML文件,即在D:\apache-tomcat-8.0.14\webapps\ROOT目錄中新建一個smyhvae.xml文件,xml文件內容如下:
復制代碼
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person id="01">
<name>smyh</name>
<age>22</age>
</person>
<person id="02">
<name>vae</name>
<age>24</age>
</person>
</persons>
復制代碼
注:關於tomcat服務器的配置,如果不清楚的話,請參照本人另外一篇博客:Android系列之網絡(三)----使用HttpClient發送HTTP請求(分別通過GET和POST方法發送數據)
因為我電腦的IP地址是192.168.1.112。現在我們在浏覽器輸入http://192.168.1.112:8080/smyhvae.xml,顯示效果如下:
現在我們需要做的是:通過Android程序去獲取並解析這段XML數據。在這個示例程序中,讀取person節點的值。因為是Android程序,所以別忘了賦予其訪問網絡的權限。
整個Android的工程結構如下:
(1)【新建工具類HttpUtils】通過URLHttpConnection獲取服務器上的XML流:
我們將其寫成工具類,代碼如下:
復制代碼
1 package com.example.androidsaxxml.http;
2
3 import java.io.InputStream;
4 import java.net.HttpURLConnection;
5 import java.net.URL;
6
7 //工具類:通過URLHttpConnection獲取服務器上的XML流
8 public class HttpUtils {
9
10 public HttpUtils() {
11 }
12
13 //方法:返回的InputStream對象就是服務器返回的XML流。
14 public static InputStream getXML(String path) {//參數path:之後將在MainActivity中指定具體的url鏈接
15 try {
16 URL url=new URL(path);
17 if(url!=null)
18 {
19 HttpURLConnection connection=(HttpURLConnection)url.openConnection();
20 connection.setDoInput(true);
21 connection.setConnectTimeout(3000);
22 connection.setRequestMethod("GET");
23 int requesetCode=connection.getResponseCode();
24 if(requesetCode==200)
25 {
26 //如果執行成功,返回HTTP響應流
27 return connection.getInputStream();
28 }
29 }
30 } catch (Exception e) {
31 // TODO: handle exception
32 }
33 return null;
34 }
35 }
復制代碼
(2)【新建類MyHandler】新建子類MyHandler,繼承DefaultHandler類:用來解析xml
sax解析xml最重要的步驟就是定義一個我們自己的Handler處理類,並讓其繼承 DefaultHandler 這個類,然後在裡面重寫其回調方法,在這些回調方法裡來做我們的xml解析。代碼如下:
復制代碼
1 package com.example.androidsaxxml.handler;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6
7 import org.xml.sax.Attributes;
8 import org.xml.sax.SAXException;
9 import org.xml.sax.helpers.DefaultHandler;
10
11
12 //類:MyHandler,繼承DefaultHandler類,用於解析XML數據。
13 //之後在MainActivity中通過設定具體的nodeName來實例化MyHandler
14 public class MyHandler extends DefaultHandler {
15 private List<HashMap<String, String>> list = null; //解析後的XML內容
16 private HashMap<String, String> map = null; //存放當前需要記錄的節點的XML內容
17 private String currentTag = null;//當前讀取的XML節點
18 private String currentValue = null;//當前節點的XML文本值
19 private String nodeName = null;//需要解析的節點名稱
20
21 public MyHandler(String nodeName) {
22 // 設置需要解析的節點名稱
23 this.nodeName = nodeName;
24 }
25
26 @Override
27 public void startDocument() throws SAXException {
28 // 接收文檔開始的通知
29 // 實例化ArrayList用於存放解析XML後的數據
30 list = new ArrayList<HashMap<String, String>>();
31 }
32
33 @Override
34 public void startElement(String uri, String localName, String qName,
35 Attributes attributes) throws SAXException {
36 // 接收元素開始的通知
37 if (qName.equals(nodeName)) {
38 //如果當前運行的節點名稱與設定需要讀取的節點名稱相同,則實例化HashMap
39 map = new HashMap<String, String>();
40 }
41 //Attributes為當前節點的屬性值,如果存在屬性值,則屬性值也讀取。
42 if (attributes != null && map != null) {
43 for (int i = 0; i < attributes.getLength(); i++) {
44 //讀取到的屬性值,插入到Map中。
45 map.put(attributes.getQName(i), attributes.getValue(i));
46 }
47 }
48 //記錄當前節點的名稱。
49 currentTag = qName;
50 }
51
52 @Override
53 public void characters(char[] ch, int start, int length)
54 throws SAXException {
55 // 接收元素中字符數據的通知。
56 //當前節點有值的情況下才繼續執行
57 if (currentTag != null && map != null) {
58 //獲取當前節點的文本值,ch這個直接數組就是存放的文本值。
59 currentValue = new String(ch, start, length);
60 if (currentValue != null && !currentValue.equals("")
61 && !currentValue.equals("\n")) {
62 //讀取的文本需要判斷不能為null、不能等於”“、不能等於”\n“
63 map.put(currentTag, currentValue);
64 }
65 }
66 //讀取完成後,需要清空當前節點的標簽值和所包含的文本值。
67 currentTag = null;
68 currentValue = null;
69 }
70
71 @Override
72 public void endElement(String uri, String localName, String qName)
73 throws SAXException {
74 // 接收元素結束的通知。
75 if (qName.equals(nodeName)) {
76 //如果讀取的結合節點是我們需要關注的節點,則把map加入到list中保存
77 list.add(map);
78 //使用之後清空map,開始新一輪的讀取person。
79 map = null;
80 }
81 }
82
83 //方法:獲取解析之後的數據
84 public List<HashMap<String, String>> getList() {
85 return list;
86 }
87 }
復制代碼
(3)【新建類SaxService】實例化一個SAX解析器的工廠對象:SAXParserFactory
需要一個調用SAXParser對象的類,這裡新建一個SaxService類,實例化SAXParserFactory用於設定XML流和解析器,也就是在這裡調用了上一步中的MyHandler類。代碼如下:
復制代碼
1 package com.example.androidsaxxml.service;
2
3 import java.io.InputStream;
4 import java.util.HashMap;
5 import java.util.List;
6
7 import javax.xml.parsers.SAXParser;
8 import javax.xml.parsers.SAXParserFactory;
9
10 import com.example.androidsaxxml.handler.MyHandler;
11
12
13 //類:用於實例化例化一個SAX解析器的工廠對象:SAXParserFactory
14 public class SaxService {
15
16 public SaxService() {
17 // TODO Auto-generated constructor stub
18 }
19
20 //方法:解析xml數據並返回,返回值類型是HashMap
21 public static List<HashMap<String, String>> readXML(InputStream inputStream,String nodeName)
22 {
23 try {
24 //實例化SAX工廠類
25 SAXParserFactory factory=SAXParserFactory.newInstance();
26 //實例化SAX解析器。
27 SAXParser sParser=factory.newSAXParser();
28 //實例化工具類MyHandler,設置需要解析的節點
29 MyHandler myHandler=new MyHandler(nodeName);
30 // 開始解析
31 sParser.parse(inputStream, myHandler);
32 // 解析完成之後,關閉流
33 inputStream.close();
34 //返回解析結果。
35 return myHandler.getList(); //在這裡返回解析之後的數據
36 } catch (Exception e) {
37 // TODO: handle exception
38 }
39 return null;
40 }
41
42 }
復制代碼
核心代碼是第29行和第31行。
(4)在MainActicity中實例化:即實例化需要訪問的鏈接path和需要解析的節點nodeName
布局界面很簡單,只有一個按鈕控件,這裡就不展示布局代碼了。點擊按鈕後,觸發點擊事件,因為是Android4.0+,所以不能在主線程中訪問網絡,需要另起一個線程,這裡使用Thread類。代碼如下:
復制代碼
1 package com.example.androidsaxxml;
2
3 import java.io.InputStream;
4 import java.util.HashMap;
5 import java.util.List;
6
7 import android.app.Activity;
8 import android.os.Bundle;
9 import android.view.View;
10 import android.widget.Button;
11
12 import com.example.androidsaxxml.http.HttpUtils;
13 import com.example.androidsaxxml.service.SaxService;
14
15
16 public class MainActivity extends Activity {
17 private Button button;
18 @Override
19 protected void onCreate(Bundle savedInstanceState) {
20 super.onCreate(savedInstanceState);
21 setContentView(R.layout.activity_main);
22
23 button=(Button)findViewById(R.id.button1);
24 button.setOnClickListener(new View.OnClickListener() {
25
26 @Override
27 //點擊按鈕,開啟線程訪問網絡
28 public void onClick(View v) {
29 Thread thread=new Thread(new Runnable() {
30
31 @Override
32 public void run() {
33 // 設置XML文檔的路徑
34 String path="http://192.168.1.112:8080/smyhvae.xml";
35 //調用類HttpUtils:從服務器上獲取XML流。
36 InputStream inputStream=HttpUtils.getXML(path);
37 try {
38 //調用類SaxService:解析流,同時設定需要解析的節點
39 List<HashMap<String, String>> list=SaxService.readXML(inputStream, "person");
40 for(HashMap<String,String> map:list)
41 {
42 //打印到LogCat中
43 System.out.println(map.toString());
44 }
45 } catch (Exception e) {
46 // TODO: handle exception
47 }
48 }
49 });
50 thread.start();
51 }
52 });
53 }
54 }