接上篇OkHttp之getResponseWithInterceptorChain(一)继续
目录
CacheInterceptor
缓存拦截器。其构造函数传入一个InternalCache,在getResponseWithInterceptorChain()实例化时从OkHttpClient中传入。
构造方法
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
在OkHttpClient的Builder提供了一个设置缓存的方法,默认的为null
@Nullable Cache cache;
@Nullable InternalCache internalCache;
/** Sets the response cache to be used to read and write cached responses. */
void setInternalCache(@Nullable InternalCache internalCache) {
this.internalCache = internalCache;
this.cache = null;
}
/** Sets the response cache to be used to read and write cached responses. */
public Builder cache(@Nullable Cache cache) {
this.cache = cache;
this.internalCache = null;
return this;
}
从源码中看到可以通过Builder设置Cache,但不能设置internalCache。下面是获取这个缓存的代码
public @Nullable Cache cache() {
return cache;
}
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
Cache
我们看下这个Cache类,提供了对缓存内容的创建和增删改查方法。
public final class Cache implements Closeable, Flushable {
//内部含有一个internalCache
final InternalCache internalCache = new InternalCache() {
}
//使用DiskLruCache来实现缓存
final DiskLruCache cache;
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
//从中可以看到key就是对应的url地址进行了md5加密
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}
@Nullable Response get(Request request) {
String key = key(request.url());
//.....省略代码
}
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
//.....省略代码
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);
//.....省略代码
}
void remove(Request request) throws IOException {
cache.remove(key(request.url()));
}
void update(Response cached, Response network) {
}
}
Cache里面有一个 internalCache,同时采用了DiskLruCache进行缓存,缓存的内容就是response,通过url地址进行了md5加密作为key来进行增删改查。
同时注意下,在构建Cache的时候,必须要传入要保存的文件路径和最大缓存的大小。
CacheStrategy
就是给定一个request和cached response,根据里面设置的条件决定去使用网络请求还是缓存的response。从创建的Factory中可以看到返回的CacheStrategy就是根据不同的条件来将CacheStrategy中的networkRequest和cacheResponse进行设置值。
CacheInterceptor的具体逻辑
进入到CacheInterceptor中看下这个拦截器是怎么实现的缓存?
@Override public Response intercept(Chain chain) throws IOException {
//1)判断有没有设置的缓存里的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//2)将request和缓存的response传入到缓存策略中,获取缓存策略中的networkRequest和cacheResponse
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//.....省略代码
//2.1)既不需要请求服务器同时缓存无效,直接返回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();
}
//2.2)不需要网络服务器,缓存有效,则直接返回缓存的response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//2.3)缓存无效,直接请求服务器,进入到下一个拦截器返回对应的response
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
//.......省略代码
}
//2.4)如果缓存response有效,则对比服务器获取的response的情况进行选择使用哪个响应
if (cacheResponse != null) {
//如果缓存有效,并且返回的response中不需要请求网络,则直接将缓存response进行返回,状态码为304
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();
//.......省略代码
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//2.5)使用网络请求
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//3)根据情况进行缓存response
if (cache != null) {
//如果有缓存,并且该response允许缓存,则将该response放到缓存中
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
1)先从设置的缓存中获取response,由于在OkHttpClient中默认的是null,如果没有设置,这里返回的是null
2)将request和缓存的response传入到缓存策略中,获取缓存策略中的networkRequest和cacheResponse,然后根据返回的值来返回不同的response,具体有以下几种情况:
(2.1)networkRequest和cacheResponse都为null:即不需要请求服务器,同时缓存无效,则直接返回,状态码为504
(2.2)networkRequest为null:不需要请求服务器,直接返回缓存response
(2.3)networkRequest不为null:则直接调用下一个拦截器去请求服务器
(2.4)cacheResponse不为null:如果缓存有效,则与服务器返回的response进行比较,如果返回的response含有标示为 Not Modified,则直接返回缓存response
3)如存在缓存,则将从服务器返回的response缓存到本地。
总结
1)缓存策略是OkHttp的特色之一。
2)Cache类用来实现对缓存内容的增删改查,request中的对应的url进行MD5加密之后作为缓存的key,同时采用DiskLruCache进行缓存response
3)CacheStrategy缓存策略就是根据request和之前缓存的response里面的header里面的配置的内容来决定里面的networkRequest和cacheResponse是否为null,来决定是去请求服务器还是直接使用缓存response
4)CacheInterceptor拦截器中根据缓存策略中的networkRequest和cacheResponse的值来决定返回服务器返回的response还是直接使用缓存response。若需要请求服务器,则通过RealInterceptorChain的 realChain.proceed(),调用到下一个拦截器ConnectInterceptor
ConnectInterceptor
与服务器建立连接
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//获取request和StreamAllocation
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");
//获取StreamAllocation的流
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取的streamAllocation连接
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
从源码中看上去很简单就是获取requst和streamAllocation。然后通过streamAllocation的方法来建立连接
StreamAllocation
简单的来说就是为请求寻找可用的连接,并建立连接的过程。
1)实例化
在RetryAndFollowUpInterceptor中进行实例化,在完成RetryAndFollowUpInterceptor拦截器的失败重试或重定向,调用RealInterceptorChain的 realChain.proceed()时,将该实例对象传入到了RealInterceptorChain中
2)获取StreamAllocation的流
通过调用StreamAllocation的newStream()返回流,进入到该方法中
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//.....代码省略
try {
//1)首先找到一个可用的连接RealConnection
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//2)通过连接获得HttpCodec
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
//.....代码省略
}
- 首先通过findHealthyConnection()找到一个可用的连接。最终就是调用的findConnection(),其中里面的大体流程就是
(1)首先判断复用的connection是否有效
result = this.connection;
该connection是通过在RealConnection中get()一个可用链接的时候,如果该链接可用,则直接通过streamAlloction.acquire()给this.connection赋值。
2)如果上面this.connection无效,则从连接池中取出一个链接
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
//.......省略代码
}
该connectionPool是在实例化StreamAllocation时传入的。在RetryAndFollowUpInterceptor实例化的时候,从OkHttpClient中获取的
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
而OkHttpClient中的Builder时,初始化的
public Builder() {
//......代码省略
connectionPool = new ConnectionPool();
//......代码省略
}
所以OkHttpClient最好在全局只有一个实例,这样可以共用该链接池。
如果此时的connection的可用,则直接返回
3)如果上述的connection不可用,则更换路由,更换线路,进行查找,直到找到一个返回
4)如果还是没有找到,则创建一个新的链接,并将该链接通过acquire关联到connection.alloctions上
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
5)建立TCP链接并握手
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
6)将链接放到链接池中
synchronized (connectionPool) {
//......省略代码
Internal.instance.put(connectionPool, result);
//......省略代码
}
- 得到可用的链接之后,通过该链接获取到流
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
进入到源码中可以看到:
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);
}
}
里面的逻辑很简单,就是根据情况返回是Http1Codec还是Http2Codec。这个HttpCodec就是为发送请求的时候写请求头部,创建请求体,在接收的时候读取响应头,以便后续获取响应数据。
总结
ConnectInterceptor就是通过StreamAllocation来为请求与服务器建立链接。而真正的去请求服务器就在下一个拦截器中。并且还是通过调用realChain.proceed的方式进行调用下一个拦截器,并且将(request, streamAllocation, httpCodec, connection)这些信息传到下一个拦截器中
realChain.proceed(request, streamAllocation, httpCodec, connection);
CallServerInterceptor
真正的向服务器发送请求,并且得到服务器返回的数据,返回给上一个拦截器。看下源码
@Override public Response intercept(Chain chain) throws IOException {
//.......省略代码
//1.写入请求头
httpCodec.writeRequestHeaders(request);
//.......省略代码
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 如果发送到请求中含有100-continue(客户端在发送request之前,需要先判断服务器是否愿意接受客户端发来的消息主体),则只有服务器返回100-continue的应答之后,才会把数据发送给服务器
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
//开始请求服务器,检查返回的response中是否含有100-continue
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
//如果前面的100-continue需要握手,但又握手失败,这时候responseBuilder不为null
//2.写入请求体
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//3.写数据
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
}
//.......省略代码
//完成网络请求,实际上就是将信息写入到socket的输出流中
httpCodec.finishRequest();
//4.读取响应头
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
//....省略代码 读取响应里面的内容进行处理
//5.读取服务器返回的内容
else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
//....省略代码
return response;
}
通过上面5步,完成整个发送和接收服务器的数据。
Okio在CacheInterceptor应用
具体的一个解析过程,可以参见OkHttp的Okio在CacheInterceptor中的应用
总结
CacheInterceptor主要就是真正的将请求发送给服务器,并接收服务器返回的数据。整个过程通过Okio来进行数据的处理。Okio里面增加了缓存机制和超时机制来进行读写数据。