參考: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 區別還是沒怎么搞懂,記錄下