在上一篇文章OkHttp 源码学习(一) 请求与调度 中将 okhttp 请求封装与调用了解了一下,那么这篇文章继续分析责任链中各个环节都做了哪些事情,
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
// 重试 重定向 拦截器
interceptors.add(retryAndFollowUpInterceptor);
// 桥接拦截器 主要用于 header 与 body 的封装
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
// 链接拦截器 okhttp 的精髓
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
// 交互拦截器
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
在这个责任链中主要分为5个重要的模块,这5个模块各司其职,下面一个一个来分析
1.RetryAndFollowUpInterceptor 重试 重定向拦截器
这个拦截器的工作主要分为2个部分,其中一个非常重要的工作就是重试,具体逻辑如下,这里只将重试的代码保留
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), true, request))
throw e.getLastConnectException();
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, false, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
}
从上面的代码中可以看到 如果发生了 RouteException 与 IOException , okhttp 会不限次数的帮我们进行重试,下面来看一下具体的逻辑
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered.
*/
private boolean recover(IOException e, boolean routeException, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (!routeException && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal.
if (!isRecoverable(e, routeException)) return false;
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
看到这里主要分为4步拦截
if (! client.retryOnConnectionFailure()) return false;
这里的 client.retryOnConnectionFailure() 我们可以在okhttpclient创建时修改 方式如下
var client=OkHttpClient.Builder()
.connectTimeout(5*1000,TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.addInterceptor {
chain -> chain?.proceed(chain.request())
}
.addNetworkInterceptor {
it.proceed(it.request())
}
.build()
这个值默认是true
if (!routeException && userRequest.body() instanceof UnrepeatableRequestBody) return false;
本次异常不是由路由引起的,并且 请求体 是 UnrepeatableRequestBody(不可重复的) 的,那就本次请求失败,不可以重试
if (!isRecoverable(e, routeException)) return false;
第三步又细分了很多种情况
private boolean isRecoverable(IOException e, boolean routeException) {
// 协议异常,通常是服务器返回的body 与 header 的信息不匹配造成,
if (e instanceof ProtocolException) {
return false;
}
//打断异常,如果本次请求被打断,则不能重试
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && routeException;
}
// ssl 协议异常,并且是证书异常
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// ssl 认证异常
if (e instanceof SSLPeerUnverifiedException) {
return false;
}
return true;
}
if (!streamAllocation.hasMoreRoutes()) return false;
从连接池取不到链接的时候,不能重试 ,
其实在我这里看得okhttp的代码是 4.0以下的版本,在4.0以上的版本还判断了如果当前请求是向服务器写一个文件,但是出现了 FileNodeFound 的异常也会让请求不能重试,这个也理解起来也比较简单,上传的文件没找到,本次请求没有意义了
到了这里重试就讲完了,下面继续来说重定向,还是上面的代码,只不过这次只保留重定向的代码
while (true) {
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()));
} else if (streamAllocation.stream() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
这里的逻辑大致如下 1:这里的代码逻辑就是通过 followUpRequest 方法判断是否可以重新构建一个Request ,如果不能构建,则代表不需要重定向 ,本次请求完成,
2:如果重定向次数超过最大次数,则抛出异常 ,MAX_FOLLOW_UPS 为20,也就是超过20则抛出异常结束
3:拿到的body 只能单次使用,抛出异常
4:不是同一个请求 , 则重新创建请求,不是同一个请求的标准如下
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}
BridgeInterceptor 桥接拦截器
桥接拦截器主要是处理 header 的中的 conent-length cookie 等信息,
@Override public Response intercept(Chain chain) throws IOException {
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");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
这里需要注意的有2点
1:cookieJar 这个cookieJar 是BridgeInterceptor在创建之初由 okhttpclient 传入进来的,我们可以在创建okhttpclient 时自定义
var client=OkHttpClient.Builder()
.connectTimeout(5*1000,TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.addInterceptor {
chain -> chain?.proceed(chain.request())
}
.cookieJar()//TODO 这里设置
.addNetworkInterceptor {
it.proceed(it.request())
}
.build()
2: userRequest.header("Accept-Encoding") == null 时自动为我们添加 gzip 请求,同时使用 GzipSource 将返回结果包装,
CacheInterceptor 缓存拦截器
开启okhttp 的缓存也比较简单,只需要在 初始化client 时 执行Cache 即可,代码如下
var client=OkHttpClient.Builder()
.connectTimeout(5*1000,TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.addInterceptor {
chain -> chain?.proceed(chain.request())
}
.cookieJar(object :CookieJar{
override fun saveFromResponse(url: HttpUrl?, cookies: MutableList<Cookie>?) {
}
override fun loadForRequest(url: HttpUrl?): MutableList<Cookie> {
return mutableListOf()
}
})
// 开启缓存
.cache(Cache(cachePath,20*1024*1024))
.addNetworkInterceptor {
it.proceed(it.request())
}
.build()
在加入了缓存的情况下,其实我们要做的请求就2件事, 1:在请求前拿缓存,如果缓存可以使用,则使用缓存,
// 注释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;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 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(EMPTY_BODY)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
虽然从逻辑上来说这个过程比较简单,但是okhttp 的实际处理是非常的复杂的 我们先从注释代码1说起,拿缓存
Response get(Request request) {
String key = urlToKey(request);
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
在拿缓存的大致过程如下, 1:先去 DiskLruCache 文件缓存中取快照 2:如果快存在通过快照信息的resouse[0]创建 Entry ,其中 resouse[0] 是按照 okhttp 的规则顺序的写入了一些数据 , 3:使用 entry 解析快照信息获取缓存请求
下面继续分析注释2代码
//注释2:创建中间调度体,封装逻辑
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
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);
}
}
}
}
注意这里我们传入的信息分别是 1:时间 2:请求 3:缓存 在创建过程中将所有数据缓存,同时解析缓存请求,从他的header 中获取 Date Expires Last-Modified ETag Age 等信息,并进行缓存
CacheStrategy.Factory.get 方法如下
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
这里的主要逻辑还是需要看 getCandidate 这个方法,代码如下
private CacheStrategy getCandidate() {
//逻辑1
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 逻辑2
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 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.
//逻辑3
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
// 逻辑4
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
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());
}
// 逻辑5
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 {
// 逻辑6
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
//逻辑7
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
这里的逻辑如下 1: 如果没有缓存则使用 请求 request 构建 CacheStrategy 2: 如果是https请求,并且缓存中没有握手记录 则使用 请求 request 构建 CacheStrategy
3: isCacheable 判断
public static boolean isCacheable(Response response, Request request) {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1),
// This implementation doesn't support caching partial content.
switch (response.code()) {
case HTTP_OK:
case HTTP_NOT_AUTHORITATIVE:
case HTTP_NO_CONTENT:
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_NOT_FOUND:
case HTTP_BAD_METHOD:
case HTTP_GONE:
case HTTP_REQ_TOO_LONG:
case HTTP_NOT_IMPLEMENTED:
case StatusLine.HTTP_PERM_REDIRECT:
// These codes can be cached unless headers forbid it.
break;
case HTTP_MOVED_TEMP:
case StatusLine.HTTP_TEMP_REDIRECT:
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
if (response.header("Expires") != null
|| response.cacheControl().maxAgeSeconds() != -1
|| response.cacheControl().isPublic()
|| response.cacheControl().isPrivate()) {
break;
}
// Fall-through.
default:
// All other codes cannot be cached.
return false;
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
}
这里的代码逻辑就是只有 HTTP_OK HTTP_NOT_AUTHORITATIVE HTTP_NO_CONTENT 等请求会查看 !response.cacheControl().noStore() && !request.cacheControl().noStore() 这个判断,其他都是false 同时这个 noStore 的意思就是请求或者相应的请求头中如果存在这个 Cache-Control ,并且 value中存在 noStore,那么本次不使用缓存
public CacheControl cacheControl() {
CacheControl result = cacheControl;
return result != null ? result : (cacheControl = CacheControl.parse(headers));
}
public static CacheControl parse(Headers headers) {
boolean noCache = false;
boolean noStore = false;
int maxAgeSeconds = -1;
int sMaxAgeSeconds = -1;
boolean isPrivate = false;
boolean isPublic = false;
boolean mustRevalidate = false;
int maxStaleSeconds = -1;
int minFreshSeconds = -1;
boolean onlyIfCached = false;
boolean noTransform = false;
boolean canUseHeaderValue = true;
String headerValue = null;
for (int i = 0, size = headers.size(); i < size; i++) {
String name = headers.name(i);
String value = headers.value(i);
if (name.equalsIgnoreCase("Cache-Control")) {
if (headerValue != null) {
// Multiple cache-control headers means we can't use the raw value.
canUseHeaderValue = false;
} else {
headerValue = value;
}
} else if (name.equalsIgnoreCase("Pragma")) {
// Might specify additional cache-control params. We invalidate just in case.
canUseHeaderValue = false;
} else {
continue;
}
int pos = 0;
while (pos < value.length()) {
int tokenStart = pos;
pos = HttpHeaders.skipUntil(value, pos, "=,;");
String directive = value.substring(tokenStart, pos).trim();
String parameter;
if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
pos++; // consume ',' or ';' (if necessary)
parameter = null;
} else {
pos++; // consume '='
pos = HttpHeaders.skipWhitespace(value, pos);
// quoted string
if (pos < value.length() && value.charAt(pos) == '"') {
pos++; // consume '"' open quote
int parameterStart = pos;
pos = HttpHeaders.skipUntil(value, pos, """);
parameter = value.substring(parameterStart, pos);
pos++; // consume '"' close quote (if necessary)
// unquoted string
} else {
int parameterStart = pos;
pos = HttpHeaders.skipUntil(value, pos, ",;");
parameter = value.substring(parameterStart, pos).trim();
}
}
if ("no-cache".equalsIgnoreCase(directive)) {
noCache = true;
} else if ("no-store".equalsIgnoreCase(directive)) {
noStore = true;
} else if ("max-age".equalsIgnoreCase(directive)) {
maxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("s-maxage".equalsIgnoreCase(directive)) {
sMaxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("private".equalsIgnoreCase(directive)) {
isPrivate = true;
} else if ("public".equalsIgnoreCase(directive)) {
isPublic = true;
} else if ("must-revalidate".equalsIgnoreCase(directive)) {
mustRevalidate = true;
} else if ("max-stale".equalsIgnoreCase(directive)) {
maxStaleSeconds = HttpHeaders.parseSeconds(parameter, Integer.MAX_VALUE);
} else if ("min-fresh".equalsIgnoreCase(directive)) {
minFreshSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("only-if-cached".equalsIgnoreCase(directive)) {
onlyIfCached = true;
} else if ("no-transform".equalsIgnoreCase(directive)) {
noTransform = true;
}
}
}
上面是请求的 CacheControl 的获取过程,首次使用的是 parese Header 创建的,
4:服务器返回不能缓存,获取request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
5:通过缓存的请求获取缓存请求的请求时间与数据的有效期,如果在有效期内,并且可以保存 则使用缓存构建 CacheStrategy
6: 如果在缓存中 eTag 等值均为null,那么则使用 请求构建 CacheStrategy
7: 根据标记信息,重新组装Header ,使用新的request 与 cache 构建 CacheStrategy
看完了组装 CacheStrategy 的过程在看他的使用缓存的逻辑就非常的简单里了
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
//注释1
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//注释2
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(EMPTY_BODY)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 注释3
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
1:如果缓从缓存组装的 cacheCandidate 存在,但是根据缓存的结果不存在,将 cacheCandidate 流信息等关闭
2:如果请求和缓存都不存在返回空body
3: 如果请求不存在,使用缓存构建结果,并返回
下面再来说说做完网络请求后重新缓存的过程
// 注释1
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.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());
}
}
//注释2
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
1: 如果我们在请求后得到的 response 与前面缓存的 response 的 Last-Modified 有差异,则需要更新缓存,判断逻辑如下
private static boolean validate(Response cached, Response network) {
if (network.code() == HTTP_NOT_MODIFIED) return true;
// The HTTP spec says that if the network's response is older than our
// cached response, we may return the cache's response. Like Chrome (but
// unlike Firefox), this client prefers to return the newer response.
Date lastModified = cached.headers().getDate("Last-Modified");
if (lastModified != null) {
Date networkLastModified = network.headers().getDate("Last-Modified");
if (networkLastModified != null
&& networkLastModified.getTime() < lastModified.getTime()) {
return true;
}
}
return false;
}
2: 这个里面的逻辑就就是查看是否可以缓存,并缓缓
由于时间的关系这篇文章就到这里了,而且链接拦截器也是 okhttp 的精华所在,并不是简单几句话就能说明白的,而且在写这篇博客前,也没有想到能写这么长,不过为了不留死角,每一个关键的逻辑点我都相应的说明,面对现在的这个行情只能不停的充实自己才能让自己不至于被淘汰
加油吧!少年