由於Android系統內挾帶大量xml檔案,所以便想研究一下XmlPullParser的作用跟用法,本文主要參考網路、Android開發者網頁,並製作讀取政府開放資料(空氣品質資料)的範例。
分析xml檔案的方法分三種,分別為Pull、Sax、Dom,三種方法的特色如下:
分析xml檔案的方法分三種,分別為Pull、Sax、Dom,三種方法的特色如下:
DOM
通用性強,此方法一開始便將xml檔案的所有內容讀取到記憶體,然後才能使用dom API輪詢xml樹狀結構、檢索需要的資料,是一簡單直觀的搜索方式,但因需要先將文字檔讀取到記憶體,較不適合記憶體餘裕不大的行動裝置。
SAX
Sax是一分析速度快並且占用記憶體較少的xml方法,採用事件驅動的方式,並不需要分析整個文字檔。
PULL
pull為Android自帶的xml分析方法,使用時不需要將整個文件都加載到記憶體,和Sax運作原理相同也是事件驅動,不同的是pull事件方法回傳的是整數類型。
Android系統的資料文件很多都為xml格式,而Android系統在分析這些xml檔案的時候,皆是使用pull分析器,故這邊主要說明與練習的是Android自帶的pull分析器。
Android的pull分析器可以參考Android開發者網站對XMLPullParser介面的說明,XMLPullParser有兩個關鍵的方法,分別是next()以及nextToken()取得當前分析器的事件狀態(event state) ,另外也可透過getEventType()方法可以得知,而分析器初始狀態是START_DOCUMENT。
表示分析器目前處於初始狀態,尚未讀取任何資料,此狀態只能在getEvent()方法在next(), nextToken()或是nextTag()任一方法之前被呼叫的時候才會出現。
next()方法促使分析器進入下一個事件,此方法回傳的整數值表示當前分析器的處理狀態,亦會與緊接著呼叫(following calls)的getEventType()方法回傳值相同。
next()方法回傳的事件類型(event types)如下:
當getEventType(), next(), nextToken()方法回傳此數值,表示讀取到XML start tag。start tag的名稱可以透過getName()取得、若namespace可用,則namespace以及prefix則可透過getNamespace()以及getPrefix()取得。
表示讀取到文字內容(text content);可以使用getText()方法取得文字內容。
當getEventType(), next(), nextToken()方法回傳此數值,表示讀取到end tag。
已在合理的xml文字檔案結尾,當getEventType(), next(), nextToken()方法回傳此數值, 表示已抵達輸入文字檔的結尾處。
接下來動手做的練習目標有:
- 參考Android開發者網站對XMLPullParser介面的說明,改成可以在Android上直接運行的程式碼
- 讀取政府的開放空氣品質xml資料(http://opendata2.epa.gov.tw/AQX.xml)
- 取得嘉義市的PM2.5數值以及該資料的發布時間
- 使用AsyncTask跟ProgressDialog,作為讀取跟分析數值時的過場動畫
使用了一些變數控制讀取的動作,名稱與功能如下:
宣告xml_tag變數儲存目前的xml tag,若為Counrty, PM2.5或是PublishTime tag的話,在XmlPullParser.TEXT事件中,就要更動b_inTargetCounty或把文字儲存到相應的字串變數(
str_pm25, str_publishTime)
不知為何,在End tag之後還會多出一個XmlPullParser.TEXT 的event,如下圖所示,所以程式碼就加上了b_tagStart變數,只有在讀取到start tag之後才將資料儲存起來。
若日後有加入其他功能至APP,將會在Github專案上同步更新,Github專案傳送門、程式碼根目錄傳送門,以下為可動之程式碼檔案:
MainActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xmlpull.sample.jean.xmlpull; | |
import android.app.ProgressDialog; | |
import android.os.AsyncTask; | |
import android.support.v7.app.AppCompatActivity; | |
import android.os.Bundle; | |
import android.util.*; | |
import android.view.View; | |
import android.widget.Button; | |
import android.widget.TextView; | |
import org.xmlpull.v1.XmlPullParser; | |
import org.xmlpull.v1.XmlPullParserException; | |
import org.xmlpull.v1.XmlPullParserFactory; | |
import java.io.BufferedReader; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.io.StringReader; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
public class MainActivity extends AppCompatActivity { | |
TextView val_pm25; | |
TextView val_time; | |
ProgressDialog waiting_dialog; | |
Button btn_update; | |
String xml_tag = ""; | |
String str_pm25; | |
String str_publishTime; | |
boolean b_inTargetCounty = false; | |
boolean b_tagStart = false; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
val_pm25 = (TextView) findViewById(R.id.tv_val_pm25); | |
val_time = (TextView) findViewById(R.id.tv_val_time); | |
btn_update = (Button)findViewById(R.id.button_update); | |
btn_update.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
new parseXMLTask().execute("http://opendata2.epa.gov.tw/AQX.xml"); | |
} | |
}); | |
new parseXMLTask().execute("http://opendata2.epa.gov.tw/AQX.xml"); | |
} | |
private class parseXMLTask extends AsyncTask<String, Integer, Integer> { | |
@Override | |
protected void onPreExecute() { | |
waiting_dialog = ProgressDialog.show(MainActivity.this, "請稍後", "資料更新中", true); | |
} | |
@Override | |
protected Integer doInBackground(String... urls) { | |
try { | |
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); | |
factory.setNamespaceAware(true); | |
XmlPullParser xpp = factory.newPullParser(); | |
String result = resultFromURL(urls[0]); | |
xpp.setInput(new StringReader(result)); | |
// xpp.setInput(new StringReader("<foo>Hello World!</foo><bar>Hello World bar!</bar>")); | |
int eventType = xpp.getEventType(); | |
while (eventType != XmlPullParser.END_DOCUMENT) { | |
if (eventType == XmlPullParser.START_DOCUMENT) { | |
// Log.d("getEventType returned", "Start document"); | |
} else if (eventType == XmlPullParser.START_TAG) { | |
// Log.d("getEventType returned", "Start tag, named [" + xpp.getName() + "]"); | |
b_tagStart = true; | |
xml_tag = xpp.getName(); | |
} else if (eventType == XmlPullParser.END_TAG) { | |
// Log.d("getEventType returned", "End tag, named [" + xpp.getName() + "]"); | |
b_tagStart = false; | |
if ((b_inTargetCounty) && (xpp.getName().equals("Data"))) { | |
b_inTargetCounty = false; | |
} | |
} else if (eventType == XmlPullParser.TEXT) { | |
// Log.d("getEventType returned", "Text, the content [" + xpp.getText() + "]"); | |
if ((xml_tag.equals("County")) && (xpp.getText().equals(getString(R.string.target_county)))) { | |
b_inTargetCounty = true; | |
} else if ((b_inTargetCounty) && (b_tagStart) && (xml_tag.equals("PM2.5"))) { | |
str_pm25 = xpp.getText(); | |
Log.d("TAG", "str_pm25 [" + str_pm25 + "]"); | |
} else if ((b_inTargetCounty) && (b_tagStart) && (xml_tag.equals("PublishTime"))) { | |
str_publishTime = xpp.getText(); | |
Log.d("TAG", "str_publishTime [" + str_publishTime + "]"); | |
} | |
} | |
eventType = xpp.next(); | |
} | |
// Log.d("getEventType returned", "End document"); | |
} catch (XmlPullParserException e) { | |
e.printStackTrace(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return 1; | |
} | |
@Override | |
protected void onPostExecute(Integer result) { | |
val_pm25.setText(str_pm25); | |
val_time.setText(str_publishTime); | |
waiting_dialog.dismiss(); | |
} | |
} | |
private String resultFromURL(String str_url) { | |
String result = ""; | |
URL url; | |
HttpURLConnection urlConnection = null; | |
try { | |
url = new URL(str_url); | |
urlConnection = (HttpURLConnection) url.openConnection(); | |
InputStream in_s = urlConnection.getInputStream(); | |
result = getStringFromInputStream(in_s); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
return result; | |
} | |
} | |
private String getStringFromInputStream(InputStream is) { | |
BufferedReader br = null; | |
StringBuilder sb = new StringBuilder(); | |
String line; | |
try { | |
br = new BufferedReader(new InputStreamReader(is)); | |
while ((line = br.readLine()) != null) { | |
sb.append(line); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (br != null) { | |
try { | |
br.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
return sb.toString(); | |
} | |
} |
activity_main.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<RelativeLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:paddingBottom="@dimen/activity_vertical_margin" | |
android:paddingLeft="@dimen/activity_horizontal_margin" | |
android:paddingRight="@dimen/activity_horizontal_margin" | |
android:paddingTop="@dimen/activity_vertical_margin" | |
tools:context="xmlpull.sample.jean.xmlpull.MainActivity"> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textAppearance="?android:attr/textAppearanceLarge" | |
android:text="嘉義市空氣品質狀況" | |
android:id="@+id/tv_city" | |
android:layout_alignParentTop="true" | |
android:layout_alignParentStart="true"/> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textAppearance="?android:attr/textAppearanceLarge" | |
android:text="PM2.5數值:" | |
android:id="@+id/tv_pm25" | |
android:layout_below="@+id/tv_city" | |
android:layout_alignParentStart="true"/> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textAppearance="?android:attr/textAppearanceLarge" | |
android:id="@+id/tv_val_pm25" | |
android:layout_alignTop="@+id/tv_pm25" | |
android:layout_toEndOf="@+id/tv_pm25" | |
android:text="N/A" /> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textAppearance="?android:attr/textAppearanceLarge" | |
android:text="資料更新時間:" | |
android:id="@+id/textView" | |
android:layout_below="@+id/tv_pm25" | |
android:layout_alignParentStart="true"/> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:textAppearance="?android:attr/textAppearanceLarge" | |
android:text="xxxx-xx-xx xx:xx" | |
android:id="@+id/tv_val_time" | |
android:layout_below="@+id/tv_pm25" | |
android:layout_toEndOf="@+id/textView"/> | |
<Button | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="更新" | |
android:id="@+id/button_update" | |
android:layout_below="@+id/textView" | |
android:layout_alignParentStart="true"/> | |
</RelativeLayout> |
Comments
Post a Comment