一般http/https的缓存策略都是,如果某一时间段内第二次请求服务器,服务器的数据和上次相比较并没有做任何改变,那么就会返回一个code 304,表示:client可以使用缓存,然后client从缓存中取出response返回给用户。
Okhttp的缓存方式是将可复用的responsebody通过CacheInterceptor缓存到文件中,先来看CacheInterceptor这个类
在CacheInterceptor这个类中只有一个属性
InternalCache cache 根据官方文档的描述他是一个okhttp内部缓存接口,大家应该使用Cache类而不是这个内部使用的类。它提供了一堆get put remove方法如下:
1. Responseget(Request request)throws IOException; //通过request获得response,不出意外,应该可以通过这个方法获得缓存中的response
2. CacheRequestput(Responseresponse)throws IOException; //将request存入缓存中
3. void remove(Request request)throws IOException; //移除request
4. void update(Responsecached, Responsenetwork); //更新已经缓存的response
5. void trackConditionalCacheHit(); //如果本次请求的response是从缓存中拿的,那么就埋下本次命中缓存策略成功的信息,具体的在具体行为中实现
6. void trackResponse(CacheStrategy cacheStrategy); //同样是埋点,跟踪这个响应,但是这里需要传CacheStrategy,它是一个缓存策略,也就是说,这个方法将会跟踪,使用传入缓存策略缓存的response。CacheStrategy,用于本次决定本次请求是使用网络还是缓存,如果指定固定的请求策略,比如使用缓存,当缓存过期,或者服务端数据更新了,这个缓存策略会让请求变得复杂。
这样 内部缓存接口都分析完了,它只是提供request,response的获取或跟踪或移除
接下去,我们继续分析这个cacheintercepetor,okhttp的intercepetor就是拦截器,先拦截下来,然后转发出去
来看一下cacheintercepetor在哪里用到,点击之后跳到了RealCall中
没有错,就是RealCall里面的getResponseWithInterceptorChain()这个方法,首先经过各种拦截器,然后返回response。在interceptors.add(new CacheInterceptor(client.internalCache()));//添加缓存拦截器的时候,或传入通过OkhttpClient构建的自定义拦截器,如果没有传入自定义拦截器,那就会传入系okhttp系统自带的默认拦截器,当添加完之后又初始化了一个RealInterceptorChain对象,很明显,这是一个拦截器责任链,将会链式调用各个拦截器的方法,这里不做深究。
继续往下看Cacheintercepetor到底做了什么,开始真正分析了!!
首先是最重要的intercept()方法
public Response intercept(Chain chain)throws IOException;在拦截之后,会放回response对象,
接下去看intercept的第一步
Response cacheCandidate =cache !=null ?cache.get(chain.request()):null;如果传入的Cache不为空的话,就通过cache拿response,但是这个response并不是最后转发给client的,因为这里并不知道服务器有没有修改数据。因此这里是一个候选response
第二步,获得当前时间,用于接下去查看请缓存信息是否过期
long now = System.currentTimeMillis(); //获得系统时间
第三步获得缓存策略,根据缓存策略构造request和response
CacheStrategy strategy =new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache !=null) {
cache.trackResponse(strategy);
}
在这里我们又用到了这个缓存策略,那么我们来看一下这个缓存策略到底是干什么的,首先他是通过工厂模式构造的,
先看一下它的构造方法
public Factory(long nowMillis, Request request, Response cacheResponse),它需要传入一个nowMillis,一个request,一个response,而这个response我们可以从命名中发现,它是从缓存中取出的response,那么传入这三个参数是用来干什么呢?
首先在构造方法中,直接使用了传进来的cacheResponse
通过response拿到了缓存时发送请求的时间,接收到相应的时间,以及响应头并且从响应头中取出各个参数
ServerDate:服务器的时间
Expires:应该在什么时候认为文档已经过期,从而不再缓存它?
LastModified:服务器最后一次修改的时间
ETag:也是用于判断服务器资源是否有修改
Age:从原始服务器到代理缓存形成的估算时间
获得了这些头信息之后,就要通过这些头信息来确定是否需要使用缓存
CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()
其中的.get()是返回一个CacheStrategy,构造CacheStrategy对象的则是getCandidate()这个方法点进去之后可以看到该方法会判断各种条件来返回一个CacheStrateg
例如:if (cacheResponse ==null) {return new CacheStrategy(request, null);} //如果没有缓存的response,则构造一个新网络的请求
所以这个CacheStrategy是通过响应头信息与服务器端信息进行对比,最后返回是否使用新的网络请求或者直接使用缓存
然后我们回到interce(),获得了缓存策略之后,接下去就是拿到重新构造的request和response
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
接着通过拿到的request和response来判断返回一个真正的response给用户
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();
}
如果不使用新的网络请求,并且缓存的Response已经过期,或者没有缓存的response,那么就会返回一个504错误
经过上述判断之后,如果不使用新的网络请求,并且cacheResponse可用,那么就利用cacheResponse返回一个新的response
if (networkRequest ==null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
第三种情况:如果即使用新的request,并且缓存可用,那就通过新的response更新响应头
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 {
closeQuietly(cacheResponse.body());
}
}
网友评论