1.http緩存機制
要弄明白volley緩存機制,那么肯定是和瀏覽器的緩存機制有關了,簡單來說volley整套框架要做的事都是模擬瀏覽器來進行一次次的http交互
1.1.概述
http緩存的是指當Web請求抵達緩存時, 如果本地有“已緩存的”副本,就可以從本地存儲設備而不是從原始服務器中提取這個文檔。
1.2.與緩存有關的頭信息
1.2.1.request:
- Cache-Control: max-age=0 以秒為單位
- If-Modified-Since: Mon, 19 Nov 2012 08:38:01 GMT 緩存文件的最后修改時間。
- If-None-Match: "0693f67a67cc1:0" 緩存文件的Etag值
- Cache-Control: no-cache 不使用緩存
- Pragma: no-cache 不使用緩存
1.2.2.response:
- Cache-Control: public 響應被緩存,並且在多用戶間共享
- Cache-Control: private 響應只能作為私有緩存,不能在用戶之間共享
- Cache-Control:no-cache 提醒瀏覽器要從服務器提取文檔進行驗證
- Cache-Control:no-store 絕對禁止緩存(用於機密,敏感文件)
- Cache-Control: max-age=60 60秒之后緩存過期(相對時間)
- Date: Mon, 19 Nov 2012 08:39:00 GMT 當前response發送的時間
- Expires: Mon, 19 Nov 2012 08:40:01 GMT 緩存過期的時間(絕對時間)
- Last-Modified: Mon, 19 Nov 2012 08:38:01 GMT 服務器端文件的最后修改時間
- ETag: "20b1add7ec1cd1:0" 服務器端文件的Etag值
如果同時存在cache-control和Expires怎么辦呢?
優先使用cache-control,如果沒有cache-control才考慮Expires
2.Volley緩存機制
1.當你add進來一個request的時候,其會根據request.shouldCache(默認為true)進行分發決定是交給NetworkDispatcher還是CacheDispatcher處理
2.NetworkDispatcher通過Network請求數據, 如果有緩存的頭信息,會一起發送給服務器
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
.....
.....
.....
addCacheHeaders(headers, request.getCacheEntry());
.....
.....
.....
}
}
3.解析NetworkResponse
Response<?> response = request.parseNetworkResponse(networkResponse);
在這,得在自定義的request中調用很重要的一個靜態方法
HttpHeaderParser.parseCacheHeaders(response)
這個方法主要是將NetworkResponse進行封裝成一個Cache.Entry對象
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
//當前系統時間
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long serverExpires = 0;
long softExpire = 0;
long maxAge = 0;
boolean hasCacheControl = false;
String serverEtag = null;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
//服務器時間
serverDate = parseDateAsEpoch(headerValue);
}
//獲取Cache-Control信息
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")) {
//不緩存
return null;
} else if (token.startsWith("max-age=")) {
try {
//緩存過期時間(相對時間)
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
maxAge = 0;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
//過期時間(絕對時間)
serverExpires = parseDateAsEpoch(headerValue);
}
//ETag
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;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = entry.softTtl;
entry.serverDate = serverDate;
entry.responseHeaders = headers;
return entry;
}
4.在NetworkDispatcher中,再根據其shouldCache和是否有緩存實體來判斷是否要進行緩存操作
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written"); }
5.CacheDispatcher的處理
當收到一個request之后,會先到Cache里面去取,看下是否有緩存,
當出現沒有緩存 和 緩存過期的情況就直接丟給NetworkDispatcher來處理;
如果緩存沒過期,直接拿到緩存實體丟給request的parseNetworkResponse方法這里調用就和NetworkDispatcher里面處理差不多了;
@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;
}
continue;
}
}
}