Android Volley完全解析(一),初識Volley的基本用法
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/17482095
1. Volley簡介
我們平時在開發Android應用的時候不可避免地都需要用到網絡技術,而多數情況下應用程序都會使用HTTP協議來發送和接收網絡數據。android系統中主要提供了兩種方式來進行HTTP通信,HttpURLConnection和HttpClient,幾乎在任何項目的代碼中我們都能看到這兩個類的身影,使用率非常高。
不過HttpURLConnection和HttpClient的用法還是稍微有些複雜的,如果不進行適當封裝的話,很容易就會寫出不少重複代碼。於是乎,一些Android網絡通信框架也就應運而生,比如說AsyncHttpClient,它把HTTP所有的通信細節全部封裝在了內部,我們只需要簡單調用幾行代碼就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上顯示網絡圖片的操作變得極度簡單,開發者不用關心如何從網絡上獲取圖片,也不用關心開啓線程、回收圖片資源等細節,Universal-Image-Loader已經把一切都做好了。
Android開發團隊也是意識到了有必要將HTTP的通信操作再進行簡單化,於是在2013年Google I/O大會上推出了一個新的網絡通信框架——Volley。Volley可是說是把AsyncHttpClient和Universal-Image-Loader的優點集於了一身,既可以像AsyncHttpClient一樣非常簡單地進行HTTP通信,也可以像Universal-Image-Loader一樣輕鬆加載網絡上的圖片。除了簡單易用之外,Volley在性能方面也進行了大幅度的調整,它的設計目標就是非常適合去進行數據量不大,但通信頻繁的網絡操作,而對於大數據量的網絡操作,比如說下載文件等,Volley的表現就會非常糟糕。
下圖所示的這些應用都是屬於數據量不大,但網絡通信頻繁的,因此非常適合使用Volley。
2. 下載Volley
介紹了這麼多理論的東西,下面我們就準備開始進行實戰了,首先需要將Volley的jar包準備好,如果你的電腦上裝有Git,可以使用如下命令下載Volley的源碼:
- git clone https://android.googlesource.com/platform/frameworks/volley
新建一個Android項目,將volley.jar文件複製到libs目錄下,這樣準備工作就算是做好了。
3. StringRequest的用法
前面已經說過,Volley的用法非常簡單,那麼我們就從最基本的HTTP通信開始學習吧,即發起一條HTTP請求,然後接收HTTP響應。首先需要獲取到一個RequestQueue對象,可以調用如下方法獲取到:
- RequestQueue mQueue = Volley.newRequestQueue(context);
接下來爲了要發出一條HTTP請求,我們還需要創建一個StringRequest對象,如下所示:
- StringRequest stringRequest = new StringRequest("http://www.baidu.com",
- new Response.Listener<String>() {
- @Override
- public void onResponse(String response) {
- Log.d("TAG", response);
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- Log.e("TAG", error.getMessage(), error);
- }
- });
最後,將這個StringRequest對象添加到RequestQueue裏面就可以了,如下所示:
- mQueue.add(stringRequest);
另外,由於Volley是要訪問網絡的,因此不要忘記在你的AndroidManifest.xml中添加如下權限:
- <uses-permission android:name="android.permission.INTERNET" />
好了,就是這麼簡單,如果你現在運行一下程序,併發出這樣一條HTTP請求,就會看到LogCat中會打印出如下圖所示的數據。
沒錯,百度返回給我們的就是這樣一長串的HTML代碼,雖然我們看起來會有些吃力,但是瀏覽器卻可以輕鬆地對這段HTML代碼進行解析,然後將百度的首頁展現出來。
這樣的話,一個最基本的HTTP發送與響應的功能就完成了。你會發現根本還沒寫幾行代碼就輕易實現了這個功能,主要就是進行了以下三步操作:
1. 創建一個RequestQueue對象。
2. 創建一個StringRequest對象。
3. 將StringRequest對象添加到RequestQueue裏面。
不過大家都知道,HTTP的請求類型通常有兩種,GET和POST,剛纔我們使用的明顯是一個GET請求,那麼如果想要發出一條POST請求應該怎麼做呢?StringRequest中還提供了另外一種四個參數的構造函數,其中第一個參數就是指定請求類型的,我們可以使用如下方式進行指定:
- StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener);
- StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
- @Override
- protected Map<String, String> getParams() throws AuthFailureError {
- Map<String, String> map = new HashMap<String, String>();
- map.put("params1", "value1");
- map.put("params2", "value2");
- return map;
- }
- };
4. JsonRequest的用法
學完了最基本的StringRequest的用法,我們再來進階學習一下JsonRequest的用法。類似於StringRequest,JsonRequest也是繼承自Request類的,不過由於JsonRequest是一個抽象類,因此我們無法直接創建它的實例,那麼只能從它的子類入手了。JsonRequest有兩個直接的子類,JsonObjectRequest和JsonArrayRequest,從名字上你應該能就看出它們的區別了吧?一個是用於請求一段JSON數據的,一個是用於請求一段JSON數組的。
至於它們的用法也基本上沒有什麼特殊之處,先new出一個JsonObjectRequest對象,如下所示:
- JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
- new Response.Listener<JSONObject>() {
- @Override
- public void onResponse(JSONObject response) {
- Log.d("TAG", response.toString());
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- Log.e("TAG", error.getMessage(), error);
- }
- });
最後再將這個JsonObjectRequest對象添加到RequestQueue裏就可以了,如下所示:
- mQueue.add(jsonObjectRequest);
由此可以看出,服務器返回給我們的數據確實是JSON格式的,並且onResponse()方法中攜帶的參數也正是一個JSONObject對象,之後只需要從JSONObject對象取出我們想要得到的那部分數據就可以了。
你應該發現了吧,JsonObjectRequest的用法和StringRequest的用法基本上是完全一樣的,Volley的易用之處也在這裏體現出來了,會了一種就可以讓你舉一反三,因此關於JsonArrayRequest的用法相信已經不需要我再去講解了吧。
好了,關於Volley的基本用法就講到這裏,下篇文章中我會帶領大家繼續探究Volley。
Android Volley完全解析(二),使用Volley加載網絡圖片
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/17482165
在上一篇文章中,我們瞭解了Volley到底是什麼,以及它的基本用法。本篇文章中我們即將學習關於Volley更加高級的用法,如何你還沒有看過我的上一篇文章的話,建議先去閱讀Android Volley完全解析(一),初識Volley的基本用法。
在上篇文章中有提到過,Volley是將AsyncHttpClient和Universal-Image-Loader的優點集成於一身的一個框架。我們都知道,Universal-Image-Loader具備非常強大的加載網絡圖片的功能,而使用Volley,我們也可以實現基本類似的效果,並且在性能上也豪不遜色於Universal-Image-Loader,下面我們就來具體學習一下吧。
1. ImageRequest的用法
前面我們已經學習過了StringRequest和JsonRequest的用法,並且總結出了它們的用法都是非常類似的,基本就是進行以下三步操作即可:
1. 創建一個RequestQueue對象。
2. 創建一個Request對象。
3. 將Request對象添加到RequestQueue裏面。
其中,StringRequest和JsonRequest都是繼承自Request的,所以它們的用法纔會如此類似。那麼不用多說,今天我們要學習的ImageRequest,相信你從名字上就已經猜出來了,它也是繼承自Request的,因此它的用法也是基本相同的,首先需要獲取到一個RequestQueue對象,可以調用如下方法獲取到:
- RequestQueue mQueue = Volley.newRequestQueue(context);
- ImageRequest imageRequest = new ImageRequest(
- "http://developer.android.com/images/home/aw_dac.png",
- new Response.Listener<Bitmap>() {
- @Override
- public void onResponse(Bitmap response) {
- imageView.setImageBitmap(response);
- }
- }, 0, 0, Config.RGB_565, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- imageView.setImageResource(R.drawable.default_image);
- }
- });
最後將這個ImageRequest對象添加到RequestQueue裏就可以了,如下所示:
- mQueue.add(imageRequest);
2. ImageLoader的用法
如果你覺得ImageRequest已經非常好用了,那我只能說你太容易滿足了 ^_^。實際上,Volley在請求網絡圖片方面可以做到的還遠遠不止這些,而ImageLoader就是一個很好的例子。ImageLoader也可以用於加載網絡上的圖片,並且它的內部也是使用ImageRequest來實現的,不過ImageLoader明顯要比ImageRequest更加高效,因爲它不僅可以幫我們對圖片進行緩存,還可以過濾掉重複的鏈接,避免重複發送請求。
由於ImageLoader已經不是繼承自Request的了,所以它的用法也和我們之前學到的內容有所不同,總結起來大致可以分爲以下四步:
1. 創建一個RequestQueue對象。
2. 創建一個ImageLoader對象。
3. 獲取一個ImageListener對象。
4. 調用ImageLoader的get()方法加載網絡上的圖片。
下面我們就來按照這個步驟,學習一下ImageLoader的用法吧。首先第一步的創建RequestQueue對象我們已經寫過很多遍了,相信已經不用再重複介紹了,那麼就從第二步開始學習吧,新建一個ImageLoader對象,代碼如下所示:
- ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
- @Override
- public void putBitmap(String url, Bitmap bitmap) {
- }
- @Override
- public Bitmap getBitmap(String url) {
- return null;
- }
- });
接下來需要獲取一個ImageListener對象,代碼如下所示:
- ImageListener listener = ImageLoader.getImageListener(imageView,
- R.drawable.default_image, R.drawable.failed_image);
最後,調用ImageLoader的get()方法來加載圖片,代碼如下所示:
- imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);
get()方法接收兩個參數,第一個參數就是圖片的URL地址,第二個參數則是剛剛獲取到的ImageListener對象。當然,如果你想對圖片的大小進行限制,也可以使用get()方法的重載,指定圖片允許的最大寬度和高度,如下所示:
- imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",
- listener, 200, 200);
現在運行一下程序並開始加載圖片,你將看到ImageView中會先顯示一張默認的圖片,等到網絡上的圖片加載完成後,ImageView則會自動顯示該圖,效果如下圖所示。
雖然現在我們已經掌握了ImageLoader的用法,但是剛纔介紹的ImageLoader的優點卻還沒有使用到。爲什麼呢?因爲這裏創建的ImageCache對象是一個空的實現,完全沒能起到圖片緩存的作用。其實寫一個ImageCache也非常簡單,但是如果想要寫一個性能非常好的ImageCache,最好就要藉助Android提供的LruCache功能了,如果你對LruCache還不瞭解,可以參考我之前的一篇博客Android高效加載大圖、多圖解決方案,有效避免程序OOM。
這裏我們新建一個BitmapCache並實現了ImageCache接口,如下所示:
- public class BitmapCache implements ImageCache {
- private LruCache<String, Bitmap> mCache;
- public BitmapCache() {
- int maxSize = 10 * 1024 * 1024;
- mCache = new LruCache<String, Bitmap>(maxSize) {
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- };
- }
- @Override
- public Bitmap getBitmap(String url) {
- return mCache.get(url);
- }
- @Override
- public void putBitmap(String url, Bitmap bitmap) {
- mCache.put(url, bitmap);
- }
- }
- ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
3. NetworkImageView的用法
除了以上兩種方式之外,Volley還提供了第三種方式來加載網絡圖片,即使用NetworkImageView。不同於以上兩種方式,NetworkImageView是一個自定義控制,它是繼承自ImageView的,具備ImageView控件的所有功能,並且在原生的基礎之上加入了加載網絡圖片的功能。NetworkImageView控件的用法要比前兩種方式更加簡單,大致可以分爲以下五步:
1. 創建一個RequestQueue對象。
2. 創建一個ImageLoader對象。
3. 在佈局文件中添加一個NetworkImageView控件。4. 在代碼中獲取該控件的實例。
5. 設置要加載的圖片地址。
其中,第一第二步和ImageLoader的用法是完全一樣的,因此這裏我們就從第三步開始學習了。首先修改佈局文件中的代碼,在裏面加入NetworkImageView控件,如下所示:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <Button
- android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Send Request" />
- <com.android.volley.toolbox.NetworkImageView
- android:id="@+id/network_image_view"
- android:layout_width="200dp"
- android:layout_height="200dp"
- android:layout_gravity="center_horizontal"
- />
- </LinearLayout>
- networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
- networkImageView.setDefaultImageResId(R.drawable.default_image);
- networkImageView.setErrorImageResId(R.drawable.failed_image);
- networkImageView.setImageUrl("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",
- imageLoader);
好了,就是這麼簡單,現在重新運行一下程序,你將看到和使用ImageLoader來加載圖片一模一樣的效果,這裏我就不再截圖了。
這時有的朋友可能就會問了,使用ImageRequest和ImageLoader這兩種方式來加載網絡圖片,都可以傳入一個最大寬度和高度的參數來對圖片進行壓縮,而NetworkImageView中則完全沒有提供設置最大寬度和高度的方法,那麼是不是使用NetworkImageView來加載的圖片都不會進行壓縮呢?
其實並不是這樣的,NetworkImageView並不需要提供任何設置最大寬高的方法也能夠對加載的圖片進行壓縮。這是由於NetworkImageView是一個控件,在加載圖片的時候它會自動獲取自身的寬高,然後對比網絡圖片的寬度,再決定是否需要對圖片進行壓縮。也就是說,壓縮過程是在內部完全自動化的,並不需要我們關心,NetworkImageView會始終呈現給我們一張大小剛剛好的網絡圖片,不會多佔用任何一點內存,這也是NetworkImageView最簡單好用的一點吧。
當然了,如果你不想對圖片進行壓縮的話,其實也很簡單,只需要在佈局文件中把NetworkImageView的layout_width和layout_height都設置成wrap_content就可以了,這樣NetworkImageView就會將該圖片的原始大小展示出來,不會進行任何壓縮。
這樣我們就把使用Volley來加載網絡圖片的用法都學習完了,今天的講解也就到此爲止,下一篇文章中我會帶大家繼續探究Volley的更多功能。
Android Volley完全解析(三),定製自己的Request
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/17612763
經過前面兩篇文章的學習,我們已經掌握了Volley各種Request的使用方法,包括StringRequest、JsonRequest、ImageRequest等。其中StringRequest用於請求一條普通的文本數據,JsonRequest(JsonObjectRequest、JsonArrayRequest)用於請求一條JSON格式的數據,ImageRequest則是用於請求網絡上的一張圖片。
可是Volley提供給我們的Request類型就只有這麼多,而我們都知道,在網絡上傳輸的數據通常有兩種格式,JSON和XML,那麼如果想要請求一條XML格式的數據該怎麼辦呢?其實很簡單,Volley提供了非常強的擴展機制,使得我們可以很輕鬆地定製出任意類型的Request,這也就是本篇文章的主題了。
在開始之前還是友情提醒一下,如果你還沒有閱讀過我前面兩篇關於Volley的文章,建議先去閱讀一下Android Volley完全解析(一),初識Volley的基本用法和Android Volley完全解析(二),使用Volley加載網絡圖片。
1. 自定義XMLRequest
下面我們準備自定義一個XMLRequest,用於請求一條XML格式的數據。那麼該從哪裏開始入手呢?額,好像是有些無從下手。遇到這種情況,我們應該去參考一下Volley的源碼,看一看StringRequest是怎麼實現的,然後就可以模仿着寫出XMLRequest了。首先看下StringRequest的源碼,如下所示:
- /**
- * A canned request for retrieving the response body at a given URL as a String.
- */
- public class StringRequest extends Request<String> {
- private final Listener<String> mListener;
- /**
- * Creates a new request with the given method.
- *
- * @param method the request {@link Method} to use
- * @param url URL to fetch the string at
- * @param listener Listener to receive the String response
- * @param errorListener Error listener, or null to ignore errors
- */
- public StringRequest(int method, String url, Listener<String> listener,
- ErrorListener errorListener) {
- super(method, url, errorListener);
- mListener = listener;
- }
- /**
- * Creates a new GET request.
- *
- * @param url URL to fetch the string at
- * @param listener Listener to receive the String response
- * @param errorListener Error listener, or null to ignore errors
- */
- public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
- this(Method.GET, url, listener, errorListener);
- }
- @Override
- protected void deliverResponse(String response) {
- mListener.onResponse(response);
- }
- @Override
- protected Response<String> parseNetworkResponse(NetworkResponse response) {
- String parsed;
- try {
- parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
- } catch (UnsupportedEncodingException e) {
- parsed = new String(response.data);
- }
- return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
- }
- }
可以看到,StringRequest的源碼很簡練,根本就沒幾行代碼,我們一起來分析下。首先StringRequest是繼承自Request類的,Request可以指定一個泛型類,這裏指定的當然就是String了,接下來StringRequest中提供了兩個有參的構造函數,參數包括請求類型,請求地址,以及響應回調等,由於我們已經很熟悉StringRequest的用法了,相信這幾個參數的作用都不用再解釋了吧。但需要注意的是,在構造函數中一定要調用super()方法將這幾個參數傳給父類,因爲HTTP的請求和響應都是在父類中自動處理的。
另外,由於Request類中的deliverResponse()和parseNetworkResponse()是兩個抽象方法,因此StringRequest中需要對這兩個方法進行實現。deliverResponse()方法中的實現很簡單,僅僅是調用了mListener中的onResponse()方法,並將response內容傳入即可,這樣就可以將服務器響應的數據進行回調了。parseNetworkResponse()方法中則應該對服務器響應的數據進行解析,其中數據是以字節的形式存放在NetworkResponse的data變量中的,這裏將數據取出然後組裝成一個String,並傳入Response的success()方法中即可。
瞭解了StringRequest的實現原理,下面我們就可以動手來嘗試實現一下XMLRequest了,代碼如下所示:
- public class XMLRequest extends Request<XmlPullParser> {
- private final Listener<XmlPullParser> mListener;
- public XMLRequest(int method, String url, Listener<XmlPullParser> listener,
- ErrorListener errorListener) {
- super(method, url, errorListener);
- mListener = listener;
- }
- public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
- this(Method.GET, url, listener, errorListener);
- }
- @Override
- protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {
- try {
- String xmlString = new String(response.data,
- HttpHeaderParser.parseCharset(response.headers));
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- XmlPullParser xmlPullParser = factory.newPullParser();
- xmlPullParser.setInput(new StringReader(xmlString));
- return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));
- } catch (UnsupportedEncodingException e) {
- return Response.error(new ParseError(e));
- } catch (XmlPullParserException e) {
- return Response.error(new ParseError(e));
- }
- }
- @Override
- protected void deliverResponse(XmlPullParser response) {
- mListener.onResponse(response);
- }
- }
可以看到,其實並沒有什麼太多的邏輯,基本都是仿照StringRequest寫下來的,XMLRequest也是繼承自Request類的,只不過這裏指定的泛型類是XmlPullParser,說明我們準備使用Pull解析的方式來解析XML。在parseNetworkResponse()方法中,先是將服務器響應的數據解析成一個字符串,然後設置到XmlPullParser對象中,在deliverResponse()方法中則是將XmlPullParser對象進行回調。
好了,就是這麼簡單,下面我們嘗試使用這個XMLRequest來請求一段XML格式的數據。http://flash.weather.com.cn/wmaps/xml/china.xml這個接口會將中國所有的省份數據以XML格式進行返回,如下所示:
- <china dn="day" slick-uniqueid="3">
- <city quName="黑龍江" pyName="heilongjiang" cityname="哈爾濱" state1="0" state2="0" stateDetailed="晴" tem1="18" tem2="6" windState="西北風3-4級轉西風小於3級"/>
- <city quName="吉林" pyName="jilin" cityname="長春" state1="0" state2="0" stateDetailed="晴" tem1="19" tem2="6" windState="西北風3-4級轉小於3級"/>
- <city quName="遼寧" pyName="liaoning" cityname="瀋陽" state1="0" state2="0" stateDetailed="晴" tem1="21" tem2="7" windState="東北風3-4級"/>
- <city quName="海南" pyName="hainan" cityname="海口" state1="1" state2="1" stateDetailed="多雲" tem1="30" tem2="24" windState="微風"/>
- <city quName="內蒙古" pyName="neimenggu" cityname="呼和浩特" state1="0" state2="0" stateDetailed="晴" tem1="19" tem2="5" windState="東風3-4級"/>
- <city quName="新疆" pyName="xinjiang" cityname="烏魯木齊" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="10" windState="微風轉東南風小於3級"/>
- <city quName="西藏" pyName="xizang" cityname="拉薩" state1="1" state2="7" stateDetailed="多雲轉小雨" tem1="18" tem2="4" windState="微風"/>
- <city quName="青海" pyName="qinghai" cityname="西寧" state1="0" state2="1" stateDetailed="晴轉多雲" tem1="18" tem2="2" windState="微風"/>
- <city quName="寧夏" pyName="ningxia" cityname="銀川" state1="0" state2="0" stateDetailed="晴" tem1="19" tem2="8" windState="微風"/>
- <city quName="甘肅" pyName="gansu" cityname="蘭州" state1="0" state2="0" stateDetailed="晴" tem1="21" tem2="6" windState="微風"/>
- <city quName="河北" pyName="hebei" cityname="石家莊" state1="0" state2="0" stateDetailed="晴" tem1="25" tem2="12" windState="北風小於3級"/>
- <city quName="河南" pyName="henan" cityname="鄭州" state1="0" state2="0" stateDetailed="晴" tem1="24" tem2="13" windState="微風"/>
- <city quName="湖北" pyName="hubei" cityname="武漢" state1="0" state2="0" stateDetailed="晴" tem1="24" tem2="12" windState="微風"/>
- <city quName="湖南" pyName="hunan" cityname="長沙" state1="2" state2="1" stateDetailed="陰轉多雲" tem1="20" tem2="15" windState="北風小於3級"/>
- <city quName="山東" pyName="shandong" cityname="濟南" state1="1" state2="1" stateDetailed="多雲" tem1="20" tem2="10" windState="北風3-4級轉小於3級"/>
- <city quName="江蘇" pyName="jiangsu" cityname="南京" state1="2" state2="2" stateDetailed="陰" tem1="19" tem2="13" windState="西北風4-5級轉3-4級"/>
- <city quName="安徽" pyName="anhui" cityname="合肥" state1="2" state2="1" stateDetailed="陰轉多雲" tem1="20" tem2="12" windState="西北風轉北風3-4級"/>
- <city quName="山西" pyName="shanxi" cityname="太原" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="8" windState="微風"/>
- <city quName="陝西" pyName="sanxi" cityname="西安" state1="1" state2="0" stateDetailed="多雲轉晴" tem1="21" tem2="9" windState="東北風小於3級"/>
- <city quName="四川" pyName="sichuan" cityname="成都" state1="1" state2="1" stateDetailed="多雲" tem1="26" tem2="15" windState="南風小於3級"/>
- <city quName="雲南" pyName="yunnan" cityname="昆明" state1="7" state2="7" stateDetailed="小雨" tem1="21" tem2="13" windState="微風"/>
- <city quName="貴州" pyName="guizhou" cityname="貴陽" state1="1" state2="3" stateDetailed="多雲轉陣雨" tem1="21" tem2="11" windState="東風小於3級"/>
- <city quName="浙江" pyName="zhejiang" cityname="杭州" state1="3" state2="1" stateDetailed="陣雨轉多雲" tem1="22" tem2="14" windState="微風"/>
- <city quName="福建" pyName="fujian" cityname="福州" state1="1" state2="2" stateDetailed="多雲轉陰" tem1="28" tem2="18" windState="微風"/>
- <city quName="江西" pyName="jiangxi" cityname="南昌" state1="2" state2="1" stateDetailed="陰轉多雲" tem1="23" tem2="15" windState="北風3-4級轉微風"/>
- <city quName="廣東" pyName="guangdong" cityname="廣州" state1="3" state2="2" stateDetailed="陣雨轉陰" tem1="26" tem2="20" windState="微風"/>
- <city quName="廣西" pyName="guangxi" cityname="南寧" state1="3" state2="3" stateDetailed="陣雨" tem1="23" tem2="19" windState="東北風小於3級"/>
- <city quName="北京" pyName="beijing" cityname="北京" state1="0" state2="0" stateDetailed="晴" tem1="26" tem2="10" windState="微風"/>
- <city quName="天津" pyName="tianjin" cityname="天津" state1="1" state2="0" stateDetailed="多雲轉晴" tem1="22" tem2="13" windState="東北風3-4級轉小於3級"/>
- <city quName="上海" pyName="shanghai" cityname="上海" state1="7" state2="1" stateDetailed="小雨轉多雲" tem1="20" tem2="16" windState="西北風3-4級"/>
- <city quName="重慶" pyName="chongqing" cityname="重慶" state1="1" state2="3" stateDetailed="多雲轉陣雨" tem1="21" tem2="14" windState="微風"/>
- <city quName="香港" pyName="xianggang" cityname="香港" state1="3" state2="1" stateDetailed="陣雨轉多雲" tem1="26" tem2="22" windState="微風"/>
- <city quName="澳門" pyName="aomen" cityname="澳門" state1="3" state2="1" stateDetailed="陣雨轉多雲" tem1="27" tem2="22" windState="東北風3-4級轉微風"/>
- <city quName="臺灣" pyName="taiwan" cityname="臺北" state1="9" state2="7" stateDetailed="大雨轉小雨" tem1="28" tem2="21" windState="微風"/>
- <city quName="西沙" pyName="xisha" cityname="西沙" state1="3" state2="3" stateDetailed="陣雨" tem1="30" tem2="26" windState="東北風4-5級"/>
- <city quName="南沙" pyName="nanshadao" cityname="南沙" state1="1" state2="1" stateDetailed="多雲" tem1="32" tem2="27" windState="東風4-5級"/>
- <city quName="釣魚島" pyName="diaoyudao" cityname="釣魚島" state1="7" state2="1" stateDetailed="小雨轉多雲" tem1="23" tem2="19" windState="西南風3-4級轉北風5-6級"/>
- </china>
- XMLRequest xmlRequest = new XMLRequest(
- "http://flash.weather.com.cn/wmaps/xml/china.xml",
- new Response.Listener<XmlPullParser>() {
- @Override
- public void onResponse(XmlPullParser response) {
- try {
- int eventType = response.getEventType();
- while (eventType != XmlPullParser.END_DOCUMENT) {
- switch (eventType) {
- case XmlPullParser.START_TAG:
- String nodeName = response.getName();
- if ("city".equals(nodeName)) {
- String pName = response.getAttributeValue(0);
- Log.d("TAG", "pName is " + pName);
- }
- break;
- }
- eventType = response.next();
- }
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- Log.e("TAG", error.getMessage(), error);
- }
- });
- mQueue.add(xmlRequest);
現在運行一下代碼,觀察控制檯日誌,就可以看到每個省的名字都從XML中解析出來了,如下圖所示。
2. 自定義GsonRequest
JsonRequest的數據解析是利用Android本身自帶的JSONObject和JSONArray來實現的,配合使用JSONObject和JSONArray就可以解析出任意格式的JSON數據。不過也許你會覺得使用JSONObject還是太麻煩了,還有很多方法可以讓JSON數據解析變得更加簡單,比如說GSON。遺憾的是,Volley中默認並不支持使用自家的GSON來解析數據,不過沒有關係,通過上面的學習,相信你已經知道了自定義一個Request是多麼的簡單,那麼下面我們就來舉一反三一下,自定義一個GsonRequest。
首先我們需要把gson的jar包添加到項目當中,jar包的下載地址是:https://code.google.com/p/google-gson/downloads/list 。
接着定義一個GsonRequest繼承自Request,代碼如下所示:
- public class GsonRequest<T> extends Request<T> {
- private final Listener<T> mListener;
- private Gson mGson;
- private Class<T> mClass;
- public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
- ErrorListener errorListener) {
- super(method, url, errorListener);
- mGson = new Gson();
- mClass = clazz;
- mListener = listener;
- }
- public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
- ErrorListener errorListener) {
- this(Method.GET, url, clazz, listener, errorListener);
- }
- @Override
- protected Response<T> parseNetworkResponse(NetworkResponse response) {
- try {
- String jsonString = new String(response.data,
- HttpHeaderParser.parseCharset(response.headers));
- return Response.success(mGson.fromJson(jsonString, mClass),
- HttpHeaderParser.parseCacheHeaders(response));
- } catch (UnsupportedEncodingException e) {
- return Response.error(new ParseError(e));
- }
- }
- @Override
- protected void deliverResponse(T response) {
- mListener.onResponse(response);
- }
- }
那麼下面我們就來測試一下這個GsonRequest能不能夠正常工作吧,調用http://www.weather.com.cn/data/sk/101010100.html這個接口可以得到一段JSON格式的天氣數據,如下所示:
- {"weatherinfo":{"city":"北京","cityid":"101010100","temp":"19","WD":"南風","WS":"2級","SD":"43%","WSE":"2","time":"19:45","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}
- public class Weather {
- private WeatherInfo weatherinfo;
- public WeatherInfo getWeatherinfo() {
- return weatherinfo;
- }
- public void setWeatherinfo(WeatherInfo weatherinfo) {
- this.weatherinfo = weatherinfo;
- }
- }
- public class WeatherInfo {
- private String city;
- private String temp;
- private String time;
- public String getCity() {
- return city;
- }
- public void setCity(String city) {
- this.city = city;
- }
- public String getTemp() {
- return temp;
- }
- public void setTemp(String temp) {
- this.temp = temp;
- }
- public String getTime() {
- return time;
- }
- public void setTime(String time) {
- this.time = time;
- }
- }
- GsonRequest<Weather> gsonRequest = new GsonRequest<Weather>(
- "http://www.weather.com.cn/data/sk/101010100.html", Weather.class,
- new Response.Listener<Weather>() {
- @Override
- public void onResponse(Weather weather) {
- WeatherInfo weatherInfo = weather.getWeatherinfo();
- Log.d("TAG", "city is " + weatherInfo.getCity());
- Log.d("TAG", "temp is " + weatherInfo.getTemp());
- Log.d("TAG", "time is " + weatherInfo.getTime());
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- Log.e("TAG", error.getMessage(), error);
- }
- });
- mQueue.add(gsonRequest);
這樣的話,XMLRequest和GsonRequest的功能就基本都實現了,我們也是藉助這兩個例子深刻地理解了自定義Request的方法,對Volley的認識也是更加深入了。好了,本篇文章就到此結束,下篇文章中我們將對Volley進行更深層次的研究
Android Volley完全解析(四),帶你從源碼的角度理解Volley
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/17656437
經過前三篇文章的學習,Volley的用法我們已經掌握的差不多了,但是對於Volley的工作原理,恐怕有很多朋友還不是很清楚。因此,本篇文章中我們就來一起閱讀一下Volley的源碼,將它的工作流程整體地梳理一遍。同時,這也是Volley系列的最後一篇文章了。
其實,Volley的官方文檔中本身就附有了一張Volley的工作流程圖,如下圖所示。
多數朋友突然看到一張這樣的圖,應該會和我一樣,感覺一頭霧水吧?沒錯,目前我們對Volley背後的工作原理還沒有一個概念性的理解,直接就來看這張圖自然會有些吃力。不過沒關係,下面我們就去分析一下Volley的源碼,之後再重新來看這張圖就會好理解多了。
說起分析源碼,那麼應該從哪兒開始看起呢?這就要回顧一下Volley的用法了,還記得嗎,使用Volley的第一步,首先要調用Volley.newRequestQueue(context)方法來獲取一個RequestQueue對象,那麼我們自然要從這個方法開始看起了,代碼如下所示:
- public static RequestQueue newRequestQueue(Context context) {
- return newRequestQueue(context, null);
- }
- public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
- File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
- String userAgent = "volley/0";
- try {
- String packageName = context.getPackageName();
- PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
- userAgent = packageName + "/" + info.versionCode;
- } catch (NameNotFoundException e) {
- }
- if (stack == null) {
- if (Build.VERSION.SDK_INT >= 9) {
- stack = new HurlStack();
- } else {
- stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
- }
- }
- Network network = new BasicNetwork(stack);
- RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
- queue.start();
- return queue;
- }
創建好了HttpStack之後,接下來又創建了一個Network對象,它是用於根據傳入的HttpStack對象來處理網絡請求的,緊接着new出一個RequestQueue對象,並調用它的start()方法進行啓動,然後將RequestQueue返回,這樣newRequestQueue()的方法就執行結束了。
那麼RequestQueue的start()方法內部到底執行了什麼東西呢?我們跟進去瞧一瞧:
- public void start() {
- stop(); // Make sure any currently running dispatchers are stopped.
- // Create the cache dispatcher and start it.
- mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
- mCacheDispatcher.start();
- // Create network dispatchers (and corresponding threads) up to the pool size.
- for (int i = 0; i < mDispatchers.length; i++) {
- NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
- mCache, mDelivery);
- mDispatchers[i] = networkDispatcher;
- networkDispatcher.start();
- }
- }
得到了RequestQueue之後,我們只需要構建出相應的Request,然後調用RequestQueue的add()方法將Request傳入就可以完成網絡請求操作了,那麼不用說,add()方法的內部肯定有着非常複雜的邏輯,我們來一起看一下:
- public <T> Request<T> add(Request<T> request) {
- // Tag the request as belonging to this queue and add it to the set of current requests.
- request.setRequestQueue(this);
- synchronized (mCurrentRequests) {
- mCurrentRequests.add(request);
- }
- // Process requests in the order they are added.
- request.setSequence(getSequenceNumber());
- request.addMarker("add-to-queue");
- // If the request is uncacheable, skip the cache queue and go straight to the network.
- if (!request.shouldCache()) {
- mNetworkQueue.add(request);
- return request;
- }
- // Insert request into stage if there's already a request with the same cache key in flight.
- synchronized (mWaitingRequests) {
- String cacheKey = request.getCacheKey();
- if (mWaitingRequests.containsKey(cacheKey)) {
- // There is already a request in flight. Queue up.
- Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
- if (stagedRequests == null) {
- stagedRequests = new LinkedList<Request<?>>();
- }
- stagedRequests.add(request);
- mWaitingRequests.put(cacheKey, stagedRequests);
- if (VolleyLog.DEBUG) {
- VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
- }
- } else {
- // Insert 'null' queue for this cacheKey, indicating there is now a request in
- // flight.
- mWaitingRequests.put(cacheKey, null);
- mCacheQueue.add(request);
- }
- return request;
- }
- }
OK,那麼既然默認每條請求都是可以緩存的,自然就被添加到了緩存隊列中,於是一直在後臺等待的緩存線程就要開始運行起來了,我們看下CacheDispatcher中的run()方法,代碼如下所示:
- public class CacheDispatcher extends Thread {
- ……
- @Override
- public void run() {
- if (DEBUG) VolleyLog.v("start new dispatcher");
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- // Make a blocking call to initialize the cache.
- mCache.initialize();
- while (true) {
- try {
- // Get a request from the cache triage queue, blocking until
- // at least one is available.
- final Request<?> request = mCacheQueue.take();
- request.addMarker("cache-queue-take");
- // If the request has been canceled, don't bother dispatching it.
- if (request.isCanceled()) {
- request.finish("cache-discard-canceled");
- continue;
- }
- // Attempt to retrieve this item from cache.
- Cache.Entry entry = mCache.get(request.getCacheKey());
- if (entry == null) {
- request.addMarker("cache-miss");
- // Cache miss; send off to the network dispatcher.
- mNetworkQueue.put(request);
- continue;
- }
- // If it is completely expired, just send it to the network.
- if (entry.isExpired()) {
- request.addMarker("cache-hit-expired");
- request.setCacheEntry(entry);
- mNetworkQueue.put(request);
- continue;
- }
- // We have a cache hit; parse its data for delivery back to the request.
- request.addMarker("cache-hit");
- Response<?> response = request.parseNetworkResponse(
- new NetworkResponse(entry.data, entry.responseHeaders));
- request.addMarker("cache-hit-parsed");
- if (!entry.refreshNeeded()) {
- // Completely unexpired cache hit. Just deliver the response.
- mDelivery.postResponse(request, response);
- } else {
- // Soft-expired cache hit. We can deliver the cached response,
- // but we need to also send the request to the network for
- // refreshing.
- request.addMarker("cache-hit-refresh-needed");
- request.setCacheEntry(entry);
- // Mark the response as intermediate.
- response.intermediate = true;
- // Post the intermediate response back to the user and have
- // the delivery then forward the request along to the network.
- mDelivery.postResponse(request, response, new Runnable() {
- @Override
- public void run() {
- try {
- mNetworkQueue.put(request);
- } catch (InterruptedException e) {
- // Not much we can do about this.
- }
- }
- });
- }
- } catch (InterruptedException e) {
- // We may have been interrupted because it was time to quit.
- if (mQuit) {
- return;
- }
- continue;
- }
- }
- }
- }
- public class NetworkDispatcher extends Thread {
- ……
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- Request<?> request;
- while (true) {
- try {
- // Take a request from the queue.
- request = mQueue.take();
- } catch (InterruptedException e) {
- // We may have been interrupted because it was time to quit.
- if (mQuit) {
- return;
- }
- continue;
- }
- try {
- request.addMarker("network-queue-take");
- // If the request was cancelled already, do not perform the
- // network request.
- if (request.isCanceled()) {
- request.finish("network-discard-cancelled");
- continue;
- }
- addTrafficStatsTag(request);
- // Perform the network request.
- NetworkResponse networkResponse = mNetwork.performRequest(request);
- request.addMarker("network-http-complete");
- // If the server returned 304 AND we delivered a response already,
- // we're done -- don't deliver a second identical response.
- if (networkResponse.notModified && request.hasHadResponseDelivered()) {
- request.finish("not-modified");
- continue;
- }
- // Parse the response here on the worker thread.
- Response<?> response = request.parseNetworkResponse(networkResponse);
- request.addMarker("network-parse-complete");
- // Write to cache if applicable.
- // TODO: Only update cache metadata instead of entire record for 304s.
- if (request.shouldCache() && response.cacheEntry != null) {
- mCache.put(request.getCacheKey(), response.cacheEntry);
- request.addMarker("network-cache-written");
- }
- // Post the response back.
- request.markDelivered();
- mDelivery.postResponse(request, response);
- } catch (VolleyError volleyError) {
- parseAndDeliverNetworkError(request, volleyError);
- } catch (Exception e) {
- VolleyLog.e(e, "Unhandled exception %s", e.toString());
- mDelivery.postError(request, new VolleyError(e));
- }
- }
- }
- }
- public class BasicNetwork implements Network {
- ……
- @Override
- public NetworkResponse performRequest(Request<?> request) throws VolleyError {
- long requestStart = SystemClock.elapsedRealtime();
- while (true) {
- HttpResponse httpResponse = null;
- byte[] responseContents = null;
- Map<String, String> responseHeaders = new HashMap<String, String>();
- try {
- // Gather headers.
- Map<String, String> headers = new HashMap<String, String>();
- addCacheHeaders(headers, request.getCacheEntry());
- httpResponse = mHttpStack.performRequest(request, headers);
- StatusLine statusLine = httpResponse.getStatusLine();
- int statusCode = statusLine.getStatusCode();
- responseHeaders = convertHeaders(httpResponse.getAllHeaders());
- // Handle cache validation.
- if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
- return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
- request.getCacheEntry() == null ? null : request.getCacheEntry().data,
- responseHeaders, true);
- }
- // Some responses such as 204s do not have content. We must check.
- if (httpResponse.getEntity() != null) {
- responseContents = entityToBytes(httpResponse.getEntity());
- } else {
- // Add 0 byte response as a way of honestly representing a
- // no-content request.
- responseContents = new byte[0];
- }
- // if the request is slow, log it.
- long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
- logSlowRequests(requestLifetime, request, responseContents, statusLine);
- if (statusCode < 200 || statusCode > 299) {
- throw new IOException();
- }
- return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
- } catch (Exception e) {
- ……
- }
- }
- }
- }
這段方法中大多都是一些網絡請求細節方面的東西,我們並不需要太多關心,需要注意的是在第14行調用了HttpStack的performRequest()方法,這裏的HttpStack就是在一開始調用newRequestQueue()方法是創建的實例,默認情況下如果系統版本號大於9就創建的HurlStack對象,否則創建HttpClientStack對象。前面已經說過,這兩個對象的內部實際就是分別使用HttpURLConnection和HttpClient來發送網絡請求的,我們就不再跟進去閱讀了,之後會將服務器返回的數據組裝成一個NetworkResponse對象進行返回。
在NetworkDispatcher中收到了NetworkResponse這個返回值後又會調用Request的parseNetworkResponse()方法來解析NetworkResponse中的數據,以及將數據寫入到緩存,這個方法的實現是交給Request的子類來完成的,因爲不同種類的Request解析的方式也肯定不同。還記得我們在上一篇文章中學習的自定義Request的方式嗎?其中parseNetworkResponse()這個方法就是必須要重寫的。
在解析完了NetworkResponse中的數據之後,又會調用ExecutorDelivery的postResponse()方法來回調解析出的數據,代碼如下所示:
- public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
- request.markDelivered();
- request.addMarker("post-response");
- mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
- }
- private class ResponseDeliveryRunnable implements Runnable {
- private final Request mRequest;
- private final Response mResponse;
- private final Runnable mRunnable;
- public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
- mRequest = request;
- mResponse = response;
- mRunnable = runnable;
- }
- @SuppressWarnings("unchecked")
- @Override
- public void run() {
- // If this request has canceled, finish it and don't deliver.
- if (mRequest.isCanceled()) {
- mRequest.finish("canceled-at-delivery");
- return;
- }
- // Deliver a normal response or error, depending.
- if (mResponse.isSuccess()) {
- mRequest.deliverResponse(mResponse.result);
- } else {
- mRequest.deliverError(mResponse.error);
- }
- // If this is an intermediate response, add a marker, otherwise we're done
- // and the request can be finished.
- if (mResponse.intermediate) {
- mRequest.addMarker("intermediate-response");
- } else {
- mRequest.finish("done");
- }
- // If we have been provided a post-delivery runnable, run it.
- if (mRunnable != null) {
- mRunnable.run();
- }
- }
- }
代碼雖然不多,但我們並不需要行行閱讀,抓住重點看即可。其中在第22行調用了Request的deliverResponse()方法,有沒有感覺很熟悉?沒錯,這個就是我們在自定義Request時需要重寫的另外一個方法,每一條網絡請求的響應都是回調到這個方法中,最後我們再在這個方法中將響應的數據回調到Response.Listener的onResponse()方法中就可以了。
好了,到這裏我們就把Volley的完整執行流程全部梳理了一遍,你是不是已經感覺已經很清晰了呢?對了,還記得在文章一開始的那張流程圖嗎,剛纔還不能理解,現在我們再來重新看下這張圖:
其中藍色部分代表主線程,綠色部分代表緩存線程,橙色部分代表網絡線程。我們在主線程中調用RequestQueue的add()方法來添加一條網絡請求,這條請求會先被加入到緩存隊列當中,如果發現可以找到相應的緩存結果就直接讀取緩存並解析,然後回調給主線程。如果在緩存中沒有找到結果,則將這條請求加入到網絡請求隊列中,然後處理髮送HTTP請求,解析響應結果,寫入緩存,並回調主線程。
怎麼樣,是不是感覺現在理解這張圖已經變得輕鬆簡單了?好了,到此爲止我們就把Volley的用法和源碼全部學習完了,相信你已經對Volley非常熟悉並可以將它應用到實際項目當中了,那麼Volley完全解析系列的文章到此結束,感謝大家有耐心看到最後。