第一篇讲的是同步、异步调用源码走向Android-OKHTTP底层原理浅析(一)
第二篇讲的是重定向拦截器、桥拦截器的工作内容Android-OKHttp底层原理浅析(二)
这篇来讲剩下的拦截器,CacheInterceptor , ConnectInterceptor , CallServerInterceptor
首先看CacheInterceptor——缓存拦截器
@Override public Response intercept(Chain chain) throws IOException {
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);
}
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(Util.EMPTY_RESPONSE)
.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();
}
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());
}
}
// 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());
}
}
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);
}
return response;
}
pia一下又是一堆代码,别慌,挑重点,跟哥走, 这一段代码我们需要明确几个关键对象
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
一个是cacheCandidate ,一个是networkRequest ,一个是cacheResponse
你仔细看看就会发现这整一篇,都是这三个参数的非空判断
if (cacheCandidate != null && cacheResponse == null) {}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {}
// If we don't need the network, we're done.
if (networkRequest == null) {}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {}
对吧,差不多有这几个判断,好几个已经官方都给注释上了,这里笼统的描述一下这个流程线,如果真的要完全搞懂可能得去研究一下http的缓存策略,
第一个如果候选缓存cacheCandidate 不适用,就调用closeQuietly()方法关闭了
第二个networkRequest跟cacheResponse 都为空的时候表示如果被禁止使用网络并且缓存不足,则返回失败, 可以看到在这里是返回了一个504出去
第三个那如果只有网络不适用的情况(因为上面已经判断过cacheResponse == null了,能走到这个流程说明缓存是有的),那没网络有缓存咋办?还能咋地,把缓存返回出去呗
接下来就走执行下一个拦截器的代码了
networkResponse = chain.proceed(networkRequest);
能走到这一步来说明networkRequest不等于空,就是说有网络的,才会去发起请求(啊,剧透了后面的拦截器了)
ok,走完回来
第四个判断,此时我们的请求已经回来了,但是我们也有一个缓存存在(cacheResponse != null)这时咋办?缓存对比
if (networkResponse.code() == HTTP_NOT_MODIFIED) {}
HTTP_NOT_MODIFIED是304,这里可以直接验证我们的猜想是对的
ok,走到这里如果所有的if都不满足,那么就只有一种情况——有网络(networkRequest!=null)无缓存(cacheResponse == null),这时又咋办?直接把请求的结果返回出去呗。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.newBuilder() 对吧,networkResponse是我们在后面的拦截器获取回来的数据。
拆开来一讲是不是其实也不算太复杂?不过这里涉及了Http的缓存策略,如果需要深入了解还是得学习http的相关知识,这里我比较在乎的是整个大体流程,所以就不深挖了,有兴趣的筒子可以自行查阅哈。
那接下来就是我们的ConnectInterceptor——连接拦截器了,这一关关的真累啊,设计这个流程的作者简直是天才。
@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, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
StreamAllocation 是不是很眼熟?
是的,这就是我们第一个拦截器当时定义的一个对象,最后是在这里使用他创建了HttpCodec跟RealConnection 对象,然后传到下个拦截器去
那既然如此我们来挖一下StreamAllocation.newStream()吧
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
这里一开始先配置了几个参数,超时时间,然后调用findHealthyConnection查找健康的连接,里面大概流程是返回一个连接,首先会从连接池中来,如果连接池中没有对应连接, 则再重新新建一个连接。
最后初始化HttpCodec并返回出去,看看resultConnection.newCodec()
public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
这里咱们看到了http1跟2的判断了,组装成一个HttpCodec类返回了出去。
ok,轮到挖findHealthyConnection()了,来吧
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
<省略代码>
return candidate;
}
}
挖findConnection
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
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.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
selectedRoute = routeSelector.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.
RealConnection result;
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
if (canceled) throw new IOException("Canceled");
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// 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.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
这里主要是分为了几个步骤
RealConnection allocatedConnection = this.connection;
尝试使用已分配的连接
Internal.instance.get(connectionPool, address, this, null);
尝试从连接池中获取连接
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
这里有一个路由对象,根据官方注释可以理解为“如果我们需要一条路线,请制作一条。 这是一个阻止操作。”,可见这里是一个阻塞操作获取一条新路线(如果目前的选择为空的话)
Internal.instance.get(connectionPool, address, this, selectedRoute);
后来又开启了一个同步,又执行了上面这句代码,注意这下第四个参数是传的selectedRoute,经过上面这么一顿操作这个值应该是不为空了,这里通过这个路由线路,去池里找连接了。
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
官方注释“做TCP + TLS握手。 这是一个阻止操作。”看来这里就是连接处了,
Internal.instance.put(connectionPool, result);
最后再汇集连接
这个方法内容有点多,也属于比较底的一部分了,简单总结一下这里的工作任务:
1,先尝试现在的连接是否可用
2,再尝试一下在连接池获取一个连接(此时路由线路为空)
3,获取路由线路
4,通过已获得的路由线路,去连接池获取连接
5,如果都找不到,那么就新建一个
6,对连接进行连接操作(握手)
7,把生成的连接对象,丢进池里
这里的步骤就有这么多,可以看出代码对复用的要求极其洁癖,各种查询,最后真不行了才去生成,生成完又扔了进去提供给后面的筒子们复用。那么这个拦截器的内容就到这过,主要是建立连接,以及数据的准备,最后交给下一个拦截器CallServerInterceptor
啊文章字又太多了,脸疼,下一篇。
Android-Okhttp底层原理浅析(四)
网友评论