
序言
2018年談Volley,可以說是too yong, too simple了,對于網(wǎng)絡庫,現(xiàn)在使用最多的莫過于OkHttp了,接觸使用Volley應該還是大二的時候了。之后也看過其源碼,但是在不久前面試的時候,被問到一個Volley庫的問題,就是Volley中請求的優(yōu)先級是如何調(diào)度的,卻卡住了,當時對于源碼的閱讀大多只是停留在對于其實現(xiàn)的流程和項目的結(jié)構(gòu)上,而對于其具體的特性和其如何實現(xiàn)了這些特性卻沒有去了解,相比于功能實現(xiàn)的流程,特性的實現(xiàn)細節(jié)也是不可忽略的,甚至可以說這才是一個庫的精華之所在,同時對于該網(wǎng)絡庫的缺陷在于那里,通過了解其優(yōu)勢和缺陷,我們可以更好的揚長避短,充分利用該庫。本著該原則,準備對于之前閱讀的代碼進行一個重新的回顧,暫定的計劃為一周拆一個輪子。
將以其實現(xiàn)流程,特性實現(xiàn)和其缺陷作為主要切入點,進行代碼分析。
Volley 基礎使用
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
- 創(chuàng)建RequestQueue 請求隊列。
- 創(chuàng)建Request,Volley提供了String,JsonObject等類型,用戶可自己繼承Reqeust實現(xiàn)自己定義的返回結(jié)果類型。
- 將請求添加到請求隊列中。
經(jīng)過以上三步,我們就完成了一次網(wǎng)絡請求,在注冊的監(jiān)聽器的onResponse方法中我們可以拿到請求成功的返回結(jié)果和在onErrorResponse方法中得到出錯的信息。
Volley實現(xiàn)

- 請求隊列的創(chuàng)建
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;
}
- 請求添加到隊列中
public <T> Request<T> add(Request<T> request) {
//請求添加到mCurrentRequests中
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//設置請求的Sequence,后期用來比較請求的優(yōu)先級
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
//如果請求不需要緩存,直接加入網(wǎng)絡請求隊列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
//如果需要緩存加入到緩存隊列中
mCacheQueue.add(request);
return request;
}
- 調(diào)用隊列的開啟
public void start() {
stop();
//創(chuàng)建緩存Dispatcher,并啟動
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 根據(jù)線程池設置的數(shù)目,創(chuàng)建網(wǎng)絡請求Dispatcher,并啟動
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
這里首先會關掉之前的Dispatcher,然后重新創(chuàng)建并開啟Dispatcher
- 創(chuàng)建并開啟CacheDispatcher
public void run() {
mCache.initialize();
while (true) {
//取出請求
final Request<?> request = mCacheQueue.take();
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
//判斷Cache未命中,這加入到網(wǎng)絡請求隊列中
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
continue;
}
//緩存過期了,拿到緩存之后,再將該請求放置到網(wǎng)絡請求隊列中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
//判斷是否需要加入到等待隊列,如果需要則不加入網(wǎng)絡請求隊列
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
continue;
}
//Cache 未過期,Cache命中,這將其包裝成NetworkResponse
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//如果緩存不需要更新,直接將結(jié)果拋回去,否則檢測其是否在等待請求隊列中,如果不在執(zhí)行請求,否則直接拋回
if (!entry.refreshNeeded()) {
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
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) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
mDelivery.postResponse(request, response);
}
}
}
}
當緩存沒有命中的時候,需要發(fā)起網(wǎng)絡請求,這個時候,通過WaitingRequestManager來進行管理,其維護了一個Map來放置響應的請求,鍵為請求CacheKey,值為請求。如果Map中不包含該CacheKey,這將其加入,并將值置為Null,如果有,則直接將其加入。第一次置為Null的原因是為了防止在請求歸來時,在NetworkDispatcher中執(zhí)行了一次數(shù)據(jù)的異步傳遞,在緩存請求隊列處理時再次被處理,通過這種方式也保證了對于同一個請求,只有可能被HttpStack執(zhí)行一次,而每一個請求設置的成功失敗回調(diào)都會被用到。
private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
String cacheKey = request.getCacheKey();
//如果請求隊列包含該Cachekey,表示已經(jīng)執(zhí)行過網(wǎng)絡請求
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new ArrayList<Request<?>>();
}
request.addMarker("waiting-for-response");
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
return true;
} else {
//將Value置為null,因為其回調(diào)在NetworkDispatcher中被執(zhí)行了
mWaitingRequests.put(cacheKey, null);
request.setNetworkRequestCompleteListener(this);
if (VolleyLog.DEBUG) {
return false;
}
}
通過為該Request設置setNetworkRequestCompleteListener,當網(wǎng)絡請求執(zhí)行完成的時候,其onResponseReceived函數(shù)會被回調(diào)到。
public void onResponseReceived(Request<?> request, Response<?> response) {
if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
onNoUsableResponseReceived(request);
return;
}
String cacheKey = request.getCacheKey();
List<Request<?>> waitingRequests;
synchronized (this) {
waitingRequests = mWaitingRequests.remove(cacheKey);
}
if (waitingRequests != null) {
// 將等待的請求結(jié)果進行傳遞
for (Request<?> waiting : waitingRequests) {
mCacheDispatcher.mDelivery.postResponse(waiting, response);
}
}
}
這個時候,排隊的請求的回調(diào)則會被執(zhí)行。
- 創(chuàng)建并開啟NetworkDispatcher
public void run() {
while (true) {
//取出請求
Request<?> request = mQueue.take();
//判斷請求是否需要取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
continue;
}
//執(zhí)行網(wǎng)絡請求
NetworkResponse networkResponse = mNetwork.performRequest(request);
//解析網(wǎng)絡請求結(jié)果
Response<?> response = request.parseNetworkResponse(networkResponse);
//判斷是否需要加入緩存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//通過Delivery將響應結(jié)果傳遞出去
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
}
}
- 請求處理到產(chǎn)生響應

Volley 特性和缺陷
特性
- 自動調(diào)度網(wǎng)絡請求,支持多并發(fā)網(wǎng)絡請求
- 透明的磁盤內(nèi)存響應結(jié)果緩存
- 支持請求優(yōu)先級調(diào)整
- 支持取消請求,并提供相應API
- 高度可拓展
- 網(wǎng)絡請求結(jié)果異步回調(diào)
缺陷
- 不適合做大的網(wǎng)絡下載請求
接下來,針對Volley的特性和缺陷,分別展開,從源碼進行分析。
- 自動調(diào)度網(wǎng)絡請求,支持多并發(fā)網(wǎng)絡請求
在RequestQueue方法中,開啟了多個NetworkDispatcher,而每一個Dispatcher都是一個線程。
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
對于網(wǎng)絡請求通過一個優(yōu)先級阻塞隊列存放,每一個線程可以從隊列中獲取請求,然后執(zhí)行,最后將響應結(jié)果返回。Volley內(nèi)部管理了這些線程,無需開發(fā)者關心。
- 透明的磁盤內(nèi)存響應結(jié)果緩存
對于緩存,Volley支持用戶制定自己的緩存規(guī)則,通過實現(xiàn)Cache接口,實現(xiàn)自己的緩存。同時提供了一個默認的緩存實現(xiàn)DiskBasedCache。其是如何實現(xiàn)透明的磁盤內(nèi)存響應的呢?
首先對于所有的請求,在判斷為設置了緩存的,將會加入到緩存隊列中,然后從緩存中去取,如果緩存中有則返回,如果沒有或者過期則將其加入到網(wǎng)絡請求隊列中。
在CacheDispatcher中,我們首先調(diào)用了Cache的初始化方法
mCache.initialize();
之后通過請求的CacheKey從Cache的get方法中獲取緩存。其內(nèi)部實現(xiàn)是內(nèi)存中維護了一個LinkedHashMap,以請求的CacheKey作為Key,CacheHeader作為值,對于每一個請求內(nèi)容通過文件的形式存放在磁盤文件中,初始化的時候,讀取緩存文件目錄,將其加載到內(nèi)存中,查詢的時候,根據(jù)內(nèi)存中的緩存進行判斷,從磁盤中加載數(shù)據(jù)。
- 優(yōu)先級實現(xiàn)
Volley支持優(yōu)先級的調(diào)整,通過一個PriorityblockingQueue隊列,進行調(diào)度,將請求加入進來,該隊列會根據(jù)其存放的Obejct的compare方法對其進行優(yōu)先級的比較,來確定其先后順序。
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
首先根據(jù)請求設置的優(yōu)先級,然后根據(jù)其Sequence值,這個值是在時間順序上遞增的。
- 任意的取消網(wǎng)絡請求
通過為請求設置cacel字段,在請求執(zhí)行的時候,對其進行判斷,來確定是否為取消了,然后再次執(zhí)行。
- 高度可拓展
Volley支持對于Cache的拓展,HttpStack的拓展,也就是對于網(wǎng)絡請求具體執(zhí)行類的拓展,這里提供了HttpClient和HttpUrlConnection。支持對于網(wǎng)絡請求回調(diào)的自定義。支持網(wǎng)絡請求解析的自定義。用戶可以根據(jù)自己的需求,進行響應的插樁拓展。
- 網(wǎng)絡請求結(jié)果異步回調(diào)
- 創(chuàng)建調(diào)度器
new ExecutorDelivery(new Handler(Looper.getMainLooper()));
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
- 傳遞數(shù)據(jù)
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
調(diào)度器持有了主線程的Handler,通過Handler的post將我們的數(shù)據(jù)透傳到主線程之中。通過這種方式從而實現(xiàn)異步回調(diào)。
- 不適合大數(shù)據(jù)量傳遞
在返回的結(jié)果,通過byte[]字段中,然后存放在內(nèi)存之中,然后獲取相應的編碼方式,將其轉(zhuǎn)化為字符串,這樣就導致當網(wǎng)絡請求數(shù)目增加,返回內(nèi)容增加的時候,導致內(nèi)存中的數(shù)據(jù)量增加,因此Volley不適合進行大數(shù)據(jù)量的傳遞,而實現(xiàn)小而頻繁的請求。
new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
這里的responseContents為一個byte類型數(shù)組。
總結(jié)
Volley的源碼實現(xiàn)比較簡單,代碼量也不是很大,比較容易閱讀和理解,所以Volley作為成了新年以來拆的第一個輪子。拆輪子的過程可以幫助我們學習到功能的實現(xiàn),同時也可以從中學習到一些設計思想。接下來的計劃是每周拆一個輪子,可能是Android也可能是其它方面的。時間充裕,可能會找一些代碼量大的,時間少的話,可能就找一些簡單輪子來拆。拆分思路為其基礎使用,實現(xiàn)原理梳理,特性和缺陷分析。通過這三方面來徹底了解一個輪子的實現(xiàn)。