
繼續(xù)上一篇的okHttp攔截器分析(一),下一個要分析的攔截器是CacheInterceptor,聽名字就知道跟緩存有關(guān),在這之前,我們先來看看一張Http緩存的流程圖,網(wǎng)絡(luò)上找到的:

當(dāng)發(fā)送相同請求的時候,先判斷緩存是否過期,如果過期了,該請求會攜帶If-Modified-Since和If-None-Match,通過這兩個值,判斷本地資源是否發(fā)生變化,沒有變化直接獲取緩存并返回code 304。
注:
Last-Modified:服務(wù)器返回給客戶端的頭部信息,表示資源的最后修改時間(ETag 比較的是響應(yīng)內(nèi)容的特征值,而Last-Modified 比較的是響應(yīng)內(nèi)容的修改時間)。
很好,接下來我們來看看okHttp里面的緩存攔截器關(guān)鍵實現(xiàn):
/** Serves requests from the cache and writes responses to the cache. */
public final class CacheInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
//獲取到緩存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//這個類有點叼,request和緩存的response都是CacheStrategy類返回的,也許這個CacheStrategy就是管理者吧,決定到底使用緩存還是進行網(wǎng)絡(luò)請求
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
//緩存不能使用,關(guān)閉
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.
// 1- 如果無網(wǎng)絡(luò)訪問(請求體networkRequest為null,內(nèi)部的url也是null),又無緩存,返回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();
}
// If we don't need the network, we're done.
// 2 - 如果不需要網(wǎng)絡(luò)請求,直接返回緩存數(shù)據(jù)
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 3 - 進行網(wǎng)絡(luò)請求,得到 網(wǎng)絡(luò)返回數(shù)據(jù)
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());
}
}
// If we have a cache response too, then we're doing a conditional get.
//4 - HTTP_NOT_MODIFIED 標(biāo)識緩存有效,網(wǎng)絡(luò)請求返回數(shù)據(jù)和緩存數(shù)據(jù)合并,并更新緩存
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());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) { //判斷是否支持緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
//5- 判斷有無緩存,寫入緩存
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
}
上面關(guān)鍵代碼,我們來總結(jié)下執(zhí)行流程:
1 如果無網(wǎng)絡(luò)訪問,又無緩存,返回504錯誤,執(zhí)行2
2 如果不需要網(wǎng)絡(luò)請求,直接返回緩存數(shù)據(jù),否則 執(zhí)行3
3 進行網(wǎng)絡(luò)請求,得到網(wǎng)絡(luò)返回數(shù)據(jù),執(zhí)行4
4 HTTP_NOT_MODIFIED 標(biāo)識緩存有效,網(wǎng)絡(luò)請求返回數(shù)據(jù)和緩存數(shù)據(jù)合并,并更新緩存,否則 執(zhí)行5
5 判斷有無緩存,寫入緩存并返回response

很好,CacheInterceptor分析完了,喝杯茶壓壓驚。
接下來 我們來看看ConnectInterceptor,字如其名,是一個跟連接有關(guān)的攔截器,我們看看關(guān)鍵代碼:
/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
代碼看的去很少,我們只需要關(guān)注下面兩行:
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
不管是httpCodec,還是connection,都是由streamAllocation完成,我們來看看newStream()里面做了什么:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
......
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
findHealthyConnection ,是找到一個可用連接的意思,重點就在這,通過找到的可用連接newCodec(),返回了HttpCodec的實現(xiàn)類Http1Codec對象。我們?nèi)タ纯磃indHealthyConnection(),看看如何找到可用連接:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {//判斷連接是否可用
noNewStreams();//連接不可用,移除
continue;//不可用,就一直持續(xù)
}
return candidate;
}
}
哇,這個方法用了死循環(huán),如果找不到可用連接,就一直卡在這里,再來看看findConnection()關(guān)鍵方法,有點長,分析都在源碼里面的注釋上:
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
//異常情況,直接拋出
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) { //
//經(jīng)過releaseIfNoNewStreams,connection不為null,則連接是可用的
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
//無可用連接,去連接池connectionPool中獲取
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {//上面通過去連接池中找,如果result不為null,說明找到了可用連接
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
//如果在連接池中也沒找到可用連接, 就需要一個路由信息,這是一個阻塞操作
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
//提供address,再次從連接池中獲取連接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
//提供路路由信息,然后進行查找可用鏈接,還是沒有找到可用鏈接,就需要生成一個新的連接
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
//如果連接是從連接池中找到的,直接拿出來使用
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
//將新生成的連接放入連接池中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
//如果是一個http2連接,http2連接應(yīng)具有多路復(fù)用特性,
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
我們來回顧下上面的獲取可用連接的流程:
先檢測鏈接是否可用:
a 可用的話直接返回,結(jié)束流程。
b 不可用,先去 連接池中查找:
連接池找到:結(jié)束流程。
未找到:提供address,再次去連接池中查找。如果找到了直接結(jié)束流程,如果沒找到:生成一個新的連接,并且將這個新的鏈接加入到連接池,然后返回這個新鏈接。
ConnectInterceptor 分析結(jié)束。
接下來是CallServerInterceptor,這個攔截器用來完成最終的請求執(zhí)行,這里面涉及到OKio這個庫,假裝它就是一個httpUrlConnection就行了,由于是另外一個庫,這里就不分析了。