1. 程式人生 > >volley原始碼解析(一)--volley的使用和架構

volley原始碼解析(一)--volley的使用和架構

Volley是一款由Google 推出的Android非同步網路請求框架和圖片載入框架,特別適合資料量小,通訊頻繁的網路操作。

Volley 的主要特點

(1). 擴充套件性強。Volley 中大多是基於介面的設計,可配置性強。
(2). 一定程度符合 Http 規範,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的處理,請求頭的處理,快取機制的支援等。並支援重試及優先順序定義。
(3). 預設 Android2.3 及以上基於 HttpURLConnection,2.3 以下基於 HttpClient 實現
(4). 提供簡便的圖片載入工具。

本專欄主要通過解析volley的原始碼,對它的整個

架構進行分析,指出它這樣設計架構,對應擴充套件性,耦合等優勢。同時,指出volley對於網路請求,考慮到的細節方面,用於指導我們日後自己寫網路框架的需求。另外還會根據volley的設計,說明它所應用的場景,volley所面臨的不足,以及它本身可以怎麼被更完善的擴充套件。

本篇文章作為專欄的開篇,會先給簡單大家介紹volley的使用和volley的整個架構設計,通過對架構的介紹,希望大家對volley的實現模式有個大體的印象。在這篇文章中,大家不必要關注實現的細節(因為在接下來的文章會詳細介紹),主要做到的是體會volley的設計思路,關注整體的實現。

網上介紹volley如何使用的文章有很多,大家可以自行搜尋一下,這裡我舉一個簡單的例子說明如何使用volley進行網路請求。

  1. StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
  2.     new Response.Listener<String>() {  
  3.         @Override
  4.         publicvoid onResponse(String response) {  
  5.             //處理響應
  6.         }  
  7.     }, new Response.ErrorListener() {  
  8.         @Override
  9.             publicvoid
     onErrorResponse(VolleyError error) {  
  10.                //處理錯誤
  11.         }  
  12.     });  

有上面的程式碼我們可以看出,我們例項化了一個StringRequest物件,其中有一個網路地址www.baidu.com,兩個監聽器

觀察監聽器裡面的方法,一個引數是String response,一個是VolleyError error

從字面上的意思理解,我們建立了一個網路請求物件request,傳入請求的地址,請求成功以後,會回撥兩個監聽器的方法,兩個監聽器分別對應請求成功與請求失敗的情況。

OK,是不是隻要這樣就已經開啟了執行緒去請求資料了呢。沒有這麼簡單,我們還要:

  1. RequestQueue mQueue = Volley.newRequestQueue(context); //建立請求佇列
  2. mQueue.add(stringRequest); //將請求新增到請求佇列
上面的操作,將我們構造的請求,新增到一個新建的請求佇列裡面。到此為止,我們就成功地發出了請求,並且在請求結束以後,會回撥我們request裡面的方法。

這就是volley最簡單的使用。

有人會問,為什麼要將請求加入佇列裡面,我直接new一個Request,然後在Request內部建立執行緒,請求資料,最後回撥介面不就好了嗎?

這樣想是沒有錯,但是volley這樣設計必然有自己的原因,就耦合過緊這一點而言,就可以知道我們上面提出的策略並不是那麼的優雅(不代表不可行)。

上面的圖有點複雜,大家先不要糾結,我們先來看我們之前的結論,volley的使用過程是

1,建立request

2,將request放入佇列RequestQueue

3,處理回撥結果

注意到例子中使用的StringRequest,對應上面的圖,找到Request和StringRequest可以知道,StringRequest是Request的一個子類,從字面上可以知道,這是一個字串請求,另外還有json,image請求等,都是request的子類,所以我們設想,是不是不同的請求,有一個不同的類呢?

正是這樣的,從面向物件的角度,我們把請求看成是一個類,所以我們每次的網路請求,只有new出一個類,然後加入佇列就可以了。

那麼佇列裡面是怎麼樣替我請求資料的呢?這裡我給大家介紹其中的奧祕,大家只要瞭解過程就可以,不必深究細節。

其實RequestQueue裡面有兩個佇列,一個我稱為快取佇列mCacheQueue,一個稱為網路佇列mNetworkQueue

如果請求要求加入快取佇列(例如我們給request設定一個屬性ShouldCache,然後提供set方法來設定),將會試圖從硬碟快取中獲取資料,如果沒有快取,這個請求將被放入網路佇列

如果請求不要求快取,則直接加入網路佇列。

加入佇列以後,我們開啟執行緒,從佇列中取出請求。

可想而知,我們最好有一個執行緒CacheDispatcher從快取佇列中取,一個NetworkDispatcher從網路佇列中取,然而網路請求往往大量,所以volley實際上有多個執行緒同時從網路佇列中取出請求(這裡涉及執行緒同步,volley使用PriorityBlockingQueue解決)

為什麼要先建立幾個執行緒,從佇列中取,而不是每個request開啟一個執行緒呢?這樣做的好處是避免重複大量建立執行緒所帶來的開銷,另外由於所有的request都存在在一個RequestQueue裡面,便於我們對request的管理,例如我們要關閉某個request,又或者我們請求了很多相同的request,對應這些操作,我們如果將request分散,是很難統一解決的,所以這樣用類似執行緒池的思想,統一管理執行緒。

同時,這樣做又會帶來不利,因為實際請求執行緒的執行緒數目是固定的,意味著當request數目大於執行緒數目時,有的執行緒將被阻塞,造成效率下降,更多的問題,會在接下來的文章提到。

至於CacheDispatcher和NetworkDispatcher是怎麼請求資料的呢?

對於NetworkDispatcher而言,必然是開啟網路連線,然後獲取資料的(例如url.openConnection),這是我們的常用實現,先不做詳細解釋(volley對這些實現進行了更詳細的封裝)

再來考慮,獲得結果以後,我們怎麼回撥。

還是面向物件的思路,volley將響應結果封裝成一個repsonse類(和request對應)

對應NetworkDispatcher而言,在它的run()方法裡面,取得request以後,根據url請求資料,將資料封裝成respsonse物件,再有一個分發器ResponseDelivery分發到對應的request

有人會問?解析到response以後,我們給request設計一個方法(例如將parseRespose(Respsonse respsonse))用於使用response,同時在這個方法內,回撥監聽器不就好了嗎?為什麼要多此一舉,建立一個分發器呢?

原因是這樣更靈活,但是還有一個重要的原因是,注意到我們回撥,往往是在主執行緒中進行的(因為很可能要操作UI),如果我們在NetworkDispatcher(子執行緒)裡面,直接回調,可能造成錯誤,這是ResponseDelivery存在的另外一個原因。

根據上面的結論,最後來看一張簡單的流程圖


根據流程分析,我們可以體會到,volley設計框架的基本思路,對比於我們簡單的實現,volley的實現方式耦合更加鬆散,使用面向介面程式設計,同時使用更多組合方式而不是繼承。使用了代理等設計模式,同時提高了執行緒的利用率。總之volley的架構設計又各種各樣的好處。

我在這裡介紹幾個volley的功能,以及它考慮到的,而我們很可能沒有考慮到的問題。這些問題或者說功能上的優勢,會伴隨著本專欄的深入讓大家逐漸體會。

下面只是籠統的介紹,大家可以對比自己的想法,看看自己是不是有什麼考慮不周的(如果是你實現這樣一個框架的話)

1,Request的設計,我們在得到response之後,我們可能根據專案需求希望有不同形式的資料(例如string,bitmap,jsonObject),volley使用抽象程式設計,讓我們可以繼承Request實現自己對response的解析方式(意味著處理volley為我們提供的StringRequest類等,我們自定義request)

2,重試策略,網路請求可能因為網路原因失敗(例如手機斷網了),volley為我們提供了重試策略

3,終止請求,例如請求資料途中,我們希望終止該請求

4,request在佇列中優先順序的問題,例如我們有的請求比較緊迫,就應該排在佇列的前面

5,重複請求的問題,利用有多個相同的request在佇列之中,請求到其中一個,其他的可能就不必請求了,直接從快取中取

6,異常的處理,例如io,403,402等授權錯

7,地址重定向處理

8,網路問題的處理,例如我們斷網了,volley使用了network這個類來處理這一類問題

9,使用HttpClient還是url.openConnection()去請求資料呢?

10,快取的讀取和儲存,我們請求到網路資料以後,可以存入本地快取

11,如何設定請求日誌來記錄請求資訊,用於除錯?

12,對於快取寫入和讀取的效率優化

13,圖片請求的處理

現在就讓我們來小試牛刀,先來看Volley這個類,Volley作為整個框架的入口,其實就是建立了一個RequestQueue佇列

  1. publicclass Volley {  
  2.     /**  
  3.      * Default on-disk cache directory. 
  4.      * 預設快取目錄名  
  5.      */
  6.     privatestaticfinal String DEFAULT_CACHE_DIR = "volley";  
  7.     /** 
  8.      * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 
  9.      * You may set a maximum size of the disk cache in bytes. 
  10.      * 建立一個預設工作池,並且啟動請求佇列 
  11.      * @param context A {@link Context} to use for creating the cache dir.用於建立快取目錄 
  12.      * @param stack An {@link HttpStack} to use for the network, or null for default. 
  13.      * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size. 
  14.      * 硬碟快取最大值,-1則預設 
  15.      * @return A started {@link RequestQueue} instance. 
  16.      */
  17.     publicstatic RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {  
  18.         File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);//快取佇列
  19.         String userAgent = "volley/0";  
  20.         try {  
  21.             String packageName = context.getPackageName();//包名
  22.             PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);  
  23.             userAgent = packageName + "/" + info.versionCode;  
  24.         } catch (NameNotFoundException e) {  
  25.         }  
  26.         if (stack == null) {//如果沒有限定stack
  27.             if (Build.VERSION.SDK_INT >= 9) {//adk版本在9或者以上
  28.                 stack = new HurlStack();  
  29.             } else {  
  30.                 // Prior to Gingerbread, HttpUrlConnection was unreliable.
  31.                 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  32.                 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));  
  33.             }  
  34.         }  
  35.         Network network = new BasicNetwork(stack);  
  36.         RequestQueue queue;  
  37.         if (maxDiskCacheBytes <= -1)//小於等於-1,則預設值
  38.         {  
  39.             // No maximum size specified
  40.             queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  
  41.         }  
  42.         else
  43.         {  
  44.             // Disk cache size specified
  45.             queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);  
  46.         }  
  47.         queue.start();  
  48.         return queue;  
  49.     }  
可以看到,Volley的newRequestQueue()方法裡面,根據版本建立了stack(分別是HurlStack和HttpClientStack)。至於不同adk版本會建立不同的stack,是由於android的版本原因,在9以上版本使用HurlStack比HttpClientStack更加好。

然後建立了網路類BasicNetwork,快取類DiskBasedCache,然後使用這兩個來建立RequestQueue物件。

或許現在大家還不明白這兩個類的作用,大家只需要記得建立RequestQueue需要這兩個類就可以了。

OK,作為本專欄的開篇文章,主要是為大家提供閱讀的興趣和思路。在接下來的文章裡面,我就會結合具體的原始碼,來說明volley的具體實現。