一、閑話
Volley 分析主要講解其對網(wǎng)絡(luò)框架的封裝,以及其主要涉及到的一些技術(shù)細(xì)節(jié)。二話不說先盜個圖來看看。

看起來就是個老生常談的話題,先請求緩存,緩存沒有再去請求網(wǎng)絡(luò)。然而在實(shí)際編碼過程中發(fā)現(xiàn)有很多零零碎碎的細(xì)節(jié)需要我們?nèi)タ紤],例如緩存的管理,如何根據(jù)請求能找到相應(yīng)的緩存等很多緩存相關(guān)的問題。再比如網(wǎng)絡(luò)的線程池是如何管理的,緩存什么時候決定是否要去發(fā)起網(wǎng)絡(luò)請求,請求回來的數(shù)據(jù)又如何處理等諸多的網(wǎng)絡(luò)問題。如何優(yōu)雅的解決這些問題,形成封裝,并且還可以保持靈活的拓展性,這就是Volley在這里所做的事情了。
秉著向大神學(xué)習(xí)以及致敬的態(tài)度,當(dāng)然也是為了讓自己的代碼更有Bigger,就來認(rèn)認(rèn)真真的分析分析Volley的源碼,學(xué)習(xí)學(xué)習(xí)架構(gòu)以及設(shè)計(jì)模式等的應(yīng)用。和其他分析Volley的思路是一致的,以一條Request 發(fā)出到得到一個 Response 為線索來進(jìn)行分析。所謂萬事都得開個頭,頭開好了就能為后面做更多的事情了。對于Volley來說,這個頭莫過于就是隊(duì)列的初始化了。
二、隊(duì)列的初始化
下面這個圖是我自己畫的,丑是丑了點(diǎn),但湊合著還是可以看的,看起來想表達(dá)的意思還是清楚了的。隊(duì)列的創(chuàng)建主要初始化緩存管理,緩存分發(fā)器,網(wǎng)絡(luò)分發(fā)器,真正執(zhí)行網(wǎng)絡(luò)的Network 以及結(jié)果返回器。似乎整個架構(gòu)就這么點(diǎn)事兒了。

而所謂的初始化,到底初始化了什么?
1.RequestQueue的創(chuàng)建。
RequestQueue是可以創(chuàng)建多個的,每個請求對列里面只有一個緩存分發(fā)器,默認(rèn)情況下只有4個網(wǎng)絡(luò)分發(fā)器。
一般調(diào)用的是這個唯一參數(shù)為 Context 的 newRequestQueue() 方法
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
而其內(nèi)部其調(diào)用了其私有的 newRequestQueue()。RequestQueue是被直接 new 出來的對象,可見隊(duì)列是可以被創(chuàng)建多個的。一個進(jìn)程里不宜創(chuàng)建過多的隊(duì)列。
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();
上面代碼中最后句 start() 就是啟動了緩存以及網(wǎng)絡(luò)的分發(fā)器。
/** Starts the dispatchers in this queue. */
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();
}
}
2.緩存的創(chuàng)建。
緩存策略用的也是LRU,底層依賴LinkedHashMap來實(shí)現(xiàn)。主要是設(shè)置了其參數(shù)accessorder為true。每訪問一次,對應(yīng)的節(jié)點(diǎn)modCount次數(shù)就會加1,從而在最后通過過取迭代器時依據(jù)modCount排序獲得一個排序迭代器。
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true);
3.Network的創(chuàng)建
Network其實(shí)是一個Interface。其只有一個唯一的方法performRequest()。這個用戶可以自己實(shí)現(xiàn),也就是說用戶可以自己設(shè)置通過什么方式去真正執(zhí)行網(wǎng)絡(luò)連接以獲取數(shù)據(jù)。來看一下默認(rèn)的實(shí)現(xiàn)吧。
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 {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
// At some point in the future we'll move our minSdkVersion past Froyo and can
// delete this fallback (along with all Apache HTTP code).
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info =
context.getPackageManager().getPackageInfo(packageName, /* flags= */ 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);
}
嗯,就是在創(chuàng)建RequestQueue時,如果用戶自己設(shè)置了HttpStack就會用用戶的HttpStack來包裝出一個Network。如果沒有則分版本進(jìn)行,sdk > 9,也就是Android 2.3 時就用 HurlStack ,其實(shí)就是 HttpUrlConnection的實(shí)現(xiàn)。如果比它還小——這樣的系統(tǒng)還在嗎,就用封裝Apache的AndroidHttpClient。原因呢是因?yàn)镠ttpUrlConnection 對于 keep-alive處理的bug,協(xié)議相關(guān),具體后面還會有分析系統(tǒng)原生網(wǎng)絡(luò)庫的文章再說明。后面Andrioid網(wǎng)絡(luò)庫棄用了Apache,建議用HttpUrlConnection或者Volley。而其實(shí)Android 4.4 之后,HttpUrlConnection的底層又換成了鼎鼎大名的OkHttp。
4.啟動緩存和網(wǎng)絡(luò)分發(fā)器。
對列啟動后會啟動緩存分發(fā)器和網(wǎng)絡(luò)分發(fā)器,而所謂的分發(fā)器就是Java的線程。緩存分發(fā)器主動從緩存對列里面撈請求,如果沒有撈到就會進(jìn)入await狀態(tài)。這一底層機(jī)制的實(shí)現(xiàn)是依賴于PriorityBlockingQueue的實(shí)現(xiàn),其本身就是一個阻塞式隊(duì)列,其內(nèi)部實(shí)現(xiàn)了生產(chǎn)者與消費(fèi)者的模型。網(wǎng)絡(luò)分發(fā)器與緩存分發(fā)器的實(shí)現(xiàn)一致,都是基于PriorityBlockingQueue的實(shí)現(xiàn)。流程上來說,一個請求是先進(jìn)入到了緩存對列,由緩存隊(duì)列處理完后仍需網(wǎng)絡(luò)請求或者更新就會進(jìn)入到網(wǎng)絡(luò)隊(duì)列。
private void processRequest() throws InterruptedException {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
processRequest(request);
}
上面這點(diǎn)代碼拷的緩存分發(fā)器的,網(wǎng)絡(luò)分發(fā)器和其非常的雷同。上面主要是調(diào)用了PriorityBlockingQueue#take()方法??纯催@個take()方法的實(shí)現(xiàn)。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
上面可以看到,如果隊(duì)列為空就會進(jìn)入等待狀態(tài)notEmpty.await()。再來看一下PriorityBlockingQueue#put()方法
public void put(E e) {
offer(e); // never need to block
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
put()方法非常簡單,其繼續(xù)調(diào)用了 offer()方法。這個方法里面其他亂七八糟的先不管,其最關(guān)鍵的一句是 notEmpty.signal() 。這個信號的發(fā)射就喚醒了前面take()因隊(duì)列為空的await狀態(tài)。這是一個典型的生產(chǎn)者-消費(fèi)者模型了吧。
4.稍微扯一下優(yōu)先級
既然知道了分發(fā)器是PriorityBlockingQueue,那也應(yīng)該知道了為什么Volley能控制網(wǎng)絡(luò)請求的優(yōu)先級了,就是這個優(yōu)先級阻塞隊(duì)列了。而優(yōu)先級的依據(jù)就是Request的mSequence的值。這就是一個簡單的整型值,Request的CompareTo()方法決定了其排序。這個優(yōu)先級用戶可以自行進(jìn)行設(shè)置,如果用戶沒有設(shè)置,那么隊(duì)列自己會維護(hù)一個,每添加一個 Request 到隊(duì)列就會自增 1。
/** Gets a sequence number. */
public int getSequenceNumber() {
return mSequenceGenerator.incrementAndGet();
}
環(huán)境都初始化好了,接下來發(fā)起Request以及返回Response就是水到渠成的事了。
三、發(fā)起請求
又是一個比較丑的圖,而且相比起大神的圖來看,很亂的樣子,簡直就是什么玩意兒呀。亂的原因主要是因?yàn)樽鲌D的水平確實(shí)一般,就跟這個寫作水平一樣的。
其次是因?yàn)榧尤肓松a(chǎn)者-消費(fèi)者的等待-喚醒模型以及其中的條件判斷——懶的畫流程圖,而且還不連貫。能認(rèn)真看完還是會有不少收獲的。

1.緩存的初始化
隊(duì)列初始化時只是創(chuàng)建了緩存,真正初始化時是在緩存分發(fā)器跑起來時。還來看一點(diǎn)代碼吧。
@Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
try {
long entrySize = file.length();
CountingInputStream cis =
new CountingInputStream(
new BufferedInputStream(createInputStream(file)), entrySize);
try {
CacheHeader entry = CacheHeader.readHeader(cis);
// NOTE: When this entry was put, its size was recorded as data.length, but
// when the entry is initialized below, its size is recorded as file.length()
entry.size = entrySize;
putEntry(entry.key, entry);
} finally {
// Any IOException thrown here is handled by the below catch block by design.
//noinspection ThrowFromFinallyBlock
cis.close();
}
} catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
代碼看起來很多,但其實(shí)關(guān)鍵就兩點(diǎn)。一方面如果當(dāng)前緩存文件夾為空則直接返回。另一方面如果當(dāng)前緩存文件夾不為空則遍歷所有的文件將其內(nèi)容解析成緩存的Entry(理解成一個單位),并put到緩存當(dāng)中。
2.緩存的key管理
緩存管理中另一個就是這個key的生成,如何保證它的唯一性這個是非常重要的,因?yàn)殛P(guān)系到了如何判斷一個請求是否有相應(yīng)的緩存。看看代碼。
/** Returns the cache key for this request. By default, this is the URL. */
public String getCacheKey() {
String url = getUrl();
// If this is a GET request, just use the URL as the key.
// For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
// legacy behavior where all methods had the same cache key. We can't determine which method
// will be used because doing so requires calling getPostBody() which is expensive and may
// throw AuthFailureError.
// TODO(#190): Remove support for non-GET methods.
int method = getMethod();
if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
return url;
}
return Integer.toString(method) + '-' + url;
}
看起來并沒有那么的復(fù)雜,就是url或者h(yuǎn)ttp請求的方法所對應(yīng)的int加上下劃線,再加上url所構(gòu)成的。url當(dāng)然唯一了,好像沒什么可以叨叨了。但為了安全起見,將url轉(zhuǎn)成MD5是否更合適,而且還能省一些空間呢。
3.緩存的處理流程
其實(shí)上面的時序圖已經(jīng)很明顯了,緩存未命中或者已經(jīng)過期就將請求丟給網(wǎng)絡(luò)隊(duì)列。網(wǎng)絡(luò)隊(duì)列會喚醒網(wǎng)絡(luò)分發(fā)器進(jìn)行處理。網(wǎng)絡(luò)分發(fā)器有多個,當(dāng)然是誰空閑誰搶到誰就執(zhí)行了。緩存的處理邏輯里面還是有東西可以叨叨的,其結(jié)合了http協(xié)議本身對緩存的處理。先來看看 Entry 中對緩存相關(guān)的定義。
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
所認(rèn)識的好像就是etag,serverDate(服務(wù)器的時間),lastModified(上次修改時間),ttl 以及 softTtl ,有點(diǎn)不明所以。
關(guān)于 ttl 以及 softTtl 從下面的代碼中可以看到表示的是失效時間和需要刷新的時間。
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
這里其實(shí)涉及到http協(xié)議緩存相關(guān)的東西,不感興趣其實(shí)可以跳過。下面代碼也有點(diǎn)長,如果不想看可以直接看代碼下面的結(jié)論。
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag = null;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",", 0);
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return null;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
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 takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
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;
entry.allResponseHeaders = response.allHeaders;
return entry;
}
前一部分都是對header中的關(guān)鍵字進(jìn)行讀取以及解析。能夠看到有:
"Date":服務(wù)器時間
"Cache-Control":緩存控制字段,其下還關(guān)注了"no-cache","max-age=","stale-while-revalidate=","must-revalidate"以及"proxy-revalidate"
"Expires":緩存期望失效時間
"Last-Modified":文件上次被修改時間
"ETag":文件是否被修改標(biāo)記字段
要全部理解這些字段以及它們的優(yōu)先級需要再花點(diǎn)功夫。這里主要關(guān)注一下softTtl 和 ttl 的計(jì)算。從代碼中可以看到 softTtl = softExpire ,而 ttl = finalExpire.
而這兩者的計(jì)算情況是:
(1) 有 cache-control字段的情況下——說明它的優(yōu)先級比其他緩存字段要高
softExpire = 現(xiàn)在開始 + 緩存最大能存活的時間
finalExpire = softExpire 或者 softExpire + stale-while-revalidate 的時間
(2)沒有 cache-control字段,但服務(wù)器時間存在,且緩存期望失效時間比服務(wù)器時間要大
softExpire = 現(xiàn)在開始 + (緩存失效時間與服務(wù)器時間的時間差)
finalExpire = softExpire
看完了上面這一部分可能還是會有點(diǎn)糊涂,那一張“借”來的請求時緩存處理的流程圖吧,說不定看了就會有幫助。

從上面的分析結(jié)果來看,softTtl 以及 ttl 是幫助框架提前判斷緩存是否有效以及是否需要更新的。而其他字段如ETag,Last-Modified 等協(xié)議字段是在執(zhí)行真正網(wǎng)絡(luò)請求時來決定緩存策略的。
緩存的理解與應(yīng)用都是難點(diǎn),所以花的篇幅有點(diǎn)長,也不免有點(diǎn)啰嗦了。還是繼續(xù)網(wǎng)絡(luò)的分發(fā)吧。
4.網(wǎng)絡(luò)分發(fā)器的處理
前面已經(jīng)說過,緩存未命中或者緩存已經(jīng)過期就會將請求送往網(wǎng)絡(luò)隊(duì)列,網(wǎng)絡(luò)隊(duì)列收到請求后就會依賴于PriorityBlockingQueue底層的實(shí)現(xiàn)機(jī)制喚醒網(wǎng)絡(luò)分發(fā)器繼續(xù)處理。
private void processRequest() throws InterruptedException {
// Take a request from the queue.
Request<?> request = mQueue.take();
processRequest(request);
}
上面即是網(wǎng)絡(luò)分發(fā)器去撈請求,如果沒撈到就會進(jìn)入等待狀態(tài)。撈到了就會繼續(xù)處理請求。
void processRequest(Request<?> request) {
long startTimeMs = SystemClock.elapsedRealtime();
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");
request.notifyListenerResponseNotUsable();
return;
}
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");
request.notifyListenerResponseNotUsable();
return;
}
// 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);
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();
}
}
這段代碼看起來多,而關(guān)鍵點(diǎn)其實(shí)就那么幾點(diǎn):
(1)請求是否已經(jīng)被取消,如果已經(jīng)被取消則終止
(2)通過mNetwork.performRequest(request)發(fā)出真正的網(wǎng)絡(luò)請求。關(guān)于Network在RequestQueue初始化的時候已經(jīng)說過了,可以用戶自己設(shè)置,也可以用默認(rèn)的HttpUrlConnection的實(shí)現(xiàn),反正現(xiàn)在底層都是 OkHttp 的實(shí)現(xiàn)了。
(3)拿到請求后由相對應(yīng)的Request自行進(jìn)行Response解析以得到網(wǎng)絡(luò)請求的結(jié)果。緩存方面,如果需要緩存就先緩存一份,最后和 Request 一起通過 ResponseDelivery將結(jié)果傳遞回去。
(4)再者就是網(wǎng)絡(luò)出現(xiàn)錯誤或者其他異常,也是通過ResponseDelivery給傳遞回去。
四、傳遞返回結(jié)果
其實(shí)除了ResponseDelivery會返回傳遞結(jié)果,還有一個Request#notifyListenerResponseReceived()也是返回結(jié)果的。它們各返回給誰呢?先看一個比較丑的圖。

(1) ResponseDelivery只是一個Interface,它的實(shí)現(xiàn)者是 ExecutorDelivery。
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);
}
};
}
從它的初始化可以看到,其內(nèi)部又包含了一個 mResponsePoster,這個就是一個Executor,其實(shí)就是希望通過一個線程池來進(jìn)行發(fā)送。而最關(guān)鍵的就是這個參數(shù) Handler,也就是說結(jié)果最后是返回給了相應(yīng)的接收 Handler。而這個Handler默認(rèn)情況下包裝的就是 MainLooper,如下代碼,又回到了RequestQueue的初始化。前面的RequestQueue其實(shí)會間接地調(diào)用到這個版本的構(gòu)造函數(shù)。
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(
cache,
network,
threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
被這個Handler處理的其實(shí)是ResponseDeliveryRunnable。在它的run()方法里面才真正地調(diào)用了對應(yīng)Request的deliverResponse()。這里借用了一個StringRequest類,不然這戲都沒法演了。
(2)再來看Request#notifyListenerResponseReceived()方法。其最終調(diào)用的是CacheDispatcher#WaitingRequestManager.onResponseReceived()方法。
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) {
if (VolleyLog.DEBUG) {
VolleyLog.v(
"Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests.
for (Request<?> waiting : waitingRequests) {
mCacheDispatcher.mDelivery.postResponse(waiting, response);
}
}
}
這個通知主要是通過緩存分發(fā)器通知所有和當(dāng)前具有同一 cache key的請求結(jié)果已經(jīng)返回了。
五、總結(jié)
(1)Volley庫是一個比較完善的封裝,編碼具有比較高的Bigger,簡單,清晰,可擴(kuò)展性強(qiáng),而又不失強(qiáng)大的功能。
(2)RequestQueue是整個庫的核心,從發(fā)起請求-緩存處理-執(zhí)行網(wǎng)絡(luò)請求-解析結(jié)果-返回結(jié)果,一條龍服務(wù)。一個進(jìn)程里面是可以有多個RequestQueue實(shí)例的。
(3)Cache是這個庫的一大亮點(diǎn),其實(shí)現(xiàn)了Http協(xié)議的緩存,而Cache內(nèi)容到閃存上所用的也是類DiskLRUCache算法。但其依賴的LinkedHashMap的實(shí)現(xiàn),利用它的排序功能實(shí)現(xiàn)了LRU算法。
(4)最關(guān)鍵的隊(duì)列其實(shí)只有兩個,一個緩存隊(duì)列,一個網(wǎng)絡(luò)隊(duì)列。緩存隊(duì)列由緩存分發(fā)器處理,網(wǎng)絡(luò)隊(duì)列由網(wǎng)絡(luò)分發(fā)器處理。緩存隊(duì)列處理完之后才會視情況交給網(wǎng)絡(luò)隊(duì)列來處理。