本片文章主要分析的是OkHttp獲取響應(yīng)的過程赃泡,以及攔截器鏈税灌。
getResponseWithInterceptorChain方法
在上篇分析同步和異步請(qǐng)求流程的時(shí)候都出現(xiàn)了getResponseWithInterceptorChain方法,現(xiàn)在從這里開始分析螟凭。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加應(yīng)用攔截器
interceptors.addAll(client.interceptors());
//添加重試和重定向攔截器
interceptors.add(new RetryAndFollowUpInterceptor(client));
//添加轉(zhuǎn)換攔截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//添加緩存攔截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//添加連接攔截器
interceptors.add(new ConnectInterceptor(client));
//添加網(wǎng)絡(luò)攔截器
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
//生成攔截器鏈
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
這段代碼主要是把
- 應(yīng)用攔截器(外部配置)client.interceptors()
- 重試跟進(jìn)攔截器RetryAndFollowUpInterceptor
- 橋攔截器BridgetInterceptor
- 緩存攔截器CacheInterceptor
- 連接攔截器ConnectInterceptor
- 網(wǎng)絡(luò)攔截器(外部配置)client.neworkInterceptors()
- 請(qǐng)求服務(wù)攔截器CallServerInterceptor
將這些攔截器一次添加到集合interceptors中虚青,然后使用interceptors、transmitter螺男、originalRequest等創(chuàng)建了攔截器鏈RealInterceptorChain實(shí)例棒厘,最后用proceed方法獲取到請(qǐng)求的結(jié)果Response。
RealInterceptorChain
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.exchange != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
在實(shí)例化RealInterceptorChain時(shí) index賦值是0下隧,exchange是null奢人,所以前面三個(gè)if都沒走進(jìn)去。然后獲取了第一個(gè)攔截器淆院,也就是我們配置的應(yīng)用攔截器邻邮,調(diào)用了它的interceptor方法嗜桌,并返回和校驗(yàn)了結(jié)果骡送。這里證實(shí)了我們猜想蛛株。同時(shí)注意到,調(diào)用 應(yīng)用攔截器的interceptor方法傳入的參數(shù):攔截器鏈實(shí)例next,next就是把index + 1而已,其他參數(shù)和當(dāng)前實(shí)例是一樣的。也就是說 在我們的應(yīng)用攔截器中調(diào)用的是 next的proceed方法指孤。
進(jìn)一步,next的proceed方法中 同樣會(huì)獲取interceptors的index=1的攔截器贬堵,即RetryAndFollowUpInterceptor實(shí)例恃轩,然后調(diào)用其interceptor方法,參數(shù)是index+1即index=2的chain黎做。跟進(jìn)RetryAndFollowUpInterceptor的代碼發(fā)現(xiàn)详恼,interceptor方法內(nèi)部也是有調(diào)用chain的proceed方法。這樣就會(huì)依次傳遞下去引几,直到最后一個(gè)攔截器CallServerInterceptor昧互。
實(shí)際上 除了最后一個(gè)攔截器CallServerInterceptor之外,所有攔截器的interceptor方法都調(diào)用了 傳入 chain的proceed方法伟桅。每個(gè)攔截器在chain的proceed方法 前后 處理了自己負(fù)責(zé)的工作敞掘。例如我們的應(yīng)用攔截器,在chain的proceed方法前 打印了request信息的日志楣铁,chain的proceed方法獲取結(jié)果 之后 打印了response信息的日志玖雁。每個(gè)攔截器interceptor方法在 調(diào)用chain的proceed方法時(shí) 都是為了獲取下一個(gè)攔截器處理的response,然后返回給上一個(gè)攔截器盖腕。
下面我們依次分析這些攔截器赫冬。
RetryAndFollowUpInterceptor-重試、重定向
如果請(qǐng)求創(chuàng)建時(shí)沒有添加應(yīng)用攔截器 溃列,那第一個(gè)攔截器就是RetryAndFollowInterceptor,意為重試和跟進(jìn)攔截器劲厌。作用是連接失敗后進(jìn)行重試,對(duì)請(qǐng)求結(jié)果跟進(jìn)后進(jìn)行重定向听隐。下面看下它的interceptor方法:
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//準(zhǔn)備連接
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//繼續(xù)執(zhí)行下一個(gè)Interceptor
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// 連接路由異常补鼻,此時(shí)請(qǐng)求還未發(fā)送,嘗試恢復(fù)
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// IO異常 請(qǐng)求可能已發(fā)出雅任,嘗試恢復(fù)
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// 請(qǐng)求沒成功风范,釋放資源
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 關(guān)聯(lián)上一個(gè)response
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
//跟進(jìn)結(jié)果,主要作用是根據(jù)響應(yīng)碼處理請(qǐng)求沪么,返回request不為空時(shí)則進(jìn)行重定向處理硼婿,拿到重定向的request
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
//最多重試20次
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
使用while循環(huán)
prepareToConnect
public void prepareToConnect(Request request) {
if (this.request != null) {
if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
return; // 有相同連接
}
...
}
this.request = request;
//創(chuàng)建ExchangeFinder,是為獲取連接做準(zhǔn)備
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
ExchangeFinder是交換查找器禽车,作用是獲取請(qǐng)求的連接寇漫。
接著調(diào)用realChain.proceed繼續(xù)傳遞請(qǐng)求給下一個(gè)攔截器拳喻,從下個(gè)攔截器獲取原始結(jié)果。如果此過程發(fā)生了連接路由異持硗螅或IO異常,就會(huì)調(diào)用recover判斷是否進(jìn)行重試恢復(fù)钦勘。
recover
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// 應(yīng)用層禁止重試陋葡,就不重試
if (!client.retryOnConnectionFailure()) return false;
// 不能再次發(fā)送請(qǐng)求,就不重試
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 發(fā)生的異常是致命的彻采,就不重試
if (!isRecoverable(e, requestSendStarted)) return false;
// 沒有路由可以嘗試腐缤,就不重試
if (!transmitter.canRetry()) return false;
//返回true,就會(huì)進(jìn)入下一次循環(huán)肛响,重新請(qǐng)求
return true;
}
如果realChain.proceed沒有發(fā)生異常岭粤,返回了結(jié)果response,就會(huì)使用followUpRequest方法跟進(jìn)結(jié)果并重定向request。如果不用跟進(jìn)處理(例如響應(yīng)碼是200)特笋,則返回null剃浇。
BridgeInterceptor-橋接攔截器
橋攔截器相當(dāng)于在請(qǐng)求發(fā)起端和網(wǎng)絡(luò)執(zhí)行端架起一座橋,其作用:
- 把應(yīng)用層發(fā)出的請(qǐng)求變?yōu)榫W(wǎng)絡(luò)層認(rèn)識(shí)的請(qǐng)求猎物;
- 把網(wǎng)絡(luò)層執(zhí)行后的響應(yīng)變?yōu)閼?yīng)用層便于應(yīng)用層使用的結(jié)果虎囚。
public final class BridgeInterceptor implements
Interceptor {
//cookie管理器,初始化OkHttpClient時(shí)創(chuàng)建的蔫磨,默認(rèn)是C
private final CookieJar cookieJar;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
...//這部分代碼比較長(zhǎng)我們?cè)谙旅娣植秸归_
}
添加頭部信息
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
這段代碼主要為request添加Content-Type(文檔類型)淘讥、Content-Length(內(nèi)容長(zhǎng)度)或Transfer-Encoding。這些信息不需要我們手動(dòng)添加堤如。
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//默認(rèn)支持gzip壓縮
//"Accept-Encoding: gzip",表示接受:返回gzip編碼壓縮的數(shù)據(jù)
// 如果我們手動(dòng)添加了 "Accept-Encoding: gzip" 蒲列,那么下面的if不會(huì)進(jìn)入,transparentGzip是false搀罢,就需要我們自己處理數(shù)據(jù)解壓蝗岖。
//如果 沒有 手動(dòng)添加"Accept-Encoding: gzip" ,transparentGzip是true榔至,同時(shí)會(huì)自動(dòng)添加剪侮,而且后面也會(huì)自動(dòng)處理解壓。
boolean transparentGzip = false;
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
這段代碼主要為Host洛退、Connection和User-Agent字段添加默認(rèn)值瓣俯。這些屬性只有用戶沒有設(shè)置時(shí),才會(huì)自動(dòng)添加兵怯。
cookie部分
//從cookieJar中獲取cookie,添加到Header
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
處理請(qǐng)求
Response networkResponse = chain.proceed(requestBuilder.build());
//從networkResponse中獲取header Set-Cookie存入cookieJar
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//如果我們手動(dòng)添加“Accept-Encoding:gzip”,這里會(huì)創(chuàng)建 能自動(dòng)解壓的responseBody---GzipSource
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
梳理下整個(gè)流程:
- chain.proceed()執(zhí)行前彩匕,對(duì)請(qǐng)求添加Header:Content-Type、Content-Length或Transfer-Encoding媒区、Host驼仪、Connection掸犬、Accept-Encoding、Cookie绪爸、User-Agent,即網(wǎng)絡(luò)層真正可執(zhí)行的請(qǐng)求湾碎。默認(rèn)是沒有cookie處理的,需要我們?cè)诔跏蓟疧kHttpClient時(shí)配置我們自己的cookieJar
- chain.proceed()執(zhí)行后奠货,先把響應(yīng)header中的cookie存入cookieJar介褥,如果沒有手動(dòng)添加請(qǐng)求heade:"Accept-Encoding:gzip",會(huì)通過創(chuàng)建能自動(dòng)解壓的responseBody——GzipSource递惋,接著構(gòu)建新的response返回柔滔。
CacheInterceptor-緩存攔截器
緩存攔截器,提供網(wǎng)絡(luò)請(qǐng)求緩存的存取萍虽。
發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求睛廊,如果每次都經(jīng)過網(wǎng)絡(luò)的發(fā)送和讀取,效率肯定是很低的杉编。若之前有相同的請(qǐng)求已經(jīng)執(zhí)行過一次超全,是否可以將其結(jié)果保存起來,這次請(qǐng)求直接使用邓馒。這就用到了CacheInterceptor卵迂,合理使用本地緩存,有效的減少網(wǎng)絡(luò)開銷绒净,減少響應(yīng)延遲见咒。
final @Nullable InternalCache cache;
public CacheInterceptor(@Nullable InternalCache cache) {
this.cache = cache;
}
@Override
public Response intercept(Chain chain) throws IOException {
// 先從緩存中獲取響應(yīng),沒有則返回null挂疆。
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//獲取CacheStrategy緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//根據(jù)緩存策略更新統(tǒng)計(jì)指標(biāo):請(qǐng)求次數(shù)改览、網(wǎng)絡(luò)請(qǐng)求次數(shù)、使用緩存次數(shù)
if (cache != null) {
cache.trackResponse(strategy);
}
//有緩存 但不能用 關(guān)掉
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 網(wǎng)絡(luò)請(qǐng)求 緩存都不能用缤言,返回504
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();
}
// 不需要網(wǎng)絡(luò)請(qǐng)求宝当,可以使用緩存,就不會(huì)再走后面的流程
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 進(jìn)行網(wǎng)絡(luò)請(qǐng)求
Response networkResponse = null;
try {
//調(diào)用下一個(gè)攔截器
networkResponse = chain.proceed(networkRequest);
} finally {
// 發(fā)生IO錯(cuò)誤
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//網(wǎng)路請(qǐng)求返回304胆萧,表示服務(wù)端資源沒有修改庆揩,就結(jié)合網(wǎng)絡(luò)響應(yīng)和網(wǎng)絡(luò)緩存,更新緩存跌穗,返回結(jié)果订晌,結(jié)束。
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();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
//如果是非304蚌吸,說明服務(wù)端資源有更新锈拨,就關(guān)閉緩存body
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//如果有響應(yīng)體并且可緩存,那么將響應(yīng)寫入緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 寫入緩存
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
// OkHttp默認(rèn)只會(huì)對(duì)get請(qǐng)求進(jìn)行緩存 因?yàn)間et請(qǐng)求的數(shù)據(jù)一般是比較持久的 而post一般是交互操作
//不是get請(qǐng)求就移除緩存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
現(xiàn)在來整體梳理下思路羹唠。
CacheStrategy緩存策略用來決定是否使用緩存及如何使用奕枢。根據(jù)緩存策略中的networkRequest和cacheResponse來進(jìn)行一系列是使用緩存還是新的網(wǎng)絡(luò)數(shù)據(jù)的判斷:
- 若networkRequest娄昆、cacheResponse都為null,即網(wǎng)絡(luò)請(qǐng)求、緩存都不能用缝彬,返回504萌焰;
- 若networkRequest為null,cacheResponse肯定不為null,就是不使用網(wǎng)絡(luò)谷浅,使用緩存扒俯,就結(jié)束返回緩存數(shù)據(jù);
- 若networkResponse不為null壳贪,不管cacheResponse是否為null,都會(huì)去請(qǐng)求網(wǎng)絡(luò),獲取網(wǎng)絡(luò)響應(yīng)networkResponse;
- 若cacheResponse不為null寝杖,且networkResponse.code是304违施,表示服務(wù)端資源未修改,緩存還是有效的瑟幕。結(jié)合網(wǎng)絡(luò)響應(yīng)和緩存響應(yīng)磕蒲,然后更新緩存;
- 若cacheResponse==null或cacheResponse不為null,但networkResponse.code不是304只盹,就寫入緩存辣往,返回響應(yīng)。
CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
這里將請(qǐng)求request殖卑、候選緩存cacheCandidate傳入工廠類Factory站削,然后調(diào)用get方法。
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
//獲取候選緩存的請(qǐng)求時(shí)間孵稽、響應(yīng)時(shí)間许起,從header中獲取過期時(shí)間、修改時(shí)間菩鲜、資源標(biāo)記等园细。
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);
}
}
}
}
//get方法內(nèi)部先調(diào)用了getCandidate()獲取到緩存策略實(shí)例
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}
return candidate;
}
getCandidate()
這個(gè)方法里涉及到很多http緩存字段方面的東西
private CacheStrategy getCandidate() {
// 沒有緩存:網(wǎng)絡(luò)請(qǐng)求
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// https,但沒有握手:網(wǎng)絡(luò)請(qǐng)求
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//網(wǎng)絡(luò)響應(yīng) 不可緩存(請(qǐng)求或響應(yīng)的 頭 Cache-Control 是'no-store'):網(wǎng)絡(luò)請(qǐng)求
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//請(qǐng)求頭的Cache-Control是no-cache 或者 請(qǐng)求頭有"If-Modified-Since"或"If-None-Match":網(wǎng)絡(luò)請(qǐng)求
//意思就是 不使用緩存 或者 請(qǐng)求 手動(dòng) 添加了頭部 "If-Modified-Since"或"If-None-Match"
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
//緩存的年齡
long ageMillis = cacheResponseAge();
//緩存的有效期
long freshMillis = computeFreshnessLifetime();
//比較請(qǐng)求頭里有效期接校,取較小值
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//可接受的最小 剩余有效時(shí)間(min-fresh標(biāo)示了客戶端不愿意接受 剩余有效期<=min-fresh 的緩存猛频。)
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//可接受的最大過期時(shí)間(max-stale指令標(biāo)示了客戶端愿意接收一個(gè)已經(jīng)過期了的緩存,例如 過期了 1小時(shí) 還可以用)
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
// 第一個(gè)判斷:是否要求必須去服務(wù)器驗(yàn)證資源狀態(tài)
// 第二個(gè)判斷:獲取max-stale值蛛勉,如果不等于-1鹿寻,說明緩存過期后還能使用指定的時(shí)長(zhǎng)
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//如果響應(yīng)頭沒有要求忽略本地緩存 且 整合后的緩存年齡 小于 整合后的過期時(shí)間,那么緩存就可以用
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
//沒有滿足“可接受的最小 剩余有效時(shí)間”诽凌,加個(gè)110警告
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
//isFreshnessLifetimeHeuristic表示沒有過期時(shí)間烈和,那么大于一天,就加個(gè)113警告
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
//到這里皿淋,說明緩存是過期的
// 然后 找緩存里的Etag招刹、lastModified恬试、servedDate
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 {
//都沒有,就執(zhí)行常規(guī)的網(wǎng)絡(luò)請(qǐng)求
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
//如果有疯暑,就添加到網(wǎng)絡(luò)請(qǐng)求的頭部训柴。
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
//conditionalRequest表示 條件網(wǎng)絡(luò)請(qǐng)求: 有緩存但過期了,去請(qǐng)求網(wǎng)絡(luò) 詢問服務(wù)端妇拯,還能不能用幻馁。能用側(cè)返回304,不能則正常執(zhí)行網(wǎng)路請(qǐng)求越锈。
return new CacheStrategy(conditionalRequest, cacheResponse);
}
下面總結(jié)下getCandidate()方法的流程:
- 沒有緩存仗嗦、是https請(qǐng)求但是沒有握手、網(wǎng)絡(luò)響應(yīng)不可緩存甘凭、忽略緩存或手動(dòng)配置緩存過期稀拐,都直接進(jìn)行網(wǎng)絡(luò)請(qǐng)求;
- 以上條件都不滿足丹弱,如果緩存沒過期那么就使用緩存;
- 如果緩存過期了德撬,但響應(yīng)頭有Etag、Last-Modified躲胳、Data蜓洪,就添加這些header進(jìn)行條件網(wǎng)絡(luò)請(qǐng)求;
- 如果緩存過期了,且響應(yīng)頭沒有設(shè)置Etag坯苹、Last-Modified隆檀、Data,就進(jìn)行網(wǎng)絡(luò)請(qǐng)求粹湃。
再繼續(xù)看get()方法:
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}
return candidate;
}
getCandidate()獲取的緩存策略對(duì)象后刚操,判斷:進(jìn)行了網(wǎng)絡(luò)請(qǐng)求且原請(qǐng)求配置是能使用緩存。這說明此時(shí)即使有緩存也是過期的緩存再芋,所以new一個(gè)實(shí)例菊霜,傳入null。
緩存的讀寫是通過InternalCache完成的济赎,InternalCache是在創(chuàng)建CacheInterceptor實(shí)例時(shí)鉴逞,用client.internalCache()作為參數(shù)傳入。而InternalCahce是OkHttp內(nèi)部使用司训,InternalCache的實(shí)例是類Cache的屬性构捡。Cache是我們初始化OkHttpClient時(shí)傳入的,所以如果沒有傳入Cache實(shí)例是沒有緩存功能的壳猜。
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
.build();
Cache是通過OkHttp內(nèi)部的DiskLruCache實(shí)現(xiàn)的勾徽。
ConnectInterceptor攔截器和CallServerInterceptor攔截器會(huì)下下一篇文章中分析。