【聲明】
歡迎轉載,但請保留文章原始出處→_→
生命壹號:http://www.cnblogs.com/smyhvae/
文章來源:http://www.cnblogs.com/smyhvae/p/4044170.html
【系列】Android系列之網絡:(持續更新)
Android系列之網絡(一)----使用HttpClient發送HTTP請求(通過get方法獲取數據)
Android系列之網絡(二)----HTTP請求頭與響應頭
Android系列之網絡(三)----使用HttpClient發送HTTP請求(分別通過GET和POST方法發送數據)
Android網絡之數據解析----SAX方式解析XML數據
【正文】
一、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.vogella.com/tutorials/AndroidXML/article.html
http://www.cnblogs.com/xiaoluo501395377/p/3444744.html
http://www.cnblogs.com/plokmju/p/android_XMLForSAX.html