我的博客: Volley 源碼分析
Volley 的使用流程分析
官網(wǎng)示例
- 創(chuàng)建一個(gè)請求隊(duì)列
RequestQueue,并啟動隊(duì)列 - 創(chuàng)建一個(gè)請求
Request添加到請求隊(duì)列中
創(chuàng)建 RequestQueue 對象
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// 實(shí)例化一個(gè)請求隊(duì)列
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// 創(chuàng)建一個(gè)期待類型為字符串類型的請求
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!");
}
});
// 將請求添加到請求隊(duì)列中
queue.add(stringRequest);
上面代碼片段的第5行,我們調(diào)用 Volley.newRequestQueue(this) 來創(chuàng)建一個(gè)請求隊(duì)列。Volley 中提供了兩種創(chuàng)建請求隊(duì)列的方法,newRequestQueue(Context context,HttpStack stack) 和 newRequestQueue(Context context)
public static RequestQueue newRequestQueue(Context context) {
// 在此方法內(nèi)部會調(diào)用另一個(gè) newRequestQueue 方法,第二個(gè)參數(shù)為 null 代表使用默認(rèn)的 HttpStack 實(shí)現(xiàn)
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
// 緩存文件目錄 data/data/packagename/cache/volley
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();//基于HttpClient
} else {
//基于HttpUrlConnection
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//利用HttpStack創(chuàng)建一個(gè)Network對象
Network network = new BasicNetwork(stack);
//創(chuàng)建一個(gè)RequestQueue對象,在構(gòu)造函數(shù)中傳入緩存對象,網(wǎng)絡(luò)對象
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//啟動隊(duì)列
queue.start();
return queue;
}
在上面代碼片段中的第2行可以看見,Volley 調(diào)用 getCacheDir() 方法來獲取緩存目錄,Volley 中的緩存文件會存儲在 /data/data/packagename/cache 目錄下面,并不是存儲在 SD 卡中的。
從12~18的代碼可以看見,Volley 當(dāng)中有對 HttpStack 的默認(rèn)實(shí)現(xiàn),HttpStack 是真正用來執(zhí)行請求的接口 ,根據(jù)版本號的不同,實(shí)例化不同的對象,在 Android2.3 版本之前采用基于 HttpClient 實(shí)現(xiàn)的 HttpClientStack 對象,不然則采用基于 HttpUrlConnection 實(shí)現(xiàn)的 HUrlStack。
之后我們通過 HttpStack 構(gòu)建了一個(gè) Network 對象,它會調(diào)用 HttpStack#performRequest() 方法來執(zhí)行請求,并且將請求的結(jié)果轉(zhuǎn)化成 NetworkResponse 對象,NetworkResponse 類封裝了響應(yīng)的響應(yīng)碼,響應(yīng)體,響應(yīng)頭等數(shù)據(jù)。
接著我們會將之前構(gòu)建的緩存目錄以及網(wǎng)絡(luò)對象傳入 RequestQueue(Cache cache, Network network) 的構(gòu)造函數(shù)中,構(gòu)造一個(gè) RequestQueue 對象,然后調(diào)用隊(duì)列的 start()方法來啟動隊(duì)列,其實(shí)就是啟動隊(duì)列中的兩種線程:
//啟動隊(duì)列中所有的調(diào)度線程.
public void start() {
stop(); // 確保停止所有當(dāng)前正在運(yùn)行的調(diào)度線程
// 創(chuàng)建緩存調(diào)度線程,并啟動它,用來處理緩存隊(duì)列中的請求
mCacheDispatcher = new CacheDispatcher(mCacheQueue,mNetworkQueue,mCache,mDelivery);
mCacheDispatcher.start();
// 創(chuàng)建一組網(wǎng)絡(luò)調(diào)度線程,并啟動它們,用來處理網(wǎng)絡(luò)隊(duì)列中的請求,默認(rèn)線程數(shù)量為4,也可以通過RequestQueue的構(gòu)造函數(shù)指定線程數(shù)量。
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue,mNetwork,mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
在 start() 方法中,主要是啟動了兩種線程分別是 CacheDispatcher 和 NetworkDispatcher,它們都是線程類,顧名思義 CacheDispatcher 線程會處理緩存隊(duì)列中請求,NetworkDispatcher 處理網(wǎng)絡(luò)隊(duì)列中的請求,由此可見在我們調(diào)用 Volley 的公開方法創(chuàng)建請求隊(duì)列的時(shí)候,其實(shí)就是開啟了兩種線程在等待著處理我們添加的請求。
添加請求 add(Request)
之前我們已經(jīng)創(chuàng)建了 RequestQueue 對象,現(xiàn)在我們只需要構(gòu)建一個(gè) Request 對象,并將它加入到請求隊(duì)列中即可。下面我們來看看 add(Request<T> request) 方法:
public <T> Request<T> add(Request<T> request) {
// 將請求加入到當(dāng)前請求隊(duì)列當(dāng)中,毋庸置疑的我們需要將所有的請求集合在一個(gè)隊(duì)列中,方便我們做統(tǒng)一操作,例如:取消單個(gè)請求或者取消具有相同標(biāo)記的請求...
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 給請求設(shè)置順序.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果請求是不能夠被緩存的,直接將該請求加入網(wǎng)絡(luò)隊(duì)列中.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 如果有相同的請求正在被處理,就將請求加入對應(yīng)請求的等待隊(duì)列中去.等到相同的正在執(zhí)行的請求處理完畢的時(shí)候會調(diào)用 finish()方法,然后將這些等待隊(duì)列中的請求全部加入緩存隊(duì)列中去,讓緩存線程來處理
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// 有相同請求在處理,加入等待隊(duì)列.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);}
} else {
// 向mWaitingRequests中插入一個(gè)當(dāng)前請求的空隊(duì)列,表明當(dāng)前請求正在被處理
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}

RequestQueue#add(Request) 方法的調(diào)用流程
處理請求 Cache/NetworkDispatcher
請求被加入緩存請求隊(duì)列或者是網(wǎng)絡(luò)請求隊(duì)列,在后臺我們的緩存處理線程,網(wǎng)絡(luò)處理線程,一直在運(yùn)行著等待著請求的到來。我們先來看看 CacheDispatcher 線程是如何處理的:
CacheDispatcher
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
這是 CacheDispatcher 的構(gòu)造函數(shù),可以看見該對象內(nèi)部持有緩存隊(duì)列,網(wǎng)絡(luò)隊(duì)列,緩存對象,響應(yīng)投遞對象的引用。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化緩存,將緩存目錄下的所有緩存文件的摘要信息加載到內(nèi)存中.
mCache.initialize();
//無線循環(huán),意味著線程啟動之后會一直運(yùn)行
while (true) {
try {
// 從緩存隊(duì)列中取出一個(gè)請求,如果沒有請求則一直等待
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果當(dāng)前請求已經(jīng)取消,那就停止處理它
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 嘗試取出緩存實(shí)體對象
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 沒有緩存,將當(dāng)前請求加入網(wǎng)絡(luò)請求隊(duì)列,讓NetworkDispatcher進(jìn)行處理.
mNetworkQueue.put(request);
continue;
}
// 如果緩存實(shí)體過期,任然將當(dāng)前請求加入網(wǎng)絡(luò)請求隊(duì)列,讓NetworkDispatcher進(jìn)行處理.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 將緩存實(shí)體解析成NetworkResponse對象.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 緩存依舊新鮮,投遞響應(yīng).
mDelivery.postResponse(request, response);
} else {
//緩存已經(jīng)不新鮮了,我們可以進(jìn)行響應(yīng)投遞,然后將請求加入網(wǎng)絡(luò)隊(duì)列中去,進(jìn)行新鮮度驗(yàn)證,如果響應(yīng)碼為 304,代表緩存新鮮可以繼續(xù)使用,不用刷新響應(yīng)結(jié)果
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 標(biāo)記當(dāng)前響應(yīng)為中間響應(yīng),如果經(jīng)過服務(wù)器驗(yàn)證緩存不新鮮了,那么隨后將有第二條響應(yīng)到來.這意味著當(dāng)前請求并沒有完成,只是暫時(shí)顯示緩存的數(shù)據(jù),等到服務(wù)器驗(yàn)證緩存的新鮮度之后才會將請求標(biāo)記為完成
response.intermediate = true;
// 將響應(yīng)投遞給用戶,然后加入網(wǎng)絡(luò)請求隊(duì)列中去.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}
在代碼的注釋中基本上可以理清 CacheDispatcher 的工作流程,下面附上流程圖

CacheDispatcher#run() 方法內(nèi)部流程
NetworkDispatcher
在 CacheDispatcher 當(dāng)中我們會把一些不符合條件的請求加入網(wǎng)絡(luò)請求隊(duì)列中,下面我們來看看在 NetworkDispatcher 的 run() 方法中是怎么來處理這些請求的:
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
這是 NetworkDispatcher 的構(gòu)造函數(shù),可以看見該對象內(nèi)部持有網(wǎng)絡(luò)隊(duì)列,緩存對象,響應(yīng)投遞對象,Network對象的引用。
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 從網(wǎng)絡(luò)隊(duì)列中取出一個(gè)請求,沒有請求則一直等待.
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");
// 如果請求被取消的話,就結(jié)束當(dāng)前請求,不再執(zhí)行
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 執(zhí)行網(wǎng)絡(luò)請求.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果響應(yīng)碼為304,并且我們已經(jīng)傳遞了一次響應(yīng),不需要再傳遞一次驗(yàn)證的響應(yīng),意味著本次請求處理完成。也就是說該請求的緩存是新鮮的,我們直接使用就可以了。
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 在工作線程中想響應(yīng)數(shù)據(jù)解析成我們需要的Response對象,之所以在工作線程進(jìn)行數(shù)據(jù)解析,是為了避免一些耗時(shí)操作造成主線程的卡頓.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 如果允許,則將響應(yīng)數(shù)據(jù)寫入緩存,這里的緩存是需要服務(wù)器支持的,這點(diǎn)我們接下來再說
// 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");
}
// 傳遞響應(yīng)數(shù)據(jù).
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);
}
}
}
在代碼當(dāng)中的第29行,我們調(diào)用 Network 對象的 performRequest(Request<?> request) 方法來執(zhí)行網(wǎng)絡(luò)請求,并且返回我們需要的 NetworkResponse 對象。如果是304響應(yīng),并且我們已經(jīng)傳遞過一次響應(yīng),就不需要在傳遞新的解析數(shù)據(jù),不然我們將數(shù)據(jù)解析成 Reponse 對象,并傳遞給主線程進(jìn)行回到處理,如果該請求允許被緩存,就將該請求的結(jié)果寫入緩存中去,這就是 Networkdispatcher 的工作流程。以下是 NetworkDispatcher 的流程圖:

NetworkDispatcher的run() 方法
執(zhí)行請求 performRequest
在上面 NetworkDispatcher 的代碼中第29行,會通過 Network 的 performRequest 方法來進(jìn)行網(wǎng)絡(luò)請求:
public interface Network {
/**
* 執(zhí)行指定的請求.
* @param request 被處理的請求
* @return 一個(gè) NetworkResponse 對象,包含響應(yīng)的數(shù)據(jù),頭部以及響應(yīng)碼等數(shù)據(jù)
* @throws VolleyError on errors
*/
NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
Network 是一個(gè)接口,它的內(nèi)部只有這一個(gè)方法,在 Volley 中我們最終調(diào)用的是它的實(shí)現(xiàn)類,BasicNetwork 的 performRequest() 方法,方法如下所示:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();//請求開始的時(shí)間
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// 收集頭部
Map<String, String> headers = new HashMap<String, String>();
//對于addCacheHeaders這個(gè)方法,我們也會在緩存的部分進(jìn)行介紹
addCacheHeaders(headers, request.getCacheEntry());//附加請求頭部,用來驗(yàn)證緩存數(shù)據(jù)?
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// 驗(yàn)證緩存的新鮮度.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
//請求的資源沒有修改,意思可以使用緩存中的數(shù)據(jù)
Cache.Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,SystemClock.elapsedRealtime() -requestStart);
}
// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it. 請求持續(xù)時(shí)間
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
//一條真正的網(wǎng)絡(luò)響應(yīng)
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
//......這部分代碼是用來進(jìn)行請求重試的,我們隨后在做解析
}
}
}
在上面代碼的第12行,又會調(diào)用 HttpStack 對象的 performRequest() 方法去執(zhí)行網(wǎng)絡(luò)請求,在 HttpStack 中才真正進(jìn)行網(wǎng)絡(luò)請求,HttpStack 對象在我們一開始調(diào)用 Volley.newRequestQueue() 的方法時(shí)候初始化的,默認(rèn)情況下,如果系統(tǒng)版本在 Android2.3 之前就會創(chuàng)建 HttpClientStack,之后就會創(chuàng)建 HUrlStack 對象,同樣我們也可以實(shí)現(xiàn)自己的 HttpStack對象,通過 Volley 的重載方法 newRequestQueue(Context,HttpStack) 將我們自定義的 HttpStack 傳入即可。
在代碼的18行,我們會進(jìn)行新鮮度驗(yàn)證,如果是304響應(yīng)那么我們會直接利用緩存實(shí)體的數(shù)據(jù)。之后會將響應(yīng)的數(shù)據(jù)組裝成一個(gè) NetworkResponse 對象返回。
在回到之前 NetworkDispatcher 的代碼中,當(dāng)我們獲得這個(gè) NetworkResponse 對象之后,如果是304響應(yīng)那我們的請求處理結(jié)束,不然的話就會調(diào)用 Request#parseNetworkResponse(NetworkResponse) 方法將 NetworkResponse 對象解析成我們需要的 Response 對象,這是一個(gè)抽象方法,由子類具體實(shí)現(xiàn)來解析成期望的響應(yīng)類型,此方法在工作線程調(diào)用。如果該請求可以被緩存,就會將響應(yīng)實(shí)體寫入緩存,標(biāo)記請求被投遞,然后調(diào)用 ResponseDelivery 對象的 postResponse() 方法來將解析的結(jié)果投遞到主線程中,然后進(jìn)行回調(diào)處理。
響應(yīng)傳遞、回調(diào) postResponse
響應(yīng)結(jié)果投遞接口,主要負(fù)責(zé)將響應(yīng)的結(jié)果/錯(cuò)誤,投遞到主線程中,供回調(diào)函數(shù)處理:
public interface ResponseDelivery {
/**
* 傳遞從網(wǎng)絡(luò)或者緩存中解析的Response對象.
*/
void postResponse(Request<?> request, Response<?> response);
/**
* 傳遞從網(wǎng)絡(luò)或者緩存中解析的Response對象.提供一個(gè)Runnable對象,會在傳遞之后執(zhí)行
*/
void postResponse(Request<?> request, Response<?> response, Runnable runnable);
/**
* 傳遞給定請求的Error
*/
void postError(Request<?> request, VolleyError error);
}
它的內(nèi)部實(shí)現(xiàn)類為 ExecutorDelivery,讓我們來看看 ExecutorDelivery 中的具體實(shí)現(xiàn):
private final Executor mResponsePoster;
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);
}
};
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
我們會在 postResponse 方法中調(diào)用 mResponsePoster 對象的 execute 方法,緊接著通過 handler 對象發(fā)送一個(gè)消息,這個(gè)消息是 ResponseDeliveryRunnable 對象,它是 Runnable 的實(shí)現(xiàn)類,并且這個(gè) Runnable 對象會在主線程被執(zhí)行,為什么呢?這是因?yàn)?handler 是在 ExecutorDelivery 初始化的時(shí)候作為參數(shù)傳遞出來的,我們可以看一下該構(gòu)造函數(shù)調(diào)用的時(shí)機(jī):
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
可以看見我們使用主線的 Looper 在構(gòu)建一個(gè) Handler對象,所以由該 Handler 對象發(fā)送的消息,都會在主線程被執(zhí)行,不熟悉 Handler 機(jī)制的可以看下這篇文章 Android消息機(jī)制。
接著我們看看這個(gè) ResponseDeliveryRunnable 類:
private class ResponseDeliveryRunnable implements Runnable {
//......省略
@SuppressWarnings("unchecked")
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
/** 如果請求已經(jīng)取消的話,就不用在投遞了*/
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();
}
}
}
這個(gè) Runnable 會在主線程執(zhí)行,然后將響應(yīng)結(jié)果傳遞給請求的回調(diào)函數(shù)。在代碼中的15,17行就是我們的回調(diào)函數(shù),每一個(gè)請求的響應(yīng)結(jié)果,不論是成功或者是失敗,都會傳遞到這兩個(gè)方法中,第15行的 deliverResponse 方法也是一個(gè)抽象方法,由子類實(shí)現(xiàn),參照 Volley 提供的 StringRequest 可以看見,在這個(gè)方法中,我們最終將期望的對象傳遞給了 Response 中的 onResponse() 方法中,可以看 創(chuàng)建 RequestQueue 對象 這一段落的第一段代碼中的12行
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!");
}
});
請求的結(jié)果最終就會被傳遞這 onResponse,onErrorResponse 中。在這里貼一張 Volley 內(nèi)部的 Response 轉(zhuǎn)換流程圖:

圖片取自codeKK Volley 源碼分析
請求完成 Request#finish(String)
在執(zhí)行完我們的回調(diào)函數(shù)之后,會調(diào)用 Request 中的 finish() 方法標(biāo)記請求完成,然后會將我們的請求從請求隊(duì)列中移除,以下代碼展示了兩處調(diào)用 finish() 的地方:
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
他們的區(qū)別是,第一段代碼的 Response 是從網(wǎng)絡(luò)返回的數(shù)據(jù),而第二段代碼是代表我們之前傳遞了需要驗(yàn)證緩存新鮮度的緩存實(shí)體,經(jīng)驗(yàn)證后緩存新鮮,標(biāo)記請求完成,大家可以查看一下 Response 的 intermediate 屬性被賦值的時(shí)機(jī)就明白了。
// Request中的finish()會調(diào)用 RequestQueue中的finish()方法
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
}
// RequestQueue#finish(Request)
<T> void finish(Request<T> request) {
//從當(dāng)前的請求隊(duì)列中移除該請求
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
//調(diào)用該請求設(shè)置的回調(diào)函數(shù)
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
在移除已經(jīng)完成的請求之后,如果該請求是可以緩存的,并且存在著等待該請求的等待隊(duì)列,就將隊(duì)列中的所有請求加入緩存隊(duì)列(mCacheQueue) 中去,讓緩存線程接著處理。
補(bǔ)充一點(diǎn)---取消請求
- 可以調(diào)用 Request 的 cancel() 方法來標(biāo)記請求取消,這樣我們的回調(diào)函數(shù)永遠(yuǎn)不會被調(diào)用
- 可以調(diào)用 RequstQueue 的 cancelAll(Object) 的方法來批量取消被打上 Object 標(biāo)記的請求
- 可以調(diào)用 RequstQueue 的 cancelAll(RequestFilter) 的方法,按照自定義的過濾方法來取消符合過濾條件的請求
抽象的處理流程圖

通過 CacheDispatcher 和 NetworkDispatcher 兩種線程不斷的從 RequestQueue 中取出請求來處理,然后將獲取的數(shù)據(jù)在子線程解析成我們需要的結(jié)果,通過 ResponseDelivery 的 postResponse 方法將結(jié)果投遞到主線程中去,觸發(fā)回調(diào)。
請求緩存和重試機(jī)制
再此之前我們先看一下 Request 類中的一些重要屬性和方法:
Request<T>
所有請求的抽象類,T 類型代表請求期望的類型,也是響應(yīng)最終被解析成的類型。支持 Get,Post,Put,Delete,Options,Trace,Head,Patch 共8種請求,提供 Low,Normal,Hight,Imediate 4種優(yōu)先級。下面會挑出一些比較重要的字段和方法進(jìn)行講解:
- mShouldCache,用于標(biāo)識請求是否允許緩存,緩存需要客戶端和服務(wù)器的支持,這個(gè)字段僅僅代表客戶端是否支持緩存
- mShouldRetryServerErrors,默認(rèn)值為 false,代表在服務(wù)器返回響應(yīng)碼在 500~599的范圍內(nèi)的話(服務(wù)器錯(cuò)誤),不進(jìn)行請求重試
- mCacheEntry,該請求的緩存實(shí)體,在 CacheDispatcher 處理 CacheQueue 中請求的時(shí)候,會判斷該請求之前是否有緩存存在,如果存在的話將緩存實(shí)體賦值給該字段。用于在服務(wù)器返回 304 響應(yīng)的時(shí)候構(gòu)建 NetworkResponse 對象
- mResponseDelivered,代表該請求的響應(yīng)結(jié)果已經(jīng)被投遞到主線程,只有在響應(yīng)被傳遞給主線程的時(shí)候標(biāo)記為 true,它的作用同樣是用來驗(yàn)證緩存一致性
- abstract protected Response<T> parseNetworkResponse(NetworkResponse response);抽象方法,將請求返回的結(jié)果解析成請求期望的類型,具體的解析方式需要子類實(shí)現(xiàn)
-
abstract protected void deliverResponse(T response);抽象方法,也同樣需要子類自行實(shí)現(xiàn),將解析后的結(jié)果傳遞給請求的回調(diào)函數(shù)
之前我們有提到過要實(shí)現(xiàn)請求緩存需要客戶端和服務(wù)器端共同的支持才行。
請求緩存
之前我們說過,緩存機(jī)制是需要客戶端和服務(wù)器端共同支持的。從客戶端的角度來說:需要實(shí)現(xiàn) Http 緩存相關(guān)的語義;從服務(wù)器的角度來說:需要允許請求的資源被緩存;我們先來看一些有關(guān)于 Http 請求頭和響應(yīng)頭的概念:
HTTP 響應(yīng)頭
Cache-Control:指明當(dāng)前資源的有效期,用來控制從緩存中取數(shù)據(jù),還是需要將請求發(fā)送到服務(wù)器進(jìn)行驗(yàn)證,重新取回?cái)?shù)據(jù),該頭部有以下幾個(gè)值:
- no-cache:使用緩存前必須先向服務(wù)器確認(rèn)其有效性
- no-store:不緩存響應(yīng)的任何內(nèi)容,相同的請求都會發(fā)送給服務(wù)器
- max-age:緩存有效性的最長時(shí)間
- must-revalidate:可緩存,但是使用的時(shí)候必需向源服務(wù)器驗(yàn)證
- proxy-revalidate:要求中間緩存服務(wù)器對緩存的有效性進(jìn)行確認(rèn)
- stale-while-revalidate:在這段時(shí)間內(nèi),允許先使用緩存,但需要向服務(wù)器驗(yàn)證緩存的有效性
Expires:資源失效的日期,如果和 max-age 同時(shí)存在,以 max-age 時(shí)間為準(zhǔn)
ETag:可將資源以字符串形式做唯一性標(biāo)識的方式,服務(wù)器會為每份資源分配對應(yīng)的 Etag 值
Last-Modified:資源最終被修改的時(shí)間
HTTP 請求頭
If-None-Match:如果上一次響應(yīng)的的響應(yīng)頭部中帶有 ETag 響應(yīng)頭,再次請求的時(shí)候會將 ETag 的值作為 If-None-Match 請求頭的值,當(dāng) If-None-Match 的值與請求資源的 Etag 不一致時(shí),服務(wù)器會處理該請求,該字段用來獲取最新的資源
If-Modified-Since:如果上一次響應(yīng)的響應(yīng)頭部中帶有 Last-Modified 響應(yīng)頭,那么再次請求的時(shí)候會將 Last-Modified 的值作為 If-Modified-Since 請求頭的值,在If-Modified-Since 字段指定的之后,資源發(fā)生了更新,服務(wù)器會接受該請求,否則返回 304 響應(yīng)
Entry
Cache接口中的內(nèi)部類,代表著緩存實(shí)體
- data,這是一個(gè)字節(jié)數(shù)組,其實(shí)也就是我們響應(yīng)的 Content 部分
- etag,用來驗(yàn)證緩存一致性的標(biāo)記
- serverDate,數(shù)據(jù)從服務(wù)器返回的時(shí)間
- lastModified,訪問的服務(wù)器資源上次被修改的時(shí)間
- ttl,數(shù)據(jù)的過期時(shí)間,在(softTtl-ttl)這段時(shí)間內(nèi)我們可以使用緩存,但是必須向服務(wù)器驗(yàn)證緩存的有效性
- softTtl,數(shù)據(jù)的新鮮時(shí)間,緩存再次之前一直有效
- responseHeaders,響應(yīng)頭部
- boolean isExpired(),判斷是否過期,ttl代表的時(shí)間小于當(dāng)前時(shí)間就意味著過期了
- boolean refreshNeeded(),顧名思義,當(dāng)softTtl代表的時(shí)間小于當(dāng)前時(shí)間,就代表數(shù)據(jù)不新鮮了,需要刷新數(shù)據(jù)
下面我們來看看 Volley 中是怎么利用這些頭部信息來對響應(yīng)結(jié)果進(jìn)行緩存處理的:
在 NetworkDispatcher 的 run() 方法中有這樣一樣代碼
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
想要緩存響應(yīng)結(jié)果需要滿足兩個(gè)條件,第一條該請求允許緩存,我們創(chuàng)建的每一條請求都是默認(rèn)支持緩存的;第二條就是響應(yīng)對象中的緩存實(shí)體不為空。那么我們需要看一下緩存實(shí)體是在什么時(shí)候被創(chuàng)建的,在執(zhí)行上述的 if 語句判斷之前會執(zhí)行這么一句代碼 Response<?> response = request.parseNetworkResponse(networkResponse); 通過parseNetworkResponse 方法將 NetworkResponse 對象轉(zhuǎn)化為 Response 對象,parseNetworkResponse 方法是一個(gè)抽象方法,我們看一下 StringRequest 中是如何重寫該方法的:
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
在代碼的最后一行,我們會發(fā)現(xiàn) Response 中的 cacheEntry 字段的值來自于 HttpHeaderParser.parseCacheHeaders(response)方法的返回值,下面我們來看看該方法的內(nèi)部實(shí)現(xiàn):
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
//提取響應(yīng)頭部信息
Map<String, String> headers = response.headers;
long serverDate = 0;//資源返回時(shí)間
long lastModified = 0;//資源上一次被修改的時(shí)間
long serverExpires = 0;//資源的有效時(shí)間,以maxAge為主
long softExpire = 0;//資源的新鮮時(shí)間,如果在該時(shí)間內(nèi),發(fā)起相同的請求,那么可以允許使用緩存的信息,不需要將請求發(fā)送給服務(wù)器。
long finalExpire = 0;//緩存過期時(shí)間,在該時(shí)間之后的請求,都將發(fā)送給服務(wù)器,無法使用緩存。
long maxAge = 0;//資源的有效時(shí)間,
//在 staleWhileRevalidate 時(shí)間內(nèi),我們可以先用緩存數(shù)據(jù)展示給用戶,在向服務(wù)器驗(yàn)證緩存的有效性
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;//代表是否有 Cache-Control 頭部
boolean mustRevalidate = false;
String serverEtag = null;//資源在服務(wù)器中的標(biāo)識
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
//獲取 Cache-Contral 頭部的相關(guān)信息
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
//如果出現(xiàn)了 no-cache,no-store指令,代表服務(wù)器不允許緩存響應(yīng),返回的 entry 對象為空
return null;
} else if (token.startsWith("max-age=")) {
//如果出現(xiàn)了 max-age指令,那么服務(wù)器允許緩存該響應(yīng),并且給出響應(yīng)的過期時(shí)間
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
//這段時(shí)間處于新鮮時(shí)間和過期時(shí)間之間,在這段時(shí)間內(nèi)發(fā)起的請求,都可以利用之前的緩存信息,但是需要將請求發(fā)送給服務(wù)器做驗(yàn)證,如果是304 響應(yīng),則請求結(jié)束,不然將重新傳遞響應(yīng)結(jié)果。
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
//代表不區(qū)分新鮮時(shí)間與過期時(shí)間,到了max-age指定的時(shí)間之后,請求都將發(fā)送給服務(wù)器用來驗(yàn)證緩存的有效性。
mustRevalidate = true;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// 在 Cache-Control 和 Expires 頭部都存在的情況下,以Cache-Control為準(zhǔn)
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
這是一個(gè)靜態(tài)工具方法,用于提取響應(yīng)頭部的信息,來構(gòu)建一個(gè) Cache.Entry 類型的緩存對象,針對該方法的分析都已經(jīng)寫在注釋當(dāng)中?,F(xiàn)在我們已經(jīng)了解了將響應(yīng)轉(zhuǎn)化為緩存的部分,下面我們來看看,Volley 是如何使用緩存的,在上面我們介紹 CacheDispatcher 工作流程的時(shí)候已經(jīng)大致看過了處理緩存請求的過程,下面我再針對緩存的部分具體分析一下,下面是 CacheDispatcher run() 方法的部分代碼:
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
mNetworkQueue.put(request);
continue;
}
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
if (!entry.refreshNeeded()) {
mDelivery.postResponse(request, response);
} else {
request.setCacheEntry(entry);
response.intermediate = true;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
}
}
});
}
先從緩存中取出緩存實(shí)體,然后通過 isExpired() 判斷該緩存有無過期,內(nèi)部是通過 return this.ttl < System.currentTimeMillis(); 的形式來進(jìn)行比較,ttl 的含義,在??的代碼中我們已經(jīng)介紹過了,之后通過 return this.softTtl < System.currentTimeMillis();的方式來判斷實(shí)體是否需要刷新,softTtl的值我們同樣已經(jīng)介紹過了,如果不需要刷新,那么我們就可以直接使用緩存,不然的話就需要向服務(wù)器驗(yàn)證緩存的有效性。那么如何通知服務(wù)器來進(jìn)行驗(yàn)證呢,接下來我們看看執(zhí)行請求的時(shí)候,在 BasicNetwork # performRequest() 中調(diào)用 HttpStack 執(zhí)行請求執(zhí)行,會調(diào)用 addCacheHeaders(headers, request.getCacheEntry()); 方法,用來附加請求頭部信息,我們看看該方法內(nèi)部的實(shí)現(xiàn):
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.lastModified > 0) {
Date refTime = new Date(entry.lastModified);
headers.put("If-Modified-Since",DateUtils.formatDate(refTime));
}
}
結(jié)合我們上面對請求頭的介紹,大家很容易明白這段代碼的意思,如果資源沒有發(fā)生改變,就會返回 304 響應(yīng)碼,告訴我們可以使用之前的緩存,對緩存的分析就到這里。
Byte[] 緩存
再次看一下下面這段代碼:
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
這段代碼位于 BasicNetwork 的 performRequest() 方法中,用于將返回的 HttpEntity 對象轉(zhuǎn)化為 byte[] 對象,這個(gè)字節(jié)數(shù)組最后將用于被轉(zhuǎn)化為 T 類型的對象,也就是請求期望的對象,我們之所以沒有直接返回 HttpEntity 對象,而把它解析成 byte[] 就是為之后 Request 的子類進(jìn)行解析提供便利。
接下來,我們看一下用于轉(zhuǎn)換數(shù)據(jù)的方法:
private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
PoolingByteArrayOutputStream bytes =
new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
byte[] buffer = null;
try {
InputStream in = entity.getContent();
if (in == null) {
throw new ServerError();
}
buffer = mPool.getBuf(1024);
int count;
while ((count = in.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
} finally {
try {
// Close the InputStream and release the resources by "consuming the content".
entity.consumeContent();
} catch (IOException e) {
// This can happen if there was an exception above that left the entity in
// an invalid state.
VolleyLog.v("Error occurred when calling consumingContent");
}
mPool.returnBuf(buffer);
bytes.close();
}
}
上面的代碼中有兩種類型對象我們需要注意,一個(gè)是 PoolingByteArrayOutputStream 對象 bytes,另一個(gè)是 ByteArrayPool 對象 mPool。我們先說一下沒有這兩個(gè)對象之前的轉(zhuǎn)化方式,首先我們從 HttpEntity 打開是一個(gè)輸入流,然后構(gòu)建一個(gè)緩沖字節(jié)數(shù)組 buffer,不斷的將輸入流的數(shù)據(jù)寫入 buffer 中,在通過 ByteArrayOutputStream 不斷的將 buffer 中的數(shù)據(jù)輸入到 ByteArrayOutputStream 中的 buf 字節(jié)數(shù)組中,如果 buf 的大小不夠,將會 new 出新的 byte[] 對象,賦值給 buf
在這個(gè)過程中,byte[] 對象被不斷的創(chuàng)建和銷毀,內(nèi)存不斷的分配和回收,如果處理不斷可能會造成內(nèi)存泄漏,消耗了系統(tǒng)資源,Volley 通過 ByteArrayPool 和 PoolingByteArrayOutputStream 來解決這個(gè)問題,先看一下 ByteArrayPool 的代碼:
public class ByteArrayPool {
private final List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();//按照使用順序排列的Buffer緩存
private final List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);//按照 byte[]大小排列的Buffer緩存
/** 當(dāng)前緩存池中緩存的總大小 */
private int mCurrentSize = 0;
/**
* 緩存池上限,達(dá)到這個(gè)限制之后,最近最長時(shí)間未使用的 byte[] * 將被丟棄
*/
private final int mSizeLimit;
/** 通過 buffer 的大小進(jìn)行比較 */
protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
@Override
public int compare(byte[] lhs, byte[] rhs) {
return lhs.length - rhs.length;
}
};
public ByteArrayPool(int sizeLimit) {
mSizeLimit = sizeLimit;
}
// 從緩存池中獲取所需的 byte[],如果沒有大小合適的 byte[],將新new一個(gè) byte[] 對象
public synchronized byte[] getBuf(int len) {
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
if (buf.length >= len) {
mCurrentSize -= buf.length;
mBuffersBySize.remove(i);
mBuffersByLastUse.remove(buf);
return buf;
}
}
return new byte[len];
}
// 將使用后的 byte[] 返還給 緩存池
public synchronized void returnBuf(byte[] buf) {
if (buf == null || buf.length > mSizeLimit) {
return;
}
mBuffersByLastUse.add(buf);
int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
if (pos < 0) {
pos = -pos - 1;
}
mBuffersBySize.add(pos, buf);
mCurrentSize += buf.length;
trim();
}
// 對緩存池?cái)?shù)據(jù)進(jìn)行修剪
private synchronized void trim() {
while (mCurrentSize > mSizeLimit) {
byte[] buf = mBuffersByLastUse.remove(0);
mBuffersBySize.remove(buf);
mCurrentSize -= buf.length;
}
}
}
簡單的來說就是,ByteArrayPool 通過在內(nèi)存中維護(hù)了兩組 byte[] 對象,來減少重復(fù)創(chuàng)建 byte[] 的次數(shù)。當(dāng)我們需要使用 byte[] 的時(shí)候,通過 Pool 來獲取一個(gè) byte[],當(dāng)使用完畢的時(shí)候,再將該字節(jié)數(shù)組返回給 Pool。
在來看看 PoolingByteArrayOutputStream 的代碼,他是 ByteArrayOutputStream 的子類,內(nèi)部使用了 ByteArrayPool 來代替 new Byte[]操作,提高性能:
public class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
/**
* 默認(rèn)的 buf 大小
*/
private static final int DEFAULT_SIZE = 256;
private final ByteArrayPool mPool;
/**
* 如果寫入的數(shù)據(jù)超出 buf 的大小,將會擴(kuò)展 buf 的大小,之前通過 new Byte[] 來分配更大的空間,現(xiàn)在通過 ByteArrayPool 提供,避免創(chuàng)建對象
*/
public PoolingByteArrayOutputStream(ByteArrayPool pool) {
this(pool, DEFAULT_SIZE);
}
public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
mPool = pool;
buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
}
//關(guān)閉輸出流,將使用的 buf 歸還到緩沖池中
@Override
public void close() throws IOException {
mPool.returnBuf(buf);
buf = null;
super.close();
}
// GC 的時(shí)候調(diào)用,我們不能保證高方法的觸發(fā)時(shí)機(jī),所以最好手動調(diào)用 close 方法
@Override
public void finalize() {
mPool.returnBuf(buf);
}
/**
* 擴(kuò)展 Buf 的大小
*/
private void expand(int i) {
// 判斷 buffer 能否處理更多的byte,不能的話將要擴(kuò)展 buffer 的大小
if (count + i <= buf.length) {
return;
}
byte[] newbuf = mPool.getBuf((count + i) * 2);
System.arraycopy(buf, 0, newbuf, 0, count);
mPool.returnBuf(buf);
buf = newbuf;
}
@Override
public synchronized void write(byte[] buffer, int offset, int len) {
expand(len);
super.write(buffer, offset, len);
}
@Override
public synchronized void write(int oneByte) {
expand(1);
super.write(oneByte);
}
}
內(nèi)部的操作很簡單,在每次寫入的時(shí)候都會檢查 buffer 大小是否合適,是否需要擴(kuò)展,在輸出流結(jié)束的時(shí)候,我們需要手動顯示調(diào)用 close 方法,來歸還從 ByteArrayPool 中擴(kuò)展的 byte[]
請求重試
RetryPolicy
RetryPolicy 接口,代表著請求重試的行為:
public interface RetryPolicy {
//當(dāng)前超時(shí)的時(shí)間
int getCurrentTimeout();
//當(dāng)前重試的次數(shù)
int getCurrentRetryCount();
/**
* 準(zhǔn)備重試
* 當(dāng)拋出 VolleyError 即意味著停止重試
*/
void retry(VolleyError error) throws VolleyError;
}
在我們初始化 Request 的時(shí)候,會給 Request 設(shè)置一個(gè)默認(rèn)的重試策略 DefaultRetryPolicy 下面我們來看看它的代碼:
DefaultRetryPolicy
public class DefaultRetryPolicy implements RetryPolicy {
/** 當(dāng)前超時(shí)毫秒數(shù). */
private int mCurrentTimeoutMs;
/** 當(dāng)前重試次數(shù). */
private int mCurrentRetryCount;
/** 最大重試次數(shù). */
private final int mMaxNumRetries;
/** 超時(shí)乘積因子,用來累計(jì)計(jì)算超時(shí)時(shí)間. */
private final float mBackoffMultiplier;
/** 默認(rèn)超時(shí)時(shí)間 */
public static final int DEFAULT_TIMEOUT_MS = 2500;
/** 默認(rèn)重試次數(shù) */
public static final int DEFAULT_MAX_RETRIES = 1;
/** 默認(rèn)超時(shí)乘積因子 */
public static final float DEFAULT_BACKOFF_MULT = 1f;
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* 返回當(dāng)前超時(shí)時(shí)間
*/
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* 返回當(dāng)前重試次數(shù).
*/
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* 返回超時(shí)乘積因子.
*/
public float getBackoffMultiplier() {
return mBackoffMultiplier;
}
/**
* 為下一次重試計(jì)算重試時(shí)間
* @param error 上一次請求的錯(cuò)誤.
*/
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
//累計(jì)下一次重試的時(shí)間
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
//拋出參數(shù)中傳入的錯(cuò)誤,就代表停止重試
}
/**
* 判斷是否允許下一次重試
*/
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
}
默認(rèn)的重試策略也挺簡單的,每一次累計(jì)超時(shí)的時(shí)間,然后判斷是否到達(dá)重試的上限,如果達(dá)到上限,就拋出入?yún)⒌?VolleyError 代表停止重試,那么為什么拋出傳入的參數(shù),就可以停止重試了呢,我們繼續(xù)看看 BasicNetwork 中的 performRequest() 方法:
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
while (true) {
try{
//........省略
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
NetworkResponse networkResponse;
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else if (statusCode >= 400 && statusCode <= 499) {
// Don't retry other client errors.
throw new ClientError(networkResponse);
} else if (statusCode >= 500 && statusCode <= 599) {
if (request.shouldRetryServerErrors()) {
attemptRetryOnException("server",
request, new ServerError(networkResponse));
} else {
throw new ServerError(networkResponse);
}
} else {
// 3xx? No reason to retry.
throw new ServerError(networkResponse);
}
} else {
attemptRetryOnException("network", request, new NetworkError());
}
}
}
}
這部分的代碼我們在上面分析 BasicNetwork 的代碼的時(shí)候已經(jīng)介紹過了,當(dāng)時(shí)省略了 catch 塊中的代碼,catch 塊中的代碼就是用來實(shí)現(xiàn)請求重試的。
當(dāng)捕獲到 SocketTimeoutException 和 ConnectTimeoutException 異常的時(shí)候調(diào)用 attemptRetryOnException() 方法來進(jìn)行重試,該方法中會調(diào)用 retryPolicy.retry(exception);方法,該方法我們已經(jīng)分析過了,是用來計(jì)算請求超時(shí)時(shí)間,以及是否達(dá)到重試上限,如果可以重試,那么該方法執(zhí)行完,會繼續(xù)下一次循環(huán),再次發(fā)起請求;當(dāng)達(dá)到重試上線,無法進(jìn)行重試的時(shí)候我們會拋出 VolleyError 的實(shí)例,在 BasicNetwork#performRequest() 中我們沒有捕獲 VolleyError 異常,因次會跳出循環(huán),停止重試,該方法執(zhí)行結(jié)束,在外部 NetworkDispatcher 的 run() 方法中捕獲了該異常,將異常結(jié)果傳遞到主線程中供回調(diào)函數(shù)處理。
- ConnectTimeoutException 表示請求超時(shí)
- SocketTimeoutException 表示響應(yīng)超時(shí)
在代碼中同樣對 AuthFailureError 以及服務(wù)器異常提供了重試操作。
一些總結(jié)
- Volley 可以幫助我們完成請求的自動調(diào)度處理,我們只需要將 Request 加入 RequestQueue 就可以了
- 提供了多個(gè)并發(fā)線程幫助處理請求,但是不適合大文件下載,因?yàn)樵陧憫?yīng)解析的過程中,會將所有響應(yīng)的數(shù)據(jù)保存在內(nèi)存中
- 提供了緩存(一定程度上符合 HTTP 語義)
- 支持請求的優(yōu)先級,可以方便的取消請求(根據(jù) Tag 或者自定義的過濾規(guī)則)
- 提供了請求重試機(jī)制,可以自定義重試機(jī)制(簡單,方便)
- Volley 面向接口編程,采用組合(少用繼承)的形式提供功能,可以自定義 Network,HttpStack,Cache 等實(shí)現(xiàn),擴(kuò)展性很高