参考:OkHttp3 Cache
拆轮子系列:拆 OkHttp
- 首先理解下缓存的几种cachecontrol ,这里网上很多,不多赘述
Cache-Control:
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令有下几种:
- Public指示响应可被任何缓存区缓存。
- Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
- no-cache指示请求或响应消息不能缓存
- no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
- max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
- min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
- max-stale指示客户机可以接收超出超时期间的响应消息。如果指定* * max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
缓存在okhttp中的处理有两种
一.官方推荐使用在request中添加CacheControl
CacheControl.FORCE_CACHE
CacheControl.FORCE_NETWORK
- 3.使用maxStale
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxStale(10, TimeUnit.SECONDS)
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
- 4.maxAge
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxAge(10, TimeUnit.SECONDS)
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
上边试过3、4 是在请求头中加入参数,然后在CacheInterceptor
中进行了处理(借助CacheStrategy
类的private CacheStrategy getCandidate()
方法).
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
CacheControl responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
二.使用拦截器,Interceptor
因为责任链的顺序是 cache -> your Application Interceptor -> network Interceptor 所以你的interceptor 要是缓存处理掉话需要 okhttpclient.addInterceptor(); 要是返回结果添加请求头的话需要使用addNetworkInterceptor();
public class OkLibCacheInterceptor implements Interceptor {
private Context context;
public OkLibCacheInterceptor(Context context) {
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//判断网络状态 直接取缓存,是在最上层进行拦截,但是我们的OkLibCacheInterceptor 是addNetworkInterceptor() ,在网络请求之后进行的
//所以这里不会被递归调用到,一旦网络失败便不会进入此拦截器了 ,学习下,所以这里的使用缓存是不管用的得需要在请求时加入,或者使用addInterceptor 放在网络拦截器之前
if (!isNetworkConnected(context)) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
Log.e("OkLibCacheInterceptor", "暂无网络");
}
Log.e("新请求", "=request==" + request.toString());
Response response = chain.proceed(request);
if (isNetworkConnected(context)) {
int maxAge = 60 * 60 * 24; // 有网络的时候从缓存1天后失效
response = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // // 无网络缓存保存四周
response = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
Log.e("oklibCacheInter", response.headers().toMultimap().toString());
return response;
}
public boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
}
NOTE
- 1.默认okhttp缓存只支持get请求,不支持post请求,post需要自己进行缓存
- 2.进行了抓包在使用maxAge的时候依然发起请求,当然我在使用拦截器的时候没事.这里我看了下官网的介绍是
This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxStale(365, TimeUnit.DAYS)
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
所以使用maxStale 进行处理
//todo 这里maxAge maxStale 区别还是没怎么搞懂,记录下