Skip to main content

【新知練習】 Android XmlPullParser-Basic

由於Android系統內挾帶大量xml檔案,所以便想研究一下XmlPullParser的作用跟用法,本文主要參考網路、Android開發者網頁,並製作讀取政府開放資料(空氣品質資料)的範例。



分析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()方法回傳此數值, 表示已抵達輸入文字檔的結尾處。

接下來動手做的練習目標有:
  1. 參考Android開發者網站對XMLPullParser介面的說明,改成可以在Android上直接運行的程式碼
  2. 讀取政府的開放空氣品質xml資料(http://opendata2.epa.gov.tw/AQX.xml)
  3. 取得嘉義市的PM2.5數值以及該資料的發布時間
  4. 使用AsyncTask跟ProgressDialog,作為讀取跟分析數值時的過場動畫
完成App介面如下圖:


使用了一些變數控制讀取的動作,名稱與功能如下:
宣告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

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

<?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

Popular posts from this blog

淺介I2C

I 2 C 起源 內部整合電路( Inter-Integrated Circuit, I 2 C, 讀做 I-square-C )是由飛利浦半導體公司開發的一種專用介面。 I 2 C 是以最少的連接線進行硬體佈線還要有靈活擴充的特性為目標而設計,最後出現了只有以序列資料線 SDA ( Serial DAta )及序列時脈線 SCL ( Serial CLock )來進行所有通訊的 I 2 C 介面, I 2 C 允許多主( master )多僕( slave )系統,其傳輸系統內每一個裝置都有唯一的地址可供辨識。資料的寫入和讀取都是由 master 主動發起, slave 無法主動向 master 回報,除非使用中斷腳通知 master 。 I 2 C 傳輸速度有慢(小於 100Kbps )、快( 400Kbps )及高速( 3.4Mbps )三種,每一種均可向下相容。 I 2 C 電路配置 如前所述 I 2 C 為兩線式,一為時脈線 SCL ,另一條為資料線 SDA ,硬體線路如圖 1 ,兩線皆為雙向性,且都需要透過高接電阻( pull-up, 對岸說的上拉電阻)接電。平常不使用時, SCL 與 SDA 的訊號都處於高電位。為了多裝置共線的功能,裝置的 SCL 和 SDA 腳位要為 開洩極( open-drain ) 或 開集極( open-collector ) 。一旦有一個腳位的開洩極導通接地,則整條線都為低電位,這種現象稱作 wired-AND 運作 ;如同邏輯 AND 運算,需要共接的腳位都是 1 (開洩極斷路),該條線的電位才是 1 。如果沒有開洩極的腳位,可以使用具內部高接電阻的腳位,當要輸出 1 時,則設定該腳位為高接型輸入腳;而輸出為 0 時,則改設定為輸出腳並輸出 0 的值。 圖 1. I 2 C 傳輸裝置接線 [1] I 2 C 通訊協定 為使說明部分更簡潔,首先介紹幾個名詞: 位元傳輸協定 當 master 要跟 slave 溝通時,會先有個起始條件( start condition )的訊號,結束時也會送出終止條件( stop condition )訊號。起始條件訊號...

【Arduino】Timers, Registers, and Fast PWM Mode

由於Arduino預設的PWM控制方法僅有500Hz(好像還有另一個),想要知道怎樣才可以調整成其他頻率,以此做記錄。 先說明Timer設定方式、PWM Mode,之後會在Arduino上實做。 如果對_BV()沒有概念,可以參考 【Arduino I/O Ports】Control under avr-libc 這篇文章。

【Arduino】Control I/O pin by avr-libc

Arduino Ports and Register 心血來潮看了一下Arduino底層對pin腳的控制,整理了一下並且分為兩部分: 第一部分:Arduino內部pin腳的port以及register的編號,並且如何使用這些register對腳位進行控制 第二部分:使用最底層的avr-libc進行pinMode、digitalWrite函數的改寫練習,發現使用avr-libc可以有效降低script的大小