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進行網路請求。
- StringRequest stringRequest = new StringRequest("http://www.baidu.com",
- new Response.Listener<String>() {
- @Override
- publicvoid onResponse(String response) {
- //處理響應
- }
- }, new Response.ErrorListener() {
- @Override
-
publicvoid
- //處理錯誤
- }
- });
有上面的程式碼我們可以看出,我們例項化了一個StringRequest物件,其中有一個網路地址www.baidu.com,兩個監聽器
觀察監聽器裡面的方法,一個引數是String response,一個是VolleyError error
從字面上的意思理解,我們建立了一個網路請求物件request,傳入請求的地址,請求成功以後,會回撥兩個監聽器的方法,兩個監聽器分別對應請求成功與請求失敗的情況。
OK,是不是隻要這樣就已經開啟了執行緒去請求資料了呢。沒有這麼簡單,我們還要:
- RequestQueue mQueue = Volley.newRequestQueue(context); //建立請求佇列
- mQueue.add(stringRequest); //將請求新增到請求佇列
這就是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佇列
- publicclass Volley {
- /**
- * Default on-disk cache directory.
- * 預設快取目錄名
- */
- privatestaticfinal String DEFAULT_CACHE_DIR = "volley";
- /**
- * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
- * You may set a maximum size of the disk cache in bytes.
- * 建立一個預設工作池,並且啟動請求佇列
- * @param context A {@link Context} to use for creating the cache dir.用於建立快取目錄
- * @param stack An {@link HttpStack} to use for the network, or null for default.
- * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
- * 硬碟快取最大值,-1則預設
- * @return A started {@link RequestQueue} instance.
- */
- publicstatic RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
- 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) {//如果沒有限定stack
- if (Build.VERSION.SDK_INT >= 9) {//adk版本在9或者以上
- stack = new HurlStack();
- } else {
- // Prior to Gingerbread, HttpUrlConnection was unreliable.
- // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
- stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
- }
- }
- Network network = new BasicNetwork(stack);
- RequestQueue queue;
- if (maxDiskCacheBytes <= -1)//小於等於-1,則預設值
- {
- // No maximum size specified
- queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
- }
- else
- {
- // Disk cache size specified
- queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
- }
- queue.start();
- return queue;
- }
然後建立了網路類BasicNetwork,快取類DiskBasedCache,然後使用這兩個來建立RequestQueue物件。
或許現在大家還不明白這兩個類的作用,大家只需要記得建立RequestQueue需要這兩個類就可以了。
OK,作為本專欄的開篇文章,主要是為大家提供閱讀的興趣和思路。在接下來的文章裡面,我就會結合具體的原始碼,來說明volley的具體實現。