目錄
一、寫在前面
二、工作流程
參考資料
一、寫在前面
Volley是Google在2013年I/O大會上推出的Android異步網絡請求框架和圖片加載框架,新技術的日新月異發展,感覺已經慢慢被OkHttp替代了,現在重新去讀它的源碼,雖然稍顯得有些過氣,但還是有很大的學習價值的,在此記錄下自己的腳印。
先來看看關于Volley的幾個關鍵特點:
- 適合數據量小,通信頻繁的網絡操作
- 基于接口設計,面向接口編程,可擴展性強
- 一定程度符合Http規范(ResponseCode請求響應碼、請求頭處理、緩存機制、請求重試、優先級定義)
- Android 2.2以下基于HttpClient,2.3及以上基于HttpUrlConnenction
- 提供了簡便的圖片加載工具
二、工作流程
既然是探索Volley的工作流程,我們可以一步步追蹤其源碼,先來看一個典型的發送Volley網絡請求的用法:
//1、創建請求隊列
RequestQueue mQueue = Volley.newRequestQueue(this);
//2、創建一個網絡請求
StringRequest stringRequest = new StringRequest("https://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.i(TAG, response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.i(TAG, error.getMessage(), error);
}
});
//3、將網絡請求添加到請求隊列中
mQueue.add(stringRequest);
簡簡單單的三個步驟,完成了一個網絡請求,并在onResponse中返回。那么Volley里面具體幫我們干了哪些事情呢,我們進入Volley.newRequestQueue(this)
這個方法內部一探究竟:
public static 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) {
if (Build.VERSION.SDK_INT >= 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)
{
// 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;
}
- 如果HttpStack參數為null,則根據系統在API Level>=9采用HurlStack(內部為HttpUrlConnection),如果<9,采用基于HttpClientStack
- 根據HttpStack 創建一個NetWork的具體實現類BasicNetwork對象
- 根據DiskBasedCache磁盤緩存對象、network對象構建一個RequestQueue,調用RequestQueue的start方法啟動。
通過源碼可以看出,我們可以拋開Volley工具類構建自定義的RequestQueue,采用自定義的HttpStack,采用自定義的NetWork實現,采用自定義的Cache實現來構建RequestQueue,Volley的面向接口編程,高可拓展性的魅力就源于此。
關于HttpURLConnection 和 HttpClient的選擇及原因
- 在Android2.2之前,HttpURLConnection 有個重大的bug,調用 close() 函數會影響連接池,導致連接復用失效,所以在Android2.2之前使用 HttpURLConnection 需要關閉 keepAlive
- 在Android2.3,HttpURLConnection 默認開啟了 gzip 壓縮,提高了 HTTPS 的性能;在Android 4.0,HttpURLConnection 支持了請求結果緩存
HttpURLConnection 本身 API 相對簡單,所以對 Android 來說,在2.3之前建議使用HttpClient,之后建議使用 HttpURLConnection。
本著對Volley請求執行流程的側重把握,我們接著看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();
}
}
- 調用stop方法,停止所有的線程(CacheDispatcher 和 NetworkDispatcher)
- 創建一個緩存調度線程CacheDispatcher并啟動
- 創建n個網絡調度線程NetworkDispatcher并啟動
在這段start方法執行完后,Volley就已經默認創建了5個線程(1個CacheDispatcher+4個NetworkDispatcher),這里存在優化的余地,比如我們可以根據CPU核數以及網絡類型計算更合適的并發數
start方法執行完,由此得到RequestQueue,我們只需要構建相應的Request,然后調用RequstQueue的add方法,就可以完成網絡請求操作。
關于Request類
- Request是一個網絡請求的抽象類,非抽象子類有StringRequest、JsonRequest、ImageRequest或者自定義子類,我們通過構建這個對象,將其加入RequestQueue來完成一次網絡請求操作
- Request子類必須實現的方法有兩個:parseNetworkResponse 和 deliverResponse
Volley支持8中請求方式:GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、PATCH
- Request類包含了請求URL,請求方式,請求Header,請求Body,請求優先級等信息
RequestQueue的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;
}
}
- 判斷是否可以緩存,如果不能緩存則直接加入網絡請求隊列mNetworkQueue,能緩存則只需執行加入到緩存隊列mCacheQueue中
- 默認情況下,Volley的每條請求都是可以緩存的,如果不需要緩存,可以調用Request的setShouldCache(false)方法來改變這一默認行為
既然將請求加入到mNetworkQueue或者mCacheQueue中,接下來就是在對應的NetworkDispatcher或CacheDispatcher線程中執行了。
這里只看NetworkDispatcher的run方法(CacheDispatcher的run方法后半部分和這里類似)
public class NetworkDispatcher extends Thread {
.....
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
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) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
}
- 外部while(true)的死循環,說明網絡線程始終運行
- 調用mNetwork的performRequest方法,將request對象傳進入,執行具體的網絡請求
- 根據請求的返回值,調用Request的parseNetworkResponse方法來解析NetworkResponse的數據,以及將數據寫入到緩存,這個方法的實現是交給Request的子類來完成的,因為不同種類的Request解析的方式也不同,就像在自定義Request中,必須重寫parseNetworkResponse方法一樣。
在解析完NetworkResponse的數據后,緊接著會調用ResponseDelivery的實現子類ExecutorDelivery的postResponse方法來回調解析出的數據:
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
mResponsePoster的execute方法傳入了一個ResponseDeliveryRunnable對象:
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();
}
}
}
可以看出實現了一個Runnable接口,在run方法內部判斷是否響應成功,如果成功則調用Request的deliverResponse方法,否則調用deliverError方法。這里的deliverResponse方法內部最終會回調我們在構建Request時設置的Response.Listener
對象,例如StringRequest的deliverResponse內部代碼如下。
public class StringRequest extends Request<String> {
......
@Override
protected void deliverResponse(String response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
}
其實performRequest內部轉換成Response的處理過程,這里借用Volley源碼解析 里的一張圖,更加從宏觀上清晰的說明問題了:
從上到下表示從得到數據后一步步的處理,箭頭旁的注釋表示該步處理后的實體類。
關于Cache類
- 緩存接口,代表一個可以獲取請求結果、存儲請求結果的緩存
- 默認的兩個實現子類:NoCache和DiskBasedCache
- DiskBasedCache類會把從服務器返回的信息寫入磁盤,然后從磁盤取出緩存,這其中涉及了一些靜態的方法如writeInt、writeLong等等,何解?原因之一是Java的IO本身是對byte進行操作,一個int占4個byte,需要按位寫入,另一方面也是因為網絡字節序是大端字節序,在80x86的平臺中,是以小端法存放的,比如我們經過網絡發送0x12345678這個整型,但實際上在流中是0x87654321
好了,到這里Volley的整體流程大概梳理了一遍,可能稍微講得有點亂,當然也僅僅是個人的記錄為主,最后,放上Volley官方的請求流程圖鎮樓(原本想著自己畫一張流程圖,但翻了翻發覺畫不出比這個更好的了):