CacheDispatcher 緩存分發 cacheQueue只是一個優先隊列,我們在start方法中,分析了CacheDispatcher的構成是需要cacheQueue,然後調用CacheDispatcher.start方法,我們看一下CacheDispatcher得到cacheQueue之後, ...
CacheDispatcher 緩存分發
cacheQueue只是一個優先隊列,我們在start方法中,分析了CacheDispatcher的構成是需要cacheQueue,然後調用CacheDispatcher.start方法,我們看一下CacheDispatcher得到cacheQueue之後,到底做了什麼。
CacheQueue是一個繼承於Thread的類,其start方法實質上是調用了run方法,我們看一下run方法所做的事情
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
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.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}
我們可以看出其Run方法是一個無限迴圈的方法,退出的方式只有產生中斷異常,也就是其thread對象調用了 interrupt()方法,這個方法是在requestQueue中的stop方法中調用了,上面我們已經分析了。
下麵我們主要run方法的執行過程,取出隊頭的request,然後判斷request是否被取消,如果沒有就判斷該request中取出entity,判斷entity的狀態,如果entity為空,則將該request放入NetWorkDispatcher中重新請求,如果entity過期了也將該request放入NetWorkDispatcher中重新請求。兩者都沒有,則從request中entity的內容,重新構造response,然後判斷該entity是否需要刷新,不需要就直接Delivery該response,如果需要刷新,則將該response依舊發給用戶,但是重新進行請求該刷新entity。
可以用下圖的邏輯去看上面的過程
這裡,涉及到了Http一個重要的點,緩存。我們看一下,entity中關於緩存是怎麼設置的.
class Entry {
/** The data returned from cache. */
public byte[] data;
/** 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;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
這裡的過期方法判斷與是否需要刷新都是通過TTL與softTTL和現在時間對比而得到的。
緩存
這裡說一下Volley的緩存機制,涉及到Http緩存,需要解析Http響應報文的頭部。
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;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
// 獲取響應體的Cache緩存策略.
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (String token : tokens) {
token = token.trim();
if (token.equals("no-cache") || token.equals("no-store")) {
// no-cache|no-store代表伺服器禁止客戶端緩存,每次需要重新發送HTTP請求
return null;
} else if (token.startsWith("max-age=")) {
// 獲取緩存的有效時間
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
maxAge = 0;
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
staleWhileRevalidate = 0;
}
} 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");
// 計算緩存的ttl和softTtl
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;
}
這裡設計到緩存,就要先得到Http的cache-control的headervalue,如果是no-cahce||no-store就不需要再處理緩存,雖然on-cache在瀏覽器那邊還是保存了請求的資源,但這裡去沒有處理。如果headervalue中有MaxAge,這個值是判斷緩存存在的有效時間。如果headervalue中有stale-while-revalidate,這個值是緩存過期的可用時間,即使緩存過期,在stale-while-revalidate時間內依舊可用。如果headervalue中有must-revalidate就意味著
從必須再驗證緩存的新鮮度,然後再用。
然後繼續解析header與緩存有關的內容,如Expires(這是一個不推薦的標簽),Last-Modified(最近被修改的時間),ETag(伺服器資源標識)。
然後如果有緩存控制就計算緩存的TTL與SoftTTL,SoftTTL就是softExpire,其值就是maxAge + 當前時間,而TTL是finalTTL其值是 先判斷是否過期就再驗證,如果是的話,其值就是softExpire,如果不是的話,其值就是softExpire加上staleWhileRevalidate(緩存過期有效時間)。
如果沒有緩存控制,softExpire = now + (serverExpires - serverDate);
所以,說回上面,CacheQueue中緩存的判斷,isExpire就是判斷finalTTL是否超過當前時間,而refreshNeeded則是判斷softExpire是否超過當前時間。