Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster.
Volley是Google在2013年推出來的HTTP庫,旨在幫助開發者更快更簡便的實現網絡請求。說說爲什麼要分析Volley的源碼吧,因爲Volley中線程的轉換時通過 Thread
和 Handler
來實現的,跟之前的兩篇都有着很大的聯繫(ps:Okhttp和Retrofit都撕不動_),哈哈,後面會一步一步的給大家帶來Okhttp和Retrofit等更多的源碼分析!
執行一個網絡請求
我們先整體看下 Volley 是如何進行一個完整的網絡請求的:
val requestQueue: RequestQueue = Volley.newRequestQueue(context)
val url = "https://www.baidu.com"
val request = StringRequest(url,
Response.Listener<String> {
Log.d("taonce", "request result is: $it")
},
Response.ErrorListener { })
requestQueue.add(request)
上面代碼主要做了三件事:
- 創建一個請求隊列
RequestQueue
:Volley.newRequestQueue(context)
- 創建一個請求
Request
:StringRequest(String url, Listener<String> listener, @Nullable ErrorListener errorListener)
- 將
Request
加入到RequestQueue
:requestQueue.add(request)
接下來通過源碼的方法來看看這三步內部做了什麼操作。
創建 RequestQueue 和 Request進入 Volley.newRequestQueue(context)
源碼:
進入 Volley.newRequestQueue(context)
源碼:
public static RequestQueue newRequestQueue(Context context) {
// 實際上是調用了另外一個構造方法
return newRequestQueue(context, (BaseHttpStack) null);
}
繼續查看 newRequestQueue(Context context, BaseHttpStack stack)
:
BasicNetwork network;
if (stack == null) {
// 判斷是否大於等於 Android 2.3 版本
if (Build.VERSION.SDK_INT >= 9) {
// 如果是 Android 2.3 及其以上,就用 HurlStack() 進行網絡請求
network = new BasicNetwork(new HurlStack());
} else {
// 如果是 Android 2.3 以下,那麼就採用 HttpClientStack(HttpClient) 進行網絡請求
network = new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
// 如果stack不爲空,那麼就採用傳進來的stack
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
可以得出:
- 創建一個
BasicNetwork
對象 -
stack
爲空 ----> Android 2.3 及其以上創建HurlStack()
對象,並且傳給network
,HurlStack()
採用的是HttpURLConnetion
進行網絡請求的。 -
stack
爲空 ----> Android 2.3 以下創建HttpClientStack()
對象,並且傳給network
,HttpClientStack()
採用的則是HttpClient
進行網絡請求,不過現在( 當前版本1.1.1 )new HttpClientStack(HttpClient client)
已經被標記了@Deprecated
了,因爲它採用的HttpClient
,Google 在 Android 6.0 中移除了對 Apache HTTP 客戶端的支持,並且從 Android P 開始,org.apache.legacy
庫將從bootclasspath
中刪除。 -
stack
不爲空 ----> 直接將 stack 傳給network
- 創建一個
RequestQueue()
對象並返回
到此爲止,大家只要記住上面幾個對象就好,接下來會慢慢的講解對象分別做了什麼工作。
這裏還是要着重介紹下 newRequestQueue()
方法:
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
// 創建請求隊列
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
創建 RequestQueue()
的過程中有一個很重要的點,我們來看看它的構造方法:
public RequestQueue(Cache cache, Network network) {
// 默認 Network Thread 數目爲 DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(
cache,
network,
threadPoolSize,
// 注意點就是這:創建 Handler 的時候,傳遞的是 Looper.getMainLooper(),也就是後面將請求結果回調到主線程的關鍵。
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(
Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
上面第二個構造方法中創建了一個 ExecutorDelivery()
, 這個對象實現了 ResponseDelivery
接口的類,用來將網絡請求的結果或者緩存中的結果分發到主線程。
再來看看上面 queue.start()
方法做了什麼操作:
// 開啓5個線程
public void start() {
// 如果5個線程不爲空,先停止它們
stop();
// 創建 CacheDispatcher,並開啓它
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 創建4個NetworkDispatcher,並開啓它們
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher =
new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
到此,我們前奏分析完了,有一些概念先不急着看,只要知道它的作用就行,後面我們來一個一個擊破。
添加請求到請求隊列中 : RequestQueue.add( request )
廢話不多了,直接進入源碼:RequestQueue.add(request)
public <T> Request<T> add(Request<T> request) {
// 將request和當前的RequestQueue綁定
request.setRequestQueue(this);
// 將request添加到set集合中,用於執行 cancelAll() 操作
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 給request添加一些標記
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);
// 判斷request是否需要緩存,對於 Get 以外的請求,默認關閉緩存
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
mCacheQueue.add(request);
return request;
}
添加 request
的操作算是很簡單的了,無非是根據 request
是否需要緩存,將 request
添加到 CacheQueue
或者 NetworkQueue
中。
看到這,我們是不是在想,怎麼添加之後就沒有動作了?非也非也,我們回顧下,在創建 requestQueue
的同時,是不是創建了5個子線程並且開啓了它們,它們內部使用的 CacheQueue
或者 NetworkQueue
不就是上面提到的麼。接下來,我們先看看 CacheDispatcher
內部做了什麼。
CacheDispatcher 調度器剖析:
CacheDispatcher
就是一個繼承了 Thread
的類,我們在執行 RequestQueue.start()
的時候創建了它,並開啓了它,現在我們來看看它的 run()
方法:
@Override
public void run() {
// 設置線程優先級爲後臺線程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 實則是執行了DiskBasedCache的initialize()方法
mCache.initialize();
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
// 中斷當前線程,並且退出死循環,mQuit在調用CacheDispatcher的quit()方法之後會被賦值爲true
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
}
}
}
開了一個死循環,然後調用了 processRequest()
, 我們接着看這個方法:
private void processRequest() throws InterruptedException {
// 從CacheQueue中取出一個可用的request
final Request<?> request = mCacheQueue.take();
processRequest(request);
}
@VisibleForTesting
void processRequest(final Request<?> request) throws InterruptedException {
request.addMarker("cache-queue-take");
request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
try {
//request如果被取消了,就直接返回
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
Cache.Entry entry = mCache.get(request.getCacheKey());
// 沒有緩存就把request添加到NetworkQueue中
if (entry == null) {
request.addMarker("cache-miss");
// 沒有緩存,並且等待隊列中也沒有此request,那麼就直接加入到NetworkQueue中
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 如果緩存過期了,也是一樣把request添加到NetworkQueue中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 有緩存並且沒有過期
request.addMarker("cache-hit");
// 根據緩存的內容解析
Response<?> response =
request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 是否需要更新
if (!entry.refreshNeeded()) {
// 不需要更新,直接將結果調度到主線程
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
// 判斷是否有相同緩存鍵的任務在執行
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
// 需要更新結果,先將結果調度到主線程,然後執行new runnable(){}
// runnable中就是將request添加到NetworkQueue中,更新一下內容
mDelivery.postResponse(
request,
response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
// request已經加入到mWaitingRequests中
// 直接把結果調度到主線程
mDelivery.postResponse(request, response);
}
}
} finally {
request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
}
}
我們在 processRequest
中可以看到有一個方法經常出現,那就是 mWaitingRequestManager.maybeAddToWaitingRequests(request)
,它的作用是判斷當前這個 request
是否有存在相同緩存鍵的請求已經處於運行狀態,如果有,那麼就將這個 request
加入到一個等待隊列中,等到相同緩存鍵的請求完成。
總結一下 CacheDispatcher
主要步驟:
- 從
CacheQueue
中循環取出request
; - 如果緩存丟失,加入到
NetworkQueue
中; - 如果緩存過期,加入到
NetworkQueue
中; - 將緩存中的數據解析成
Response
對象; - 如果不需要更新,直接將結果回調到主線程,回調操作等介紹完NetworkDispatcher之後一起深入剖析;
- 如果需要更新,先將結果回調到主線程,然後再將
request
加入到NetworkQueue
中。
NetworkDispatcher 調度器剖析:
NetworkDispatcher
和 CacheDispatcher
十分類似,都是 Thread
的子類,下面重點看下它的 run()
方法:
@Override
public void run() {
// 設置線程優先級爲後臺線程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
// 調用quit()方法之後,mQuit就會被賦值true
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
}
}
}
繼續看 processRequest()
方法:
private void processRequest() throws InterruptedException {
// 從NetworkQueue中取出request
Request<?> request = mQueue.take();
processRequest(request);
}
@VisibleForTesting
void processRequest(Request<?> request) {
long startTimeMs = SystemClock.elapsedRealtime();
request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
try {
request.addMarker("network-queue-take");
// 如果request被取消了,那麼就不執行此request
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// 還記得這個mNetwork麼,它就是Volley.newRequestQueue()方法裏的BasicNetwork對象,一會我們來看看mNetwork.performRequest()方法是如何得到NetworkResponse的
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// notModified是服務端返回304,hasHadResponseDelivered()是request已經回調過了
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
request.notifyListenerResponseNotUsable();
return;
}
// 將NetworkResponse解析成Response對象,在子線程中執行
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 將request寫入緩存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
request.markDelivered();
// 回調結果至主線程
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
}
// 以下都是處理異常錯誤,然後也需要回調至主線程
catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} 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);
request.notifyListenerResponseNotUsable();
} finally {
request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
}
}
通過 NetworkDispatcher.run()
方法可以發現,主要分爲以下幾步:
- 通過
BasicNetwork.performRequest(request)
得到NetworkResponse
對象; - 通過
request.parseNetworkResponse(networkResponse)
解析得到Response
對象; - 通過
mDelivery
將成功結果或者失敗結果回調到主線程。
現在我們依次來分析下這三步:
-
請求網絡,得到
NetworkResponse
:BasicNetwork
實現了Network
接口,其主要代碼集中在NetworkResponse performRequest(Request<?> request) throws VolleyError
中,也就是執行特定的請求。@Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; List<Header> responseHeaders = Collections.emptyList(); try { // 得到請求頭信息 Map<String, String> additionalRequestHeaders = getCacheHeaders(request.getCacheEntry()); // 具體的網絡請求是靠BaseHttpStack執行的 httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // 下面就是根據不同的狀態碼返回不同的NetworkResponse對象了,具體就不分析了 if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null) { return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, /* data= */ null, /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } } // 省略大部分代碼... }
通過上面源碼可以看出,
BasicNetwork
就是封裝了一下NetworkResponse
對象蠻,並沒有涉及到網絡請求,我們繼續深入到BaseHttpStack.executeRequest(request, additionalRequestHeaders)
源碼中。public abstract class BaseHttpStack implements HttpStack { public abstract HttpResponse executeRequest( Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
我們發現
BaseHttpStack
是一個抽象類,那麼具體的請求是在哪呢,不知道你是否還有印象,在Volley.newRequestQueue()
中,我們創建BasicNetwork
的時候是根據 Android 版本傳入不同的BaseHttpStack
子類,Android 2.3 以上我們傳入的是HurlStack
對象,下面就看看HurlStack.executeRequest()
方法吧。@Override public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { // 得到url String url = request.getUrl(); HashMap<String, String> map = new HashMap<>(); map.putAll(additionalHeaders); // Request.getHeaders() takes precedence over the given additional (cache) headers). map.putAll(request.getHeaders()); ... URL parsedUrl = new URL(url); // 通過HttpURLConnection來請求網絡 HttpURLConnection connection = openConnection(parsedUrl, request); }
其實
HurlStack.executeRequest()
方法裏,就是藉助了HttpURLConnection
對象來請求網絡,並根據不同的條件返回不同的HttpResponse
對象的。 -
解析
NetworkResponse
, 得到Response
:解析過程是定義在
Request
抽象類的protected abstract Response<T> parseNetworkResponse(NetworkResponse response)
抽象方法中,對於每一種具體的Request
類,比如:StringRequest
、JsonObjectRequest
等等都有自己的實現,下面我們來看看StringRequest
中的parseNetworkResponse()
方法:@Override @SuppressWarnings("DefaultCharset") protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { // 將response中的data信息取出來 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { // Since minSdkVersion = 8, we can't call // new String(response.data, Charset.defaultCharset()) // So suppress the warning instead. parsed = new String(response.data); } // 封裝成Response對象 return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); }
-
回調主線程,如果你看過我之前的 Handler源碼剖析 文章話,那麼這一步就很簡單了,我們來理一理:
回調是通過
ResponseDelivery mDelivery
對象來執行的,這個對象最早是在創建RequestQueue()
的時候初始化的,我在那一小節特意標註了,再來回顧下:public RequestQueue(Cache cache, Network network, int threadPoolSize) { this( cache, network, threadPoolSize, // 創建ExecutorDelivery對象,傳入一個Handler對象,並且Handler綁定的是主線程的Looper new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public class ExecutorDelivery implements ResponseDelivery { private final Executor mResponsePoster; /** * ExecutorDelivery構造函數,內部初始化了mResponsePoster接口 * 並且在execute()方法裏調用了handler.post()方法 */ public ExecutorDelivery(final Handler handler) { // 初始化Execute對象 mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; } }
知道了它的初始化,我們再來看看它是如何實現回調的:
Volley 中回調是通過
postResponse()
方法的 :public void postResponse(Request<?> request, Response<?> response) { postResponse(request, response, null); } @Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); }
postResponse()
最終會調用mResponsePoster
對象的execute()
方法,傳入了一個ResponseDeliveryRunnable
對象,它實現了Runnable
接口,execute()
方法會通過Handler.post(runnable)
將ResponseDeliveryRunnable
放入消息隊列。最後我們來看看這個ResponseDeliveryRunnable
的run()
方法在主線程中做了什麼操作:@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; } if (mResponse.isSuccess()) { // 執行成功的回調,在具體的Request實現類中,比如StringRequest就會調用listener.onResponse(string)回調 mRequest.deliverResponse(mResponse.result); } else { // 執行失敗的回調,在request中,直接回調了listener.onErrorResponse(error) mRequest.deliverError(mResponse.error); } // intermediate默認爲false,但是在CacheDispatcher的run()中,如果需要更新緩存,那麼就會置爲true if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } // 如果傳入了runnable不爲空,那就就執行runnable.run()方法 // 回憶下在CacheDispatcher的run()方法中,如果request有緩存,但是需要更新緩存的時候,mDelivery是不是調用的帶runnable的方法 if (mRunnable != null) { mRunnable.run(); } }
分析到這的時候,大家可以藉助下方的流程圖理一理Volley的整體流程,主要理解一下緩存線程和網絡線程的轉換,子線程和主線程的轉換:
源碼分析的文字還在不斷的更新,如果本文章你發現的不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!