okhttp缓存分析
在实际项目开发中,数据的缓存是十分有必要的,一来可以减少服务器端查询压力,二来也能加速反馈时间,在网络良好时实现秒回。
服务端的缓存处理
服务端无论采用php或者java实现,在业务层都可以使用非常成熟的MemCache
或Radis
缓存框架,合理配置缓存以减少数据库压力,在发送响应时通过header中的Cache_Control
实现更精准的缓存控制策略
1.Header中的"Cache_Control"
fiddler截获的请求与响应中header示例这个接口在Request和Response中都返回了
Cache_Control
字段,科普下字段对应取值的含义
- max-age:告诉浏览器页面将缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新
- s-max-age:这个参数告诉缓存服务器(proxy,如Squid)的缓存页面的时间。如果不单独指定,缓存服务器将使用max-age。
- must-revalidate:这告诉浏览器,一旦缓存的内容过期,一定要向服务器询问是否有新版本。
- proxy-revalidate:proxy上的缓存一旦过期,一定要向服务器询问是否有新版本。
- no-cache:不做缓存。
- no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。
- public:告诉缓存服务器, 即便是对于不该缓存的内容也缓存起来。
- private:告诉proxy不要缓存,但是浏览器可使用private cache进行缓存。一般登录后的个性化页面是private的。
- no-transform: 告诉proxy不进行转换,比如告诉手机浏览器不要下载某些图片。
- max-stale: 指示客户端可以接收超时多久的响应消息。
使用
Builder#cacheControl(CacheControl)
构建Request
的"Cache-Control"
。
使用CacheControl#parse(Headers)
构建Response
的"Cache-Control"
。
- 开发中常用的几个属性:
"max-age"
,"no-cache"
,"max-stale"
关于 Pragma:no-cache
Pragma:no-cache
跟Cache-Control: no-cache
相同。Pragma: no-cache
兼容http 1.0 ,Cache-Control: no-cache
是http 1.1提供的。因此,Pragma: no-cache
可以应用到http 1.0 和http 1.1,而Cache-Control: no-cache
只能应用于http 1.1.
"Cache-Control"
其实只是请求的相应标记,告诉服务器/客户端所需的缓存策略。客户端具体的缓存执行对象是Cache
2.OkHttp中的 Cache 解析
看一看RealCall
中CacheInterceptor
的添加顺序:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
······
interceptors.add(new CacheInterceptor(client.internalCache()));
······
interceptors.addAll(client.networkInterceptors());
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
Interceptor.Chain#(Request)
的处理逻辑如下
通常
client.interceptors()
和client.networkInterceptors()
只包含客户端手动定义的拦截器,分析CacheInterCeptor
的intercept
如下(代码经过精简,只保留关键模块)
@Override public Response intercept(Chain chain) throws IOException {
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;
······
如下为强制使用缓存 却又没有获取到:返回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 (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
······如下 通过Chain调用CallServerInterceptor获取网络响应
Response networkResponse = chain.proceed(networkRequest);
······
如下 有缓存且网络返回code=304(not modified),则组合并刷新cache后返回组合响应
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();
······
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
写入新缓存,对于POST/PATCH/PUT/DELETE/MOVE不会缓存
满足指定的条件则执行缓存cache
if (HttpHeaders.hasBody(response)) { //有body
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
在代码的最后找到了缓存指令,贴出需要的条件maybeCache()
:
private CacheRequest maybeCache(Response userResponse, Request networkRequest,
InternalCache responseCache) throws IOException {
if (responseCache == null) return null;
如果request与response的head中都没有设置"no-cache",则可以缓存,否则从缓存中移除
if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
对POST/PATCH/PUT/DELETE/MOVE方法 移除缓存,get未做处理
if (HttpMethod.invalidatesCache(networkRequest.method())) {
responseCache.remove(networkRequest);
} ······
return null;
}添加进缓存中
return responseCache.put(userResponse);
}
分析到这里OkHttp的缓存模型已经基本结束了,结合源码, 我们可以:
3. 通过Interceptor
定制客户端自己的缓存策略
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Interceptor requestInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (isNetWorkDisConnected) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
return chain.proceed(request);
}
};
Interceptor responseInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (chain.request().cacheControl().noCache()
|| !response.cacheControl().noCache()) {
return response;
}//对于不需要缓存的request和服务端配置了缓存的response,直接放行
if (isNetWorkDisConnected) {
CacheControl control = new CacheControl.Builder().build();
return response.newBuilder()
.header("Cache-Control", "public, max-age=60")
.removeHeader("Pragma")
//CacheControl.parse(response.Header)时,Pragma字段会造成干扰
.build();
} else {
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=0")
.removeHeader("Pragma")
.build();
}
}
};
OkHttpClient client = builder.addInterceptor(requestInterceptor)
.addNetworkInterceptor(responseInterceptor)
.build();
写到这里告一段落吧,leader催着发周报,cookieJar这块就不贴了,以后放另一个模块写。部分逻辑借鉴自
https://www.jianshu.com/p/2821000526df,前人栽树后人乘凉,在此表示感谢