1. 程式人生 > >Volley 實現原理解析(轉)

Volley 實現原理解析(轉)

Volley 實現原理解析

轉自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915

1. 功能介紹

1.1. Volley

Volley 是 Google 推出的 Android 非同步網路請求框架和圖片載入框架。在 Google I/O 2013 大會上釋出。

名字由來:a burst or emission of many things or a large amount at once
釋出演講時候的配圖
Volley

從名字由來和配圖中無數急促的火箭可以看出 Volley 的特點:特別適合資料量小,通訊頻繁的網路操作。(個人認為 Android 應用中絕大多數的網路操作都屬於這種型別)。

1.2 Volley 的主要特點

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

2. 總體設計

2.1. 總體設計圖

總體設計圖
上面是 Volley 的總體設計圖,主要是通過兩種Diapatch Thread

不斷從RequestQueue中取出請求,根據是否已快取呼叫CacheNetwork這兩類資料獲取介面之一,從記憶體快取或是伺服器取得請求的資料,然後交由ResponseDelivery去做結果分發及回撥處理。

2.2. Volley 中的概念

簡單介紹一些概念,在詳細設計中會仔細介紹。
Volley 的呼叫比較簡單,通過 newRequestQueue(…) 函式新建並啟動一個請求佇列RequestQueue後,只需要往這個RequestQueue不斷 add Request 即可。

Volley:Volley 對外暴露的 API,通過 newRequestQueue(…) 函式新建並啟動一個請求佇列RequestQueue

Request:表示一個請求的抽象類。StringRequestJsonRequestImageRequest 都是它的子類,表示某種型別的請求。

RequestQueue:表示請求佇列,裡面包含一個CacheDispatcher(用於處理走快取請求的排程執行緒)、NetworkDispatcher陣列(用於處理走網路請求的排程執行緒),一個ResponseDelivery(返回結果分發介面),通過 start() 函式啟動時會啟動CacheDispatcherNetworkDispatchers

CacheDispatcher:一個執行緒,用於排程處理走快取的請求。啟動後會不斷從快取請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理。當結果未快取過、快取失效或快取需要重新整理的情況下,該請求都需要重新進入NetworkDispatcher去排程處理。

NetworkDispatcher:一個執行緒,用於排程處理走網路的請求。啟動後會不斷從網路請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行快取。

ResponseDelivery:返回結果分發介面,目前只有基於ExecutorDelivery的在入參 handler 對應執行緒內進行分發。

HttpStack:處理 Http 請求,返回請求結果。目前 Volley 中有基於 HttpURLConnection 的HurlStack和 基於 Apache HttpClient 的HttpClientStack

Network:呼叫HttpStack處理請求,並將結果轉換為可被ResponseDelivery處理的NetworkResponse

Cache:快取請求結果,Volley 預設使用的是基於 sdcard 的DiskBasedCacheNetworkDispatcher得到請求結果後判斷是否需要儲存在 Cache,CacheDispatcher會從 Cache 中取快取結果。

3. 流程圖

Volley 請求流程圖
Volley請求流程圖

上圖是 Volley 請求時的流程圖,在 Volley 的釋出演講中給出,我在這裡將其用中文重新畫出。

4. 詳細設計

4.1 類關係圖

類關係圖
這是 Volley 框架的主要類關係圖

圖中紅色圈內的部分,組成了 Volley 框架的核心,圍繞 RequestQueue 類,將各個功能點以組合的方式結合在了一起。各個功能點也都是以介面或者抽象類的形式提供。
紅色圈外面的部分,在 Volley 原始碼中放在了toolbox包中,作為 Volley 為各個功能點提供的預設的具體實現。
通過類圖我們看出, Volley 有著非常好的拓展性。通過各個功能點的介面,我們可以給出自定義的,更符合我們需求的具體實現。

多用組合,少用繼承;針對介面程式設計,不針對具體實現程式設計。

優秀框架的設計,令人叫絕,受益良多。

4.2 核心類功能介紹

4.2.1 Volley.java

這個和 Volley 框架同名的類,其實是個工具類,作用是構建一個可用於新增網路請求的RequestQueue物件。
(1). 主要函式
Volley.java 有兩個過載的靜態方法。

public static RequestQueue newRequestQueue(Context context)

public static RequestQueue newRequestQueue(Context context, HttpStack stack)

第一個方法的實現呼叫了第二個方法,傳 HttpStack 引數為 null。
第二個方法中,如果 HttpStatck 引數為 null,則如果系統在 Gingerbread 及之後(即 API Level >= 9),採用基於 HttpURLConnection 的 HurlStack,如果小於 9,採用基於 HttpClient 的 HttpClientStack。

if (stack == null) {
    if (Build.VERSION.SDK_INT >= 9) {
        stack = new HurlStack();
    } else {
        stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
    }
}

得到了 HttpStack,然後通過它構造一個代表網路(Network)的具體實現BasicNetwork
接著構造一個代表快取(Cache)的基於 Disk 的具體實現DiskBasedCache
最後將網路(Network)物件和快取(Cache)物件傳入構建一個 RequestQueue,啟動這個 RequestQueue,並返回。

Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;

我們平時大多采用Volly.newRequestQueue(context)的預設實現,構建RequestQueue。
通過原始碼可以看出,我們可以拋開 Volley 工具類構建自定義的RequestQueue,採用自定義的HttpStatck,採用自定義的Network實現,採用自定義的Cache實現等來構建RequestQueue
優秀框架的高可拓展性的魅力來源於此啊

(2). HttpURLConnection 和 AndroidHttpClient(HttpClient 的封裝)如何選擇及原因:
在 Froyo(2.2) 之前,HttpURLConnection 有個重大 Bug,呼叫 close() 函式會影響連線池,導致連線複用失效,所以在 Froyo 之前使用 HttpURLConnection 需要關閉 keepAlive。
另外在 Gingerbread(2.3) HttpURLConnection 預設開啟了 gzip 壓縮,提高了 HTTPS 的效能,Ice Cream Sandwich(4.0) HttpURLConnection 支援了請求結果快取。
再加上 HttpURLConnection 本身 API 相對簡單,所以對 Android 來說,在 2.3 之後建議使用 HttpURLConnection,之前建議使用 AndroidHttpClient。

(3). 關於 User Agent
通過程式碼我們發現如果是使用 AndroidHttpClient,Volley 還會將請求頭中的 User-Agent 欄位設定為 App 的 ${packageName}/${versionCode},如果異常則使用 "volley/0",不過這個獲取 User-Agent 的操作應該放到 if else 內部更合適。而對於 HttpURLConnection 卻沒有任何操作,為什麼呢?
如果用 Fiddler 或 Charles 對資料抓包我們會發現,我們會發現 HttpURLConnection 預設是有 User-Agent 的,類似:

Dalvik/1.6.0 (Linux; U; Android 4.1.1; Google Nexus 4 - 4.1.1 - API 16 - 768x1280_1 Build/JRO03S)

經常用 WebView 的同學會也許會發現似曾相識,是的,WebView 預設的 User-Agent 也是這個。實際在請求發出之前,會檢測 User-Agent 是否為空,如果不為空,則加上系統預設 User-Agent。在 Android 2.1 之後,我們可以通過

String userAgent = System.getProperty("http.agent");

得到系統預設的 User-Agent,Volley 如果希望自定義 User-Agent,可在自定義 Request 中重寫 getHeaders() 函式

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    // self-defined user agent
    Map<String, String> headerMap = new HashMap<String, String>();
    headerMap.put("User-Agent", "android-open-project-analysis/1.0");
    return headerMap;
}

4.2.2 Request.java

代表一個網路請求的抽象類。我們通過構建一個Request類的非抽象子類(StringRequest、JsonRequest、ImageRequest或自定義)物件,並將其加入到·RequestQueue·中來完成一次網路請求操作。
Volley 支援 8 種 Http 請求方式 GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
Request 類中包含了請求 url,請求請求方式,請求 Header,請求 Body,請求的優先順序等資訊。

因為是抽象類,子類必須重寫的兩個方法。

abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

子類重寫此方法,將網路返回的原生位元組內容,轉換成合適的型別。此方法會在工作執行緒中被呼叫。

abstract protected void deliverResponse(T response);

子類重寫此方法,將解析成合適型別的內容傳遞給它們的監聽回撥。

以下兩個方法也經常會被重寫

public byte[] getBody()

重寫此方法,可以構建用於 POST、PUT、PATCH 請求方式的 Body 內容。

protected Map<String, String> getParams()

在上面getBody函式沒有被重寫情況下,此方法的返回值會被 key、value 分別編碼後拼裝起來轉換為位元組碼作為 Body 內容。

4.2.3 RequestQueue.java

Volley 框架的核心類,將請求Request加入到一個執行的RequestQueue中,來完成請求操作。

(1). 主要成員變數

RequestQueue 中維護了兩個基於優先順序的 Request 佇列,快取請求佇列和網路請求佇列。
放在快取請求佇列中的 Request,將通過快取獲取資料;放在網路請求佇列中的 Request,將通過網路獲取資料。

private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();

維護了一個正在進行中,尚未完成的請求集合。

private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

維護了一個等待請求的集合,如果一個請求正在被處理並且可以被快取,後續的相同 url 的請求,將進入此等待佇列。

private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();

(2). 啟動佇列

創建出 RequestQueue 以後,呼叫 start 方法,啟動佇列。

/**
 * Starts the dispatchers in this queue.
 */
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();
    }
}

start 方法中,開啟一個快取排程執行緒CacheDispatcher和 n 個網路排程執行緒NetworkDispatcher,這裡 n 預設為4,存在優化的餘地,比如可以根據 CPU 核數以及網路型別計算更合適的併發數。
快取排程執行緒不斷的從快取請求佇列中取出 Request 去處理,網路排程執行緒不斷的從網路請求佇列中取出 Request 去處理。

(3). 加入請求

public <T> Request<T> add(Request<T> request);

流程圖如下:
加入請求流程圖

(4). 請求完成

void finish(Request<?> request)

Request 請求結束

(1). 首先從正在進行中請求集合mCurrentRequests中移除該請求。
(2). 然後查詢請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待佇列移除,並將等待佇列所有的請求新增到快取請求佇列中,讓快取請求處理執行緒CacheDispatcher自動處理。

(5). 請求取消

public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)

取消當前請求集合中所有符合條件的請求。
filter 引數表示可以按照自定義的過濾器過濾需要取消的請求。
tag 表示按照Request.setTag設定好的 tag 取消請求,比如同屬於某個 Activity 的。

4.2.4 CacheDispatcher.java

一個執行緒,用於排程處理走快取的請求。啟動後會不斷從快取請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery 去執行後續處理。當結果未快取過、快取失效或快取需要重新整理的情況下,該請求都需要重新進入NetworkDispatcher去排程處理。

(1). 成員變數

BlockingQueue<Request<?>> mCacheQueue 快取請求佇列
BlockingQueue<Request<?>> mNetworkQueue 網路請求佇列
Cache mCache 快取類,代表了一個可以獲取請求結果,儲存請求結果的快取
ResponseDelivery mDelivery 請求結果傳遞類

(2). 處理流程圖

快取排程執行緒處理流程圖

4.2.5 NetworkDispatcher.java

一個執行緒,用於排程處理走網路的請求。啟動後會不斷從網路請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給 ResponseDelivery 去執行後續處理,並判斷結果是否要進行快取。

(1). 成員變數

BlockingQueue<Request<?>> mQueue 網路請求佇列
Network mNetwork 網路類,代表了一個可以執行請求的網路
Cache mCache 快取類,代表了一個可以獲取請求結果,儲存請求結果的快取

ResponseDelivery mDelivery 請求結果傳遞類,可以傳遞請求的結果或者錯誤到呼叫者

(2). 處理流程圖

網路排程執行緒處理流程圖

4.2.6 Cache.java

快取介面,代表了一個可以獲取請求結果,儲存請求結果的快取。

(1). 主要方法:

public Entry get(String key); 通過 key 獲取請求的快取實體
public void put(String key, Entry entry); 存入一個請求的快取實體
public void remove(String key); 移除指定的快取實體
public void clear(); 清空快取

(2). 代表快取實體的內部類 Entry

成員變數和方法
byte[] data 請求返回的資料(Body 實體)
String etag Http 響應首部中用於快取新鮮度驗證的 ETag
long serverDate Http 響應首部中的響應產生時間
long ttl 快取的過期時間
long softTtl 快取的新鮮時間
Map<String, String> responseHeaders 響應的 Headers
boolean isExpired() 判斷快取是否過期,過期快取不能繼續使用
boolean refreshNeeded() 判斷快取是否新鮮,不新鮮的快取需要發到服務端做新鮮度的檢測

4.2.7 DiskBasedCache.java

繼承 Cache 類,基於 Disk 的快取實現類。

(1). 主要方法:

public synchronized void initialize() 初始化,掃描快取目錄得到所有快取資料摘要資訊放入記憶體。
public synchronized Entry get(String key) 從快取中得到資料。先從摘要資訊中得到摘要資訊,然後讀取快取資料檔案得到內容。
public synchronized void put(String key, Entry entry) 將資料存入快取內。先檢查快取是否會滿,會則先刪除快取中部分資料,然後再新建快取檔案。
private void pruneIfNeeded(int neededSpace) 檢查是否能再分配 neededSpace 位元組的空間,如果不能則刪除快取中部分資料。
public synchronized void clear() 清空快取。 public synchronized void remove(String key) 刪除快取中某個元素。

(2). CacheHeader 類

CacheHeader 是快取檔案摘要資訊,儲存在快取檔案的頭部,與上面的Cache.Entry相似。

4.2.8 NoCache.java

繼承 Cache 類,不做任何操作的快取實現類,可將它作為構建RequestQueue的引數以實現一個不帶快取的請求佇列。

4.2.9 Network.java

代表網路的介面,處