一.基礎知識
通過前面的學習我們已經知道了Android上使用SAX和DOM方式解析XML的方法,并且對兩種做了簡單的比較,通過比較我們知道對在往往記憶體比較稀缺的移動裝置上運作的Android系統來說,SAX是一種比較合适的XML解析方式。
但是SAX方式的特點是需要解析完整個文檔才會傳回,如果在一個XML文檔中我們隻需要前面一部分資料,但是使用SAX方式還是會對整個文檔進行解析,盡管XML文檔中後面的大部分資料我們其實都不需要解析,是以這樣實際上就浪費了處理資源。
就以USGS的地震資料為例,USGS網上的這個資料是定時更新的,但是一次更新往往隻更新前面幾條地震資料,大部分資料還是相同的,是以我們在解析時可以在上一次解析的結果之上根據<updated>元素标簽中的值解析前面幾條比之前updated值更新的地震資料即可。但是如果使用SAX方式的話,每次還是都會解析整個XML文檔,而這卻浪費了處理器資源和延長了處理的時間。
不過Android系統還提供了另一種XML解析方式可以使你更好的處理這種情況,就是Pull方式解析XML資料。
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形式的USGS地震資料的Demo例子。
二.執行個體開發
我們要完成的效果圖如下圖1所示:

圖1 ListView清單顯示的地震資料
和上一部分Demo例子的一樣,也是解析完地震資料後用ListView清單的方式顯示每條地震的震級和地名資訊。
建立一個Android工程AndroidXMLDemoPull。
要添加的基本内容和上一個Demo中的一樣,這裡就不再贅述,這次要添加的解析器新類為PullEarthquakeHandler,内容如下所示:
[java] view plain copy print ?
- public class PullEarthquakeHandler {
- //xml解析用到的Tag
- private String kEntryElementName = "entry";
- private String kLinkElementName = "link";
- private String kTitleElementName = "title";
- private String kUpdatedElementName = "updated";
- private String kGeoRSSPointElementName = "georss:point";
- private String kGeoRSSElevElementName = "georss:elev";
- //用于儲存xml解析擷取的結果
- private ArrayList<EarthquakeEntry> earthquakeEntryList = null;
- private EarthquakeEntry earthquakeEntry = null;
- private Boolean startEntryElementFlag = false;
- //解析xml資料
- public ArrayList<EarthquakeEntry> parse(InputStream inStream)
- {
- try {
- //建立XmlPullParser,有兩種方式
- //方式一:使用工廠類XmlPullParserFactory
- XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance();
- XmlPullParser xmlPullParser = pullFactory.newPullParser();
- // //方式二:使用Android提供的實用工具類android.util.Xml
- // XmlPullParser xmlPullParser = Xml.newPullParser();
- xmlPullParser.setInput(inStream, "UTF-8");
- int eventType = xmlPullParser.getEventType();
- boolean isDone = false;
- //具體解析xml
- while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true)) {
- String localName = null;
- switch (eventType) {
- case XmlPullParser.START_DOCUMENT:
- {
- earthquakeEntryList = new ArrayList<EarthquakeEntry>();
- }
- break;
- case XmlPullParser.START_TAG:
- {
- localName = xmlPullParser.getName();
- if(localName.equalsIgnoreCase(kEntryElementName))
- {
- earthquakeEntry = new EarthquakeEntry();
- startEntryElementFlag = true;
- }
- else if(startEntryElementFlag == true)
- {
- String currentData = null;
- if(localName.equalsIgnoreCase(kTitleElementName))
- {
- currentData = xmlPullParser.nextText();
- // Log.v("Pull", currentData);
- //提取強度資訊
- String magnitudeString = currentData.split(" ")[1];
- int end = magnitudeString.length()-1;
- magnitudeString = magnitudeString.substring(0, end);
- double magnitude = Double.parseDouble(magnitudeString);
- earthquakeEntry.setMagnitude(magnitude);
- //提取位置資訊
- String place = currentData.split(",")[1].trim();
- earthquakeEntry.setPlace(place);
- }
- else if (localName.equalsIgnoreCase(kUpdatedElementName)) {
- currentData = xmlPullParser.nextText();
- // Log.v("Pull", currentData);
- //構造更新時間
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- Date qdate = new GregorianCalendar(0,0,0).getTime();
- try {
- qdate = sdf.parse(currentData);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- earthquakeEntry.setDate(qdate);
- }
- else if (localName.equalsIgnoreCase(kGeoRSSPointElementName)) {
- currentData = xmlPullParser.nextText();
- // Log.v("Pull", currentData);
- //提取經緯度資訊
- String[] latLongitude = currentData.split(" ");
- Location location = new Location("dummyGPS");
- location.setLatitude(Double.parseDouble(latLongitude[0]));
- location.setLongitude(Double.parseDouble(latLongitude[1]));
- earthquakeEntry.setLocation(location);
- }
- else if (localName.equalsIgnoreCase(kGeoRSSElevElementName)) {
- currentData = xmlPullParser.nextText();
- // Log.v("Pull", currentData);
- //提取海拔高度資訊
- double evel;
- //因為USGS資料有可能會輸錯,比如為"--10000",多了一個"-"号
- try {
- evel = Double.parseDouble(currentData);
- } catch (Exception e) {
- // TODO: handle exception
- e.printStackTrace();
- evel = 0;
- }
- Log.v("Pull_Elev", String.valueOf(evel));
- earthquakeEntry.setElev(evel);
- }
- else if(localName.equalsIgnoreCase(kLinkElementName))
- {
- String webLink = xmlPullParser.getAttributeValue("", "href");
- earthquakeEntry.setLink(webLink);
- // Log.v("Pull", webLink);
- }
- }
- }
- break;
- case XmlPullParser.END_TAG:
- {
- localName = xmlPullParser.getName();
- if((localName.equalsIgnoreCase(kEntryElementName))&&(startEntryElementFlag==true))
- {
- earthquakeEntryList.add(earthquakeEntry);
- startEntryElementFlag = false;
- }
- }
- break;
- default:
- break;
- }
- eventType = xmlPullParser.next();
- }
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- Log.v("Pull", "End");
- return earthquakeEntryList;
- }
- }
public class PullEarthquakeHandler { //xml解析用到的Tag private String kEntryElementName = "entry"; private String kLinkElementName = "link"; private String kTitleElementName = "title"; private String kUpdatedElementName = "updated"; private String kGeoRSSPointElementName = "georss:point"; private String kGeoRSSElevElementName = "georss:elev"; //用于儲存xml解析擷取的結果 private ArrayList<EarthquakeEntry> earthquakeEntryList = null; private EarthquakeEntry earthquakeEntry = null; private Boolean startEntryElementFlag = false; //解析xml資料 public ArrayList<EarthquakeEntry> parse(InputStream inStream) { try { //建立XmlPullParser,有兩種方式 //方式一:使用工廠類XmlPullParserFactory XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = pullFactory.newPullParser();// //方式二:使用Android提供的實用工具類android.util.Xml// XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inStream, "UTF-8"); int eventType = xmlPullParser.getEventType(); boolean isDone = false; //具體解析xml while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true)) { String localName = null; switch (eventType) { case XmlPullParser.START_DOCUMENT: { earthquakeEntryList = new ArrayList<EarthquakeEntry>(); } break; case XmlPullParser.START_TAG: { localName = xmlPullParser.getName(); if(localName.equalsIgnoreCase(kEntryElementName)) { earthquakeEntry = new EarthquakeEntry(); startEntryElementFlag = true; } else if(startEntryElementFlag == true) { String currentData = null; if(localName.equalsIgnoreCase(kTitleElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取強度資訊 String magnitudeString = currentData.split(" ")[1]; int end = magnitudeString.length()-1; magnitudeString = magnitudeString.substring(0, end); double magnitude = Double.parseDouble(magnitudeString); earthquakeEntry.setMagnitude(magnitude); //提取位置資訊 String place = currentData.split(",")[1].trim(); earthquakeEntry.setPlace(place); } else if (localName.equalsIgnoreCase(kUpdatedElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //構造更新時間 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); Date qdate = new GregorianCalendar(0,0,0).getTime(); try { qdate = sdf.parse(currentData); } catch (ParseException e) { e.printStackTrace(); } earthquakeEntry.setDate(qdate); } else if (localName.equalsIgnoreCase(kGeoRSSPointElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取經緯度資訊 String[] latLongitude = currentData.split(" "); Location location = new Location("dummyGPS"); location.setLatitude(Double.parseDouble(latLongitude[0])); location.setLongitude(Double.parseDouble(latLongitude[1])); earthquakeEntry.setLocation(location); } else if (localName.equalsIgnoreCase(kGeoRSSElevElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取海拔高度資訊 double evel; //因為USGS資料有可能會輸錯,比如為"--10000",多了一個"-"号 try { evel = Double.parseDouble(currentData); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); evel = 0; } Log.v("Pull_Elev", String.valueOf(evel)); earthquakeEntry.setElev(evel); } else if(localName.equalsIgnoreCase(kLinkElementName)) { String webLink = xmlPullParser.getAttributeValue("", "href"); earthquakeEntry.setLink(webLink);// Log.v("Pull", webLink); } } } break; case XmlPullParser.END_TAG: { localName = xmlPullParser.getName(); if((localName.equalsIgnoreCase(kEntryElementName))&&(startEntryElementFlag==true)) { earthquakeEntryList.add(earthquakeEntry); startEntryElementFlag = false; } } break; default: break; } eventType = xmlPullParser.next(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.v("Pull", "End"); return earthquakeEntryList; }}
程式首先也是定義解析用到的變量,在定義的用于解析xml資料的方法中
public ArrayList<EarthquakeEntry> parse(InputStream inStream)
定義了一個局部變量
boolean isDone = false;
用于标志在有滿足條件時停止讀取XML文檔,退出解析過程。
主體部分首先建立XmlPullParser,
[java] view plain copy print ?
- //建立XmlPullParser,有兩種方式
- //方式一:使用工廠類XmlPullParserFactory
- XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance();
- XmlPullParser xmlPullParser = pullFactory.newPullParser();
- // //方式二:使用Android提供的實用工具類android.util.Xml
- // XmlPullParser xmlPullParser = Xml.newPullParser();
//建立XmlPullParser,有兩種方式 //方式一:使用工廠類XmlPullParserFactory XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = pullFactory.newPullParser();// //方式二:使用Android提供的實用工具類android.util.Xml// XmlPullParser xmlPullParser = Xml.newPullParser();
建立XmlPullParser有兩種方式,一種是使用我們介紹的org.xmlpull.v1包中的工廠類XmlPullParserFactory。除了這種方式外,還可以使用Android SDK提供的實用工具包android.util中的類Xml的newPullParser()方法直接建立。
接着為pull解析器設定要解析的xml文檔資料,并使用主動的方式擷取解析器中的事件,
[java] view plain copy print ?
- xmlPullParser.setInput(inStream, "UTF-8");
- int eventType = xmlPullParser.getEventType();
xmlPullParser.setInput(inStream, "UTF-8"); int eventType = xmlPullParser.getEventType();
事件将作為數值代碼被發送,是以可以使用一個簡單 case-switch或者if-else對事件的類型(START_TAG, END_TAG, TEXT或者其他等)進行判斷,并根據對應的事件類型實作相應的處理邏輯。解析并未像 SAX 解析那樣監聽元素的結束,而是在開始處(START_TAG)完成了大部分處理,因為當某個元素開始時,可以調用 解析器執行個體的nextText() 從 XML 文檔中提取所有字元資料,
currentData = xmlPullParser.nextText();
當一個事件處理完成後,可以調用next()方法主動從解析器中擷取下一個事件,
eventType = xmlPullParser.next();
整個過程可以放在一個while循環中,直到碰到XML文檔的結束事件,
while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true))
或者是設定的條件滿足主動停止解析過程,可以看到在上面的while條件中除了碰到文檔結束的條件外,
(eventType != XmlPullParser.END_DOCUMENT)
還有設定的标志是否滿足的條件,
(isDone != true)
設定一個标記(布爾變量 isDone)來确定何時到達感興趣内容的結束部分,允許我們提早停止讀取 XML 文檔,因為我們知道代碼将不會關心文檔的其餘部分。這有時非常實用,特别是當我們隻需要通路一小部分 XML 文檔時。通過盡快停止解析,我們可以極大地減少解析時間。這種優化對于Android系統運作的速度較慢的移動裝置尤為重要。是以Pull解析器可以提供一些性能優勢以及易用性。
最後添加AndroidXMLDemoPull.java檔案中的内容,内容和前一個Demo工程AndroidXMLDemoDom中的AndroidXMLDemoDom.java基本一樣,
[java] view plain copy print ?
- public class AndroidXMLDemoPull extends Activity {
- //定義顯示的List相關變量
- ListView list;
- ArrayAdapter<EarthquakeEntry> adapter;
- ArrayList<EarthquakeEntry> earthquakeEntryList;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //擷取地震資料流
- InputStream earthquakeStream = readEarthquakeDataFromFile();
- //Pull方式進行xml解析
- PullEarthquakeHandler pullHandler = new PullEarthquakeHandler();
- earthquakeEntryList = pullHandler.parse(earthquakeStream);
- //用ListView進行顯示
- list = (ListView)this.findViewById(R.id.list);
- adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList);
- list.setAdapter(adapter);
- }
- private InputStream readEarthquakeDataFromFile()
- {
- //從本地擷取地震資料
- InputStream inStream = null;
- try {
- inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml");
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return inStream;
- }
- private InputStream readEarthquakeDataFromInternet()
- {
- //從網絡上擷取實時地震資料
- URL infoUrl = null;
- InputStream inStream = null;
- try {
- infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml");
- URLConnection connection = infoUrl.openConnection();
- HttpURLConnection httpConnection = (HttpURLConnection)connection;
- int responseCode = httpConnection.getResponseCode();
- if(responseCode == HttpURLConnection.HTTP_OK)
- {
- inStream = httpConnection.getInputStream();
- }
- } catch (MalformedURLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return inStream;
- }
- }
public class AndroidXMLDemoPull extends Activity { /** Called when the activity is first created. */ //定義顯示的List相關變量 ListView list; ArrayAdapter<EarthquakeEntry> adapter; ArrayList<EarthquakeEntry> earthquakeEntryList; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //擷取地震資料流 InputStream earthquakeStream = readEarthquakeDataFromFile(); //Pull方式進行xml解析 PullEarthquakeHandler pullHandler = new PullEarthquakeHandler(); earthquakeEntryList = pullHandler.parse(earthquakeStream); //用ListView進行顯示 list = (ListView)this.findViewById(R.id.list); adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList); list.setAdapter(adapter); } private InputStream readEarthquakeDataFromFile() { //從本地擷取地震資料 InputStream inStream = null; try { inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return inStream; } private InputStream readEarthquakeDataFromInternet() { //從網絡上擷取實時地震資料 URL infoUrl = null; InputStream inStream = null; try { infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"); URLConnection connection = infoUrl.openConnection(); HttpURLConnection httpConnection = (HttpURLConnection)connection; int responseCode = httpConnection.getResponseCode(); if(responseCode == HttpURLConnection.HTTP_OK) { inStream = httpConnection.getInputStream(); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return inStream; }}
隻是把進行XML解析的部分換成了如下方式:
[java] view plain copy print ?
- //Pull方式進行xml解析
- PullEarthquakeHandler pullHandler = new PullEarthquakeHandler();
- earthquakeEntryList = pullHandler.parse(earthquakeStream);
//Pull方式進行xml解析 PullEarthquakeHandler pullHandler = new PullEarthquakeHandler(); earthquakeEntryList = pullHandler.parse(earthquakeStream);
完成了,可以儲存運作看下效果。
三.總結
在這部分中我們學習了Android平台上除了SAX和DOM方式外的第三種解析XML的方式,即使用Pull解析器的方式,并且介紹了Pull方式和SAX方式比較的特點,就是可以在程式中使用代碼主動從解析器中提取事件且可以提前停止XML文檔的解析。并用這個方式完成了一個解析USGS地震資料的Demo例子。
這樣我們就學習了Android平台上的三種解析XML的方式:SAX、DOM和Pull,這三種方式除了我們已經學習過的各自的特點外,他們的性能比較如何?那個解析的速度最快?這部分内容我們以後接着學習。