現(xiàn)在的app沒有幾個是不聯(lián)網(wǎng)的了跃赚,在流量費(fèi)用很高笆搓、速度一般的今天給用戶合理節(jié)省流量,以及提高響應(yīng)速度就顯得尤為重要了纬傲。所以一個優(yōu)秀的app都會在發(fā)展到一定程度后就會開始引入緩存满败,什么是緩存呢?
百度百科:
緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache)叹括,當(dāng)某一硬件要讀取數(shù)據(jù)時算墨,會首先從緩存中查找需要的數(shù)據(jù),如果找到了則直接執(zhí)行汁雷,找不到的話則從內(nèi)存中找米同。由于緩存的運(yùn)行速度比內(nèi)存快得多,故緩存的作用就是幫助硬件更快地運(yùn)行摔竿。
通俗一點(diǎn)就是:接杯水放在手邊,渴了直接喝少孝,沒有去飲水機(jī)取继低。
原理
Okhttp3的網(wǎng)絡(luò)緩存是基于http協(xié)議,如果不清楚稍走,請自行搜索袁翁。
對于緩存,可閱讀婿脸,緩存簡介粱胜。
使用DiskLruCache緩存策略
注意點(diǎn)
- 目前只支持GET方式,其他請求方式需要自己實(shí)現(xiàn)
- 需要服務(wù)器配合狐树,通過header相關(guān)的頭來控制緩存
- 創(chuàng)建okhttpclient時候需要配置Cache
流程
1焙压、如果配置緩存,則從緩存中取一次,不保證存在
2涯曲、緩存策略
3野哭、緩存監(jiān)測
4、禁止使用網(wǎng)絡(luò)(根據(jù)緩存策略)幻件,緩存又無效拨黔,直接返回
5、緩存有效绰沥,不使用網(wǎng)絡(luò)
6篱蝇、緩存無效,執(zhí)行下一個攔截器
7徽曲、本地有緩存零截,根具條件選擇使用哪個響應(yīng)
8、使用網(wǎng)絡(luò)響應(yīng)
9疟位、 緩存到本地
源碼
@Override
public Response intercept(Chain chain) throws IOException {
// 1瞻润、如果配置緩存,則從緩存中取一次甜刻,不保證存在
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 2绍撞、緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 3、緩存監(jiān)測
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 4得院、禁止使用網(wǎng)絡(luò)(根據(jù)緩存策略)傻铣,緩存又無效,直接返回
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 5祥绞、緩存有效非洲,不使用網(wǎng)絡(luò)
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 6、緩存無效蜕径,執(zhí)行下一個攔截器
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 7两踏、本地有緩存,根具條件選擇使用哪個響應(yīng)
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 8兜喻、使用網(wǎng)絡(luò)響應(yīng)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 9梦染、 緩存到本地
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
步驟分析
讀取緩存
// 入口
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
// 主要是Cache類
- 通過url生成key(MD5、HEX)
- 通過key從內(nèi)存中讀取包裝實(shí)體類Entry朴皆,內(nèi)存中使用LinkedHashMap<String, Entry>
- 通過實(shí)體得到一個Snapshot帕识,關(guān)聯(lián)起文件系統(tǒng)中的緩存文件(緩存文件有多個,請求頭文件遂铡、響應(yīng)提文件)肮疗,然后生成流(Source,Okio中的類扒接,時間上就是inputStream)
- 通過快照得到一個Response實(shí)例
- 匹配是否是符合要求的伪货,是返回響應(yīng)们衙,否關(guān)閉
// 位置 okhttp3/Cache
Response get(Request request) {
// 1、
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
// 2超歌、
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
// 3砍艾、
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
// 4、
Response response = entry.response(snapshot);
// 5巍举、
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
緩存策略的配置
如果上一步能夠得到緩存響應(yīng)脆荷,則配置策略,主要是解析緩存中與響應(yīng)有關(guān)的頭(Date\Expires\Last-Modified\ETag\Age)
// 2懊悯、緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
-
解析緩存中與緩存有關(guān)的頭
public Factory(long nowMillis, Request request, Response cacheResponse) { this.nowMillis = nowMillis; this.request = request; this.cacheResponse = cacheResponse; // 有緩存響應(yīng) if (cacheResponse != null) { this.sentRequestMillis = cacheResponse.sentRequestAtMillis(); this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis(); Headers headers = cacheResponse.headers(); for (int i = 0, size = headers.size(); i < size; i++) { String fieldName = headers.name(i); String value = headers.value(i); if ("Date".equalsIgnoreCase(fieldName)) { servedDate = HttpDate.parse(value); servedDateString = value; } else if ("Expires".equalsIgnoreCase(fieldName)) { expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { lastModified = HttpDate.parse(value); lastModifiedString = value; } else if ("ETag".equalsIgnoreCase(fieldName)) { etag = value; } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HttpHeaders.parseSeconds(value, -1); } } } }
-
根據(jù)一些條件實(shí)例一個CacheStrategy(get())
private CacheStrategy getCandidate() { // No cached response. // 1蜓谋、沒有緩存響應(yīng),返回一個沒有響應(yīng)的策略 if (cacheResponse == null) { return new CacheStrategy(request, null); } // 2炭分、如果是https桃焕,丟失了握手緩存則,返回一個沒有響應(yīng)的策略 // Drop the cached response if it's missing a required handshake. if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } // 3捧毛、不能被緩存 // If this response shouldn't have been stored, it should never be used // as a response source. This check should be redundant as long as the // persistence store is well-behaved and the rules are constant. if (!isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } // 4观堂、緩存控制 CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } // 5、根據(jù)響應(yīng)頭 long ageMillis = cacheResponseAge(); long freshMillis = computeFreshnessLifetime(); if (requestCaching.maxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } long minFreshMillis = 0; if (requestCaching.minFreshSeconds() != -1) { minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } long maxStaleMillis = 0; CacheControl responseCaching = cacheResponse.cacheControl(); if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder(); if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); } long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); } return new CacheStrategy(null, builder.build()); } // Find a condition to add to the request. If the condition is satisfied, the response body // will not be transmitted. String conditionName; String conditionValue; if (etag != null) { conditionName = "If-None-Match"; conditionValue = etag; } else if (lastModified != null) { conditionName = "If-Modified-Since"; conditionValue = lastModifiedString; } else if (servedDate != null) { conditionName = "If-Modified-Since"; conditionValue = servedDateString; } else { return new CacheStrategy(request, null); // No condition! Make a regular request. } Headers.Builder conditionalRequestHeaders = request.headers().newBuilder(); Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue); Request conditionalRequest = request.newBuilder() .headers(conditionalRequestHeaders.build()) .build(); return new CacheStrategy(conditionalRequest, cacheResponse); }
只有一種情況是會有正常的緩存被使用:所有的緩存頭符合要求呀忧,即第5條师痕。
緩存監(jiān)測
// 3、緩存監(jiān)測
if (cache != null) {
cache.trackResponse(strategy);
}
此處記錄緩存使用情況
synchronized void trackResponse(CacheStrategy cacheStrategy) {
requestCount++;
if (cacheStrategy.networkRequest != null) {
// If this is a conditional request, we'll increment hitCount if/when it hits.
networkCount++;
} else if (cacheStrategy.cacheResponse != null) {
// This response uses the cache and not the network. That's a cache hit.
hitCount++;
}
}
禁止使用網(wǎng)絡(luò)(根據(jù)緩存策略)而账,緩存又無效胰坟,直接返回
根據(jù)上面緩存策略的配置,這種情況不會發(fā)生泞辐,不清楚為什么有這個邏輯
緩存有效笔横,不使用網(wǎng)絡(luò)
通過緩存策略,如果符合要求將會把Request置空咐吼,Response不為空吹缔,所以直接使用緩存
// 5、緩存有效锯茄,不使用網(wǎng)絡(luò)
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
緩存無效涛菠,執(zhí)行下一個攔截器
如果緩存無效,將會執(zhí)行下一個攔截器撇吞,等待響應(yīng)結(jié)果
本地有緩存,根具條件選擇使用哪個響應(yīng)
// 本地有緩存礁叔,響應(yīng)結(jié)果沒有修改牍颈,合并兩個響應(yīng)
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
// 更新緩存
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
使用網(wǎng)絡(luò)響應(yīng)
以上都不符合,只能使用網(wǎng)絡(luò)響應(yīng)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
緩存到本地
// 9琅关、 緩存到本地
// 1.
if (HttpHeaders.hasBody(response)) {
// 2.
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
-
根據(jù)頭判斷是否支持緩存
- 必須有響應(yīng)體
- 內(nèi)容有變化
-
是否符合緩存要求煮岁,根據(jù)策略
private CacheRequest maybeCache(Response userResponse, Request networkRequest, InternalCache responseCache) throws IOException { // 1讥蔽、沒有響應(yīng)體 不緩存 if (responseCache == null) return null; // 2、是否支持 // Should we cache this response for this request? // 2.1画机、根據(jù)頭 if (!CacheStrategy.isCacheable(userResponse, networkRequest)) { // 2.2冶伞、根據(jù)請求方式,有請求體的方式都不支持 if (HttpMethod.invalidatesCache(networkRequest.method())) { try { responseCache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } return null; } // 寫入緩存 // Offer this request to the cache. return responseCache.put(userResponse); }
根據(jù)請求方式步氏,有請求體的方式都不支持緩存
-
通過配置好的cache寫入緩存
寫入緩存和讀取緩存使用的方式類似响禽,都是通過Cache,DiskLruCache
CacheRequest put(Response response) { String requestMethod = response.request().method(); if (HttpMethod.invalidatesCache(response.request().method())) { try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } if (!requestMethod.equals("GET")) { // Don't cache non-GET responses. We're technically allowed to cache // HEAD requests and some POST requests, but the complexity of doing // so is high and the benefit is low. return null; } if (HttpHeaders.hasVaryAll(response)) { return null; } Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { editor = cache.edit(key(response.request().url())); if (editor == null) { return null; } // 提交緩存 entry.writeTo(editor); return new CacheRequestImpl(editor); } catch (IOException e) { abortQuietly(editor); return null; } }
總結(jié)
緩存實(shí)際上是一個比較復(fù)雜的邏輯荚醒,單獨(dú)的功能塊芋类,實(shí)際上不屬于okhttp上的功能,只是通過http協(xié)議和DiskLruCache做了處理而已界阁。