本文將基于Android N Framework層中的Volley庫,對Volley請求庫中的網(wǎng)絡(luò)緩存框架源碼進(jìn)行分析
在上面兩篇文章中,我們已經(jīng)對Volley的簡單使用和圖片加載的源碼進(jìn)行了簡單分析,在這篇文章中,我們將具體對Volley的網(wǎng)絡(luò)緩存源碼進(jìn)行分析吗货。
1. 使用HTTP緩存的作用
使用緩存其實(shí)主要有兩個(gè)原因隶糕。首先降低延遲,緩存離客戶端更近,因此闸英,從緩存請求內(nèi)容比從源服務(wù)器所用時(shí)間更少谆沃,呈現(xiàn)速度更快钝凶,網(wǎng)站就顯得更靈敏。其次降低網(wǎng)絡(luò)傳輸, 副本被重復(fù)使用唁影,大大降低了用戶的帶寬使用耕陷,其實(shí)也是一種變相的省錢(如果流量要付費(fèi)的話),同時(shí)保證了帶寬請求在一個(gè)低水平上据沈,更容易維護(hù)了哟沫。
2. Volley中的緩存實(shí)現(xiàn)
在前面文章中,我們已經(jīng)知道,Volley中RequestQueue是通過下面方法進(jìn)行構(gòu)建的
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
在初始化RequestQueue時(shí)會啟動兩個(gè)循環(huán)線程CacheDispatcher和NetworkDispatcher,它們會不斷的遍歷其耦合的隊(duì)列,執(zhí)行響應(yīng)操作。
/**
* 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();
}
}
而對于每一個(gè)add()到RequestQueue中的Request對象,需要設(shè)置SequenceNumber(用來決定執(zhí)行順序),其中對于addMarker(Adds an event to this request's event log; for debugging.),注釋上說明是用來做調(diào)試用的,我們不用重點(diǎn)關(guān)注锌介。
對于不需要進(jìn)行緩存的請求,會直接加入到NetWorkQueue中,由NetWorkDispatcher進(jìn)行處理嗜诀。而對于需要緩存的請求,則先判斷waitingRequest隊(duì)列中是否存在,如果存在,說明已經(jīng)有相同任務(wù)通過CacheDispatcher進(jìn)行處理,則會加入到WaitingReqeust的Map中進(jìn)行排隊(duì),否則,則會加入到CacheDispatcher中進(jìn)行處理。
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
此處的緩存是WaitingRequests,其作用主要是對于短時(shí)間內(nèi)重復(fù)請求的Request進(jìn)行緩存,其結(jié)構(gòu)為HashMap孔祸。
我們在此處還要留意隆敢,就是對于WaitingRequests的處理,我們可以看到加入后等待隊(duì)列后崔慧,并無處理拂蝎,事實(shí)上,在已有請求Request Cancel時(shí)或者處于緩存尊浪,無需變化時(shí)匣屡,都會由Request調(diào)用finish()封救,進(jìn)而將所有等待隊(duì)列中的請求加入到緩存隊(duì)列中進(jìn)行遍歷執(zhí)行,最終返回緩存結(jié)果捣作。具體代碼參考如下所示:
/**
* Called from {@link Request#finish(String)}, indicating that processing of the given request
* has finished.
*
* <p>Releases waiting requests for <code>request.getCacheKey()</code> if
* <code>request.shouldCache()</code>.</p>
*/
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
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);
}
}
}
}
當(dāng)時(shí)看到這段的時(shí)候當(dāng)時(shí)挺疑惑誉结,為什么不直接返回一個(gè)緩存結(jié)果,將等待隊(duì)列的請求都拋棄掉券躁,后來想想也對惩坑,對于重復(fù)請求返回一個(gè)請求結(jié)果,這也不符合實(shí)際使用場景也拜。
3. CacheDispatcher的緩存策略:
通過上文中,我們可以看到,Volley會默認(rèn)為我們構(gòu)建一個(gè)DiskBasedCache用來緩存Request請求以舒。下面我們重點(diǎn)分析CacheDispatcher。
對于CacheQueue中的Request,如果Request已經(jīng)取消,則直接調(diào)用Request.finish()進(jìn)行處理,如果未命中緩存,則添加到NetworkQueue中,交由NetworkDispatcher進(jìn)行處理,如果entry過期,則重新設(shè)置entry并交由NetworkDispatcher進(jìn)行處理,如果緩存命中,則直接返回緩存內(nèi)容,由ResponseDelivery進(jìn)行轉(zhuǎn)發(fā),最后將結(jié)果回調(diào)回調(diào)用者慢哈。但是此處緩存命中也分了兩種情況,一種需要刷新緩存,一種不需要,如果需要刷新緩存,則需要將緩存結(jié)果先返回,然后在調(diào)用NetworkDispatcher進(jìn)行緩存刷新蔓钟。關(guān)鍵代碼如下所示:
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
此處使用的mDelivery是ResponseDelivery接口的實(shí)現(xiàn)類ExecutorDelivery的對象,其主要作用是用來轉(zhuǎn)發(fā)請求結(jié)果與錯(cuò)誤信息卵贱。其在RequestQueue中傳入如下所示:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
我們可以看到它會傳入一個(gè)主線程的Handler滥沫,傳入的Handler其實(shí)最終是通過ExecutorDelivery中的Executor來將所有消息以postRunnable()的方式返回到主線程中。但是這里還是會有一個(gè)疑問键俱,使用postResponse時(shí)兰绣,其傳入的Runnable依然會在主線程中運(yùn)行。為了保證主線程的優(yōu)先工作编振,為什么不把這個(gè)提到子線程去做呢缀辩?具體關(guān)鍵代碼如下:
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);
}
};
}
//ResponseDeliveryRunnable run方法中的傳入Runnable處理
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
4. NetworkDispatcher的緩存策略:
NetworkDispatcher中只是簡單的執(zhí)行Request請求,將請求結(jié)果解析后踪央,將Response置入緩存中臀玄,此時(shí)的過期時(shí)間也是在此時(shí)進(jìn)行設(shè)置的。具體關(guān)鍵代碼如下:
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);
最后補(bǔ)上一張自己喪心病狂畫的流程圖:
至此杯瞻,對于Volley的分析也就暫時(shí)告一段落了镐牺,如果分析中有什么錯(cuò)漏還請各位積極留言指導(dǎo),謝謝(∩_∩)哈哈~