大綱
本篇文章會先介紹為何Android中,用到網路請求時建議使用Volley框架,和Volley中如何使用GET請求網路資料。接下來會介紹在JAVA中,解析複雜的XML資料時,超級好用的SimpleXML以及如何使用。
最後再解說,我如何利用Volley框架結合SimpleXML解析網站上的RSS xml Data,本文會花多一點篇幅在這部分。
為何使用Volley框架請求網路數據
一談到Android的網路請求方式,相信大家第一個想到的就是HttpURLConnection和HttpClient,我以前也是這樣寫過來的。但若請求的東西單純還好,如果要POST、要傳複雜的參數或請求網路圖片等…
就會感到前所未有的煩躁,重複創建AsyncTask,一直重複copy/past的動作,還要擔心自己的寫法穩定性是否足夠。
幸運的是我亂逛網站時,偶然得知了Volley框架。Volley是Google在2013 Google I/O大會上發表的網路通訊框架,它將Http的通訊細節都經過封裝,讓使用者能輕鬆的請求網路數據。
GitHub中,比較複雜的APP也常看到Volley的蹤跡。但Volley有優點也有缺點,是否使用它要視情況而定。下面是Volley優缺點列表:
優點
高效能的GET/POST數據請求交互
幫忙處理複雜的網路圖片加載與緩存
性能穩定
Google官方出的,保證功能全面
缺點
不適合大數據的上傳/下載
Volley設計的目標就是請求數據量不大但頻繁的網路資料 ,所以如果有大數據量的網路下載(ex 下載檔案文件),Volley表現就會不太好。
Volley的GET請求
因為這次我只會使用到Volley的GET,而Volley裡文字數據的請求有三個,StringRequest、JsonObjectRequest和JsonArrayRequest。JsonObjectRequest和JsonArrayRequest可以讓請求JSON數據時更快速,只可惜這次要請求的是XML數據,所以以下說明將使用StringRequest的GET方法做請求範例 ,JsonObjectRequest和JsonArrayRequest的用法其實和StringRequest相似,詳細會在以後用到時再做介紹,還有Volley最方便的網路圖片請求也會在以後做說明!
1 ) 引用volley包
感恩好心網友幫gradle和Maven建立Volley庫,讓我們能輕鬆使用框架。原repo請至這裡 。
dependencies {
compile 'com.mcxiaoke.volley:library:1.0.18'
}
2 ) 宣告請求對列RequestQueue
RequestQueue用來緩存所有HTTP請求。RequestQueue內會按照一定算法發出網路請求。而RequestQueue本身就適合高開發,所以重複宣告多個很浪費資源,故RequestQueue物件大都設為全局Application的變數。
public class MyApp extends Application {
/*......*/
RequestQueue mQueue = Volley . newRequestQueue ( context );
/*......*/
}
MyApp別忘記在AndroidManifest.xml加上android:name=”MyApp” ,也別忘記加上網路權限 。
3 ) 創建StringRequest物件
/*
使用POST時用Request.Method.POST;使用GET時用Request.Method.GET
String url 為請求對像server的網址,因為是GET所以參數能直接夾帶在網址後
Response.Listener response successful時調用
Response.ErrorListener response error時調用
*/
StringRequest stringRequest = new StringRequest ( Request . Method . GET , url ,
new Response . Listener < String >() {
@Override
public void onResponse ( String response ) {
//response成功時做的事
}
}, new Response . ErrorListener () {
@Override
public void onErrorResponse ( VolleyError error ) {
Log . e ( "TAG" , error . getMessage (), error ); //response失敗時做的事
}
});
4 ) 把建立好的request丟到RequestQueue執行,大功告成!
mQueue . add ( stringRequest );
如果想知道更詳細的Volley使用方法,能參考這裡 。
Java中,解析XML的方便套件:SimpleXML
台灣有很多活動網站,但不是沒提供完整的API,要不然就是API資料還死守XML不放……尋了好久,最後還是決定,用請求KKTIX的RSS xml資料,來獲得每日活動詳細。
RSS的xml 資料層次多,一層包一層很複雜,如果想快速取出想要的資訊,並跳過不需要的資料,好的XML解析套件就非常重要。
在這個JSON輕量級資料傳輸格式幾乎取代XML的時代,找出好的XML解析套件花了我不少時間,最後終於找到超直觀好用又穩定的套件:SimpleXML了!!
SimpleXML的使用思路是,先跟據要解析的XML文件創建一個java class(用SimpleXML規定的方式撰寫),再通過這個java class去取得XML文件內的具體值。
以下會做些簡易說明:
1 ) 以下為要解析的文件
<?xml version="1.0" encoding="UTF-8"?>
<list>
<entry id= "1" >
<name> John</name>
<gender> Boy</gender>
</entry>
</list>
2 ) 到官網下載SimpleXML Jar 並加到Android Studio或Eclipse的library,官網的example 也有教學能讓初學者更快上手。
3 ) 撰寫和要解析的XML文件相對應的java class
@Root ( name = "list" , strict = true ) //Root為根,如果<list>內不是每個元素都想去,strict要用false
public class Mylist {
@Attribute ( required = true )
protected String id ; // entry: id="1"
@Element ( name = "name" , required = true )
protected String Name ;
@Element ( name = "gender" , required = true )
protected String Gender ;
public String getID ( ) {
return id ;
}
public String getName ( ) {
return Name ;
}
public String getGender ( ) {
return Gender ;
}
}
怎麼樣!上面的java class對應看起來超直觀的吧!(灑花)
4 ) 通過這個java class去取得XML文件內的具體值
final Serializer serializer ;
serializer = new Persister ();
Mylist list = serializer . read ( Mylist . class , String fileData ); //得到Mylist
其中還有能將重複Item都整理成list的@ElementList,歡迎參考這裡 。
本次實作架構的UML如下:
app資料夾:放置App全局資源
AppController.java :繼承了Application,放置全局變量和method的地方
AppRemoteConfig.java:放置資料請求目標端的URL
bean資料夾
Author.java、xmlBeanList.java、xmlEventBean.java:根據KKTIX RSS的xml data撰寫的映射物件。
volleyResponse資料夾:放置Volley相關改寫
SimpleXmlRequest.java:繼承自Volley的Request,給Request做了改寫。
思路始末
我原來的思路,是希望使用Volley的StringRequest把所有xml請求下來,到Response.Listener再用SimpleXML進行XML的解析。但是當我使用StringRequest請求完資料,並在Response.Listener用Log打印出來時,竟然只打印出一半的XML資料,後半資料都消失得莫名其妙,但StringRequest內確實是接收到全部的XML資料,這樣的結果讓我想到Response.Listener內可能不適合放置耗時邏輯,像是我本來將要做的,大量映射XML物件的邏輯…
經過翻找,很幸運地看到itsalif大大的SimpleXmlRequest.java 貢獻,這位大大繼承了Volley的Request,並在parseNetworkResponse method中,一接收完資料就用SimpleXML解析XML資料,將映射出來的所有物件再丟給Response.Listener處理,這樣就完美的一個資料都不會漏了!
我就趕快來試一試,真的非常好用!如果各位有想從網路上請求大量的XML資料並解析,非常推薦這個方法,SimpleXmlRequest頁面 下也有許多網友做了進階的改寫。再觀看的途中,可能會對 void method( )這種寫法感到疑惑,可以參考這裡 。
AppController.java內的GetKKTIXRequestToEventBean(Context context)實作
public void GetKKTIXRequestToEventBean ( Context context ){
eventRequestQueue . cancelAll ( VolleyTAG . KKTIX_ALL . getTAG ()); //取消還在運行的同一個request
if ( ifInternetOpen ( context )) { //如果網路有開啟
/*
*AppRemoteConfig使用single pattern,確保不浪費空間資源
*xmlBeanList.class為給SimpleXML解析資料時的映射物件
*Response.Listener<xmlBeanList>為Volley資料回傳成功時調用,要實作onResponse method,xmlBeanList為回傳資料型態。
*Response.ErrorListener為錯誤時調用,要實作 onErrorResponse。
*/
SimpleXmlRequest < xmlBeanList > simpleRequest = new SimpleXmlRequest < xmlBeanList > ( Request . Method . GET ,
AppRemoteConfig . getInstance (). getKKTIX_ALL_url (),
xmlBeanList . class ,
new Response . Listener < xmlBeanList >() {
@Override
public void onResponse ( xmlBeanList response ) {
List < xmlEventBean > datas = response . getMatches ();
for ( xmlEventBean bean : datas ) {
Log . i ( "success" , "title: " + bean . getTitle () + " and " + bean . getAuthor (). getName ());
}
}
},
new Response . ErrorListener () {
@Override
public void onErrorResponse ( VolleyError error ) {
Log . i ( "error" , error . getMessage ());
}
}
);
AppController . getInstance (). addToRequestQueue ( simpleRequest , VolleyTAG . KKTIX_ALL . getTAG ()); //加入RequestQueue執行請求
} else {
//如果網路未開啟
Log . i ( "Internet Error" , "user phone didn't open internet." );
}
關於網路的判斷可以對照下面。
public boolean ifInternetOpen ( Context context ){
final ConnectivityManager connMgr = ( ConnectivityManager )
this . getSystemService ( Context . CONNECTIVITY_SERVICE ); //呼叫App內建Service
final android . net . NetworkInfo wifi =
connMgr . getNetworkInfo ( ConnectivityManager . TYPE_WIFI ); //Wifi狀態
final android . net . NetworkInfo mobile =
connMgr . getNetworkInfo ( ConnectivityManager . TYPE_MOBILE ); //3G、4G網路狀態
if ( wifi . isAvailable () || mobile . isAvailable ()){
return true ;
}
else {
Toast . makeText ( context , "請開起網路連線" , Toast . LENGTH_LONG ). show ();
return false ;
}
}
之後在MainActivity就能很輕鬆的使用AppController.getInstance().GetKKTIXRequestToEventBean(MainActivity.this); 載入網路上的KKTIX RSS的xml data。
首先,觀察KKTIX RSS的xml data。網址如這 ,下面截圖已經取了片段說明。
由上面說明撰寫物件。
xmlBeanList.java
public class xmlBeanList {
@Root ( name = "feed" , strict = false ) //strict = false:非嚴格的feed tag中每個element和Attribute都要取,root為feed
@ElementList ( entry = "entry" , inline = true ) //<entry></entry>的list
private List < xmlEventBean > beans ; //集合,內容物對應到下面的xmlEventBean.java
public xmlBeanList () {
}
public List < xmlEventBean > getMatches () { //必須要有GET method才能拿到data
return beans ;
}
}
xmlEventBean.java
public class xmlEventBean {
@Root ( name = "entry" , strict = false ) //root為entry
@Element ( name = "title" , required = true ) //<title>...</title>,required設true:沒符合的話會丟error
private String title ;
@Element ( name = "link" , required = true ) //<link rel="..." ..... href="..."/>
public class Link {
@Attribute ( name = "href" , required = true )
private String url ;
}
@Element ( name = "summary" , required = false ) //<summary>...</summary>
private String summary ;
@Element ( name = "content" , required = true ) //<content>...</content>
private String timeANDplace ;
@Element ( name = "author" , required = false ) //<author>...</author>,對應到Author class
private Author author ;
//必須要有GET method才能拿到data
public String getTitle () {
return title ;
}
public void setTitle ( String title ) {
this . title = title ;
}
public String getSummary () {
return summary ;
}
public void setSummary ( String summary ) {
this . summary = summary ;
}
public String getTimeANDplace () {
return timeANDplace ;
}
public void setTimeANDplace ( String timeANDplace ) {
this . timeANDplace = timeANDplace ;
}
public Author getAuthor () {
return author ;
}
}
Author.java
public class Author {
@Element ( name = "name" , required = true )
private String name ;
@Element ( name = "url" , required = false )
private String url ;
public String getName () {
return name ;
}
public String getUrl () {
return url ;
}
}
最後將 xmlBeanList.class給SimpleXmlRequest class當參數,讓SimpleXmlRequest內的parseNetworkResponse中,serializer.read(clazz, data,false )去將XML data都解析成物件。其中的false代表XML格式不嚴格,要加才不易抱錯。
限制選項Enum: VolleyTAG
最後,為了配合不同的請求目標URL,就要有相應不同的TAG管理,Enum就是個很好限制選項的寫法。使用自訂Enum並 限制住呼叫建構子的方法,讓使用者不會搞錯TAG,詳細教學見這 。
public enum VolleyTAG {
//限制住本class只允許2種建構子: VolleyTAG("KKTIX_ALL") / VolleyTAG("GOV_EVENT")
KKTIX_ALL ( "KKTIX_ALL" ),
GOV_EVENT ( "GOV_EVENT" );
private String TAG = null ;
VolleyTAG ( String TAG ) {
this . TAG = TAG ;
}
public String getTAG (){
return TAG ;
}
}
結果:成功映射出XML data物件
程式碼
git clone下面專案到自己的電腦後,使用 git checkout a5ab . 。