花了好幾天,從新研究了 Volley 的源碼實現,比起以前又有了一番新的體會,啃源碼真的是一件讓人糾結的事情,閱讀優秀的源碼,特別是難度相對較大的源碼,一旦陷入代碼細節或者情緒一煩躁,很容易讓人奔潰,可是真正的啃下來,收穫真的很大。從優秀的代碼中學習優秀的編程思想以及良好的代碼設計和代碼風格是一個很是好的方法,此次通讀了 Volley 的源碼以後,對於 Volley 的代碼質量和拓展性深感佩服,爲了更好的記錄此次的源碼研究之旅,寫幾篇博客記錄一下。編程
Volley 是 Google 在 2013 年的 I/O 大會上推出的 「Android 異步網絡請求框架和圖片加載框架」,它的設計目標就是去進行 數據量不大,但 通訊頻繁 的網絡操做,而對於大數據量的網絡操做,好比下載文件等,Volley 的表現就會很是糟糕。緩存
在進行源碼分析以前,先讓咱們來看下平時是怎樣使用 Volley 的bash
RequestQueue requestQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest(url
, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
// TODO:
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO:
}
});
requestQueue.add(stringRequest);
複製代碼
一、經過 Volley.newRequestQueue(Context) 獲取一個 RequestQueue 二、傳入 URL 構建 Request,並實現相應的回調 三、將 Request 加入到 RequestQueue 中服務器
在這先把 Volley 中比較重要的類說一下,到時候看源碼能更加明白:網絡
類名 | 做用 |
---|---|
Volley | 對外暴露的 API,主要做用是構建 RequestQueue |
Request | 全部網絡請求的抽象類,StringRequest、JsonRequest、ImageRequest 都是它的子類 |
RequestQueue | 存放請求的隊列,裏面包括 CacheDispatcher、NetworkDispatcher 和 ResponseDelivery |
Response | 封裝一個解析後的結果以便分發 |
CacheDispatcher | 用於執行緩存隊列請求的線程 |
NetworkDispatcher | 用戶執行網絡隊列請求的線程 |
Cache | 緩存請求結果,Volley 默認使用的是基於 sdcard 的 DiskBaseCache |
HttpStack | 處理 Http 請求,並返回請求結果 |
Network | 調用 HttpStack 處理請求,並將結果轉換成可被 ResponseDelivery 處理的 NetworkResponse |
ResponseDelivery | 返回結果的分發接口 |
咱們從 Volley 的使用方法入手,一步一步探究底層的源碼實現,咱們的入手點就是 Volley.newRequestQueue(context)框架
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
複製代碼
這個方法只有一行代碼,只是調用了 newRequestQueue() 的方法重載,並給第二個參數傳入 null,那咱們看下帶有兩個參數的 newRequestQueue 方法中的代碼異步
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
network = new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}
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;
}
複製代碼
能夠看到,這個方法中先判斷 stack 是否爲 null,若是是的話,這裏會根據 Android 手機的系統版本號來進行相應的處理,當 SDK >= 9,則建立一個 HurlStack 實例,不然建立一個 HttpClientStack 實例,實際上 HurlStack 內部使用的是 HttpURLConnction 進行網絡請求,而 HttpClientStack 則是使用 HttpClient 進行網絡請求,這裏之因此要這麼處理,主要是由於在 Android 2.3(SDK = 9)以前,HttpURLConnection 存在一個很嚴重的問題,因此這時候用 HttpClient 來進行網絡請求會比較合適,具體的緣由能夠看下這篇文章:Android 一塊兒來看看 HttpURLConnection 和 HttpClient 的區別。ide
不過因爲如今的 Android 手機基本都是 4.0 以上的,並且 HttpClient 已經因爲某些緣由被棄用了,因此如今只要瞭解 HttpURLConnection 相關的知識就夠了。思路拉回來,咱們繼續看代碼,拿到 Stack 的實例以後將其構建成一個 Network 對象,它是用於根據傳入的 Stack 對象來處理網絡請求的,緊接着構建出一個 RequestQueue 對象,並調用 start() 方法。源碼分析
咱們接着看 start() 方法究竟作了什麼:post
public void start() {
stop();
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (final NetworkDispatcher mDispatcher : mDispatchers) {
if (mDispatcher != null) {
mDispatcher.quit();
}
}
}
複製代碼
先調用 stop() 方法將當前正在進行 Dispatcher 都停掉,而後建立了一個 CacheDispatcher 實例,並調用了它的 start() 方法,接着在一個循環裏去建立 NetworkDispatcher 的實例,分別調用它們的 start() 方法,這裏的 CacheDispatcher 和 NetworkDispatcher 都是繼承自 Thread 的,默認狀況下 for 循環會執行四次,也就是說當調用了 Volley.newRequestQueue(context) 以後,就會有五個線程在後臺運行,等待網絡請求的到來,其中 CacheDispatcher 是緩存線程,NetworkDispatcher 是網絡請求線程。
獲得 RequestQueue 以後,構建相應的 Request,而後調用 add() 方法將其加入到請求隊列中
public <T> Request<T> add(Request<T> request) {
// 將 Request 標記爲屬於此隊列,並將其放入 mCurrentRequests 中
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 讓 Request 按照他們被添加的順序執行
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
//若是請求不須要被緩存,就跳過緩存,直接進行網絡請求
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
mCacheQueue.add(request);
return request;
}
複製代碼
能夠看到,傳入 Request 以後,會先判斷該 Request 是否須要進行緩存,若是不須要就直接將其加入到網絡請求隊列,須要緩存則加入緩存隊列。默認狀況下,每條請求都是應該緩存的,固然咱們也能夠調用 Request 的 setShouldCache() 方法來進行設置。
Request 被添加到緩存隊列中後,在後臺等待的緩存線程就要開始運行起來了,咱們看下 CacheDispatcher 的 run() 方法到底是怎麼實現的。
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化 Cache
mCache.initialize();
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
}
}
}
private void processRequest() throws InterruptedException {
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 若是請求已經取消了,咱們直接結束該請求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
// 從 Cache 中取出包含請求緩存數據的 Entry
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 若是緩存的請求過時了,就將其添加到網絡請求隊列中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 緩存的數據封裝成 NetworkResponse
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
if (!entry.refreshNeeded()) {
// 若是緩存沒有過時就直接進行分發
mDelivery.postResponse(request, response);
} else {
// 重置該請求的 Entry
request.setCacheEntry(entry);
response.intermediate = true;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
} else {
mDelivery.postResponse(request, response);
}
}
}
複製代碼
代碼相對比較長,我在關鍵的地方已經打上註釋了,在這裏總結一下,能夠看到在初始化了 Cache 以後,有一個 while(true) 循環,說明緩存線程是始終執行的,接着會在緩存中取出響應結果,若是爲 null 的話,就將其加入到網絡請求隊列中,若是不爲空的話,再判斷該緩存是否已過時,已通過期則一樣把這條請求加入到網絡請求隊列中,不然直接使用緩存中的數據。最後將數據進行解析,並進行分發。
看完 CacheDispathcer 的 run() 方法,咱們接着看 NetworkDispatcher 的 run() 方法
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
}
}
}
private void processRequest() throws InterruptedException {
Request<?> request = mQueue.take();
long startTimeMs = SystemClock.elapsedRealtime();
try {
request.addMarker("network-queue-take");
// 若是 Request 已經取消了,那就不執行網絡請求
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// 執行網絡請求
NetworkResponse networkResponse = mNetwork.performRequest(request);
// 若是服務器返回 304,並且咱們已經分發過該 Request 的結果,那就不用進行第二次分發了
//(這裏補充一下,304 表明服務器上的結果跟上次訪問的結果是同樣的,也就是說數據沒有變化)
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
request.notifyListenerResponseNotUsable();
return;
}
// 在子線程解析返回的結果
Response<?> response = request.parseNetworkResponse(networkResponse);
// 若是須要的話,就將返回結果寫入緩存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry); }
// 分發響應結果
request.markDelivered();
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
request.notifyListenerResponseNotUsable();
} catch (Exception e) {
request.notifyListenerResponseNotUsable();
}
}
複製代碼
在 NetworkDispatcher 一樣使用了 while(true),說明網絡請求線程也是不斷運行的。而後從網絡隊列裏面取出 Request,再調用 Network 的 performRequest() 方法去發送網絡請求。Network 實際上是一個接口,這裏具體的實現是 BasicNetwork,咱們來看下它的 performRequest() 方法實現:
@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());
// 該注意的地方:調用 Stack 的 executeRequest 進行網絡請求
httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
int statusCode = httpResponse.getStatusCode();
responseHeaders = httpResponse.getHeaders();
if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true,
SystemClock.elapsedRealtime() - requestStart, responseHeaders);
}
List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);
}
// 有些返回結果是沒有內容的,如:204,因此咱們必須進行檢查
InputStream inputStream = httpResponse.getContent();
if (inputStream != null) {
responseContents =
inputStreamToBytes(inputStream, httpResponse.getContentLength());
} else {
responseContents = new byte[0];
}
return new NetworkResponse(statusCode, responseContents, false,
SystemClock.elapsedRealtime() - requestStart, responseHeaders);
} catch (Exception e) {
// ...
}
}
}
複製代碼
這個方法裏面,基本上都是網絡請求方面處理的細節,咱們這篇文章,主要是梳理總體的流程,對細節方面先不深刻。須要注意的是在我標註的第一個地方,調用了 Stack 的 executeRequest() 方法,這裏的 Stack 就是以前調用 Volley.newRequestQueue() 所建立的實例,前面也說過了這個對象的內部是使用了 HttpURLConnection 或 HttpClient(已棄用)來進行網絡請求。網絡請求結束後將返回的數據封裝成一個 NetworkResponse 對象進行返回。
在 NetworkDispatcher 接收到了這個 NetworkResponse 對象以後,又會調用 Request 的 parseNetworkResponse() 方法來對結果進行解析,而後將數據寫入到緩存,最後調用 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(一個 Executor 的實例對象) 的 execute() 方法中傳入了一個 ResponseDeliveryRunnable 對象,execute() 方法默認是在主線程中執行的,這樣就保證了 ResponseDeliveryRunnable 的 run() 方法也是在主線程當中運行的,咱們看下 run() 方法裏面的邏輯:
@SuppressWarnings("unchecked")
@Override
public void run() {
// 若是 Request 被取消了,調用 finish() 方法,結束該請求,不進行傳遞
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 根據響應的結果來進行不一樣的分發
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// 若是傳入的 mRunnable 不爲 null,則運行
if (mRunnable != null) {
mRunnable.run();
}
}
複製代碼
能夠看到當 Response.isSuccess() 爲 true 的話,調用 Resquest 的 deliverResponse() 方法,對結果進行回調,deliverResponse() 方法是每個具體的 Request 子類都必須實現的抽象類,來看下咱們最熟悉的 StringRequest 中的 deliverResponse() 方法
@Override
protected void deliverResponse(String response) {
Response.Listener<String> listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}
複製代碼
看到這裏應該就很明白了,在 deliverResponse() 方法中,調用 listener.onResponse() 方法進行回調,這個 listener 正是咱們構建 StringRequest 時傳入的 Listener,也就是說將返回的結果回調到咱們在外部調用的地方。
StringRequest stringRequest = new StringRequest(url
, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
// TODO:
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO:
}
});
複製代碼
到這裏,終於把 Volley 的完整執行流程所有都梳理了一遍,最後咱們來看一下 Volley 官方提供的流程圖: