Android網絡之數據解析----SAX方式解析XML數據

【系列】Android系列之網絡:(持續更新)

Android系列之網絡(一)----使用HttpClient發送HTTP請求(通過get方法獲取數據)

Android系列之網絡(二)----HTTP請求頭與響應頭

Android系列之網絡(三)----使用HttpClient發送HTTP請求(分別通過GET和POST方法發送數據)

【正文】

一、XML和Json數據的引入:

通常情況下,每個需要訪問網絡的應用程序都會有一個自己的服務器,我們可以向服務器提交數據,也可以從服務器獲取數據。不過這個時候就有一個問題,這些數據是以什麼格式在網絡上傳輸的呢?一般我們都會在網絡上傳輸一些格式化後的數據,這種數據會有一定的結構規格和語言,當另一方受到數據消息後就可以按照相同的結構規格進行解析,從而取出它想要的那部分內容

在網絡上傳輸數據最常用的格式:XML和Json。本文就來學習一下XML數據的解析,Json格式的數據解析將在下一篇文章中講到。

 

二、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 }
複製代碼

核心代碼是第36行(解析具體的url)、39行(從person節點開始讀取)。

當點擊按鈕後,XML解析後的內容會把打印到日誌中

【工程文件】

鏈接:http://pan.baidu.com/s/1dDtilYp

密碼:nnsu

文章來源:http://www.cnblogs.com/smyhvae/p/4044170.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章