使用Retrofit和Okhttp實(shí)現(xiàn)網(wǎng)絡(luò)緩存,更新于2016.02.02
本文使用 Retrofit2.0.0-beta2、Okhttp 2.6.0(Okhttp3.0之后api寫(xiě)法有變化)
- 配置Okhttp的Cache
- 配置請(qǐng)求頭中的cache-control或者統(tǒng)一處理所有請(qǐng)求的請(qǐng)求頭
- 云端配合設(shè)置響應(yīng)頭或者自己寫(xiě)攔截器修改響應(yīng)頭中cache-control
最后實(shí)現(xiàn)的效果是:有網(wǎng)的時(shí)候根據(jù)你每個(gè)接口設(shè)置的需要緩存的時(shí)間(1分鐘、5分鐘等)進(jìn)行緩存,過(guò)了時(shí)間重新請(qǐng)求;沒(méi)網(wǎng)的時(shí)候讀緩存。
在這里插一句為什么要做緩存,或者說(shuō)有什么好處?
減少服務(wù)器負(fù)荷,降低延遲提升用戶(hù)體驗(yàn)。復(fù)雜的緩存策略會(huì)根據(jù)用戶(hù)當(dāng)前的網(wǎng)絡(luò)情況采取不同的緩存策略,比如在2g網(wǎng)絡(luò)很差的情況下,提高緩存使用的時(shí)間;不用的應(yīng)用、業(yè)務(wù)需求、接口所需要的緩存策略也會(huì)不一樣,有的要保證數(shù)據(jù)的實(shí)時(shí)性,所以不能有緩存,有的你可以緩存5分鐘,等等。你要根據(jù)具體情況所需數(shù)據(jù)的時(shí)效性情況給出不同的方案。當(dāng)然你也可以全部都一樣的緩存策略,看你自己。
1.配置okhttp中的Cache
OkHttpClient okHttpClient = new OkHttpClient();
File cacheFile = new File(context.getCacheDir(), "[緩存目錄](méi)");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
okHttpClient.setCache(cache);
2.配置請(qǐng)求頭中的cache-control
緩存的相關(guān)知識(shí)和參數(shù)的說(shuō)明,我是個(gè)鏈接1
緩存的相關(guān)知識(shí)和參數(shù)的說(shuō)明,我是個(gè)鏈接2
在Retrofit中,我們可以通過(guò)@Headers來(lái)配置,如:
@Headers("Cache-Control: public, max-age=3600)
@GET("merchants/{shopId}/icon")
Observable<ShopIconEntity> getShopIcon(@Path("shopId") long shopId);
沒(méi)有設(shè)置的可以即為有網(wǎng)的時(shí)候不進(jìn)行緩存。
或者你所有接口在有網(wǎng)的時(shí)候都不需要緩存或者都需要緩存且時(shí)間一樣,那么也不用配置每個(gè)接口的@Headers的Cache-Control了。
3.云端配合設(shè)置響應(yīng)頭或者自己寫(xiě)攔截器修改響應(yīng)頭response中cache-control
到這一步緩存就已經(jīng)待在你的緩存目錄了。
如果云端有處里cache的話(huà),就已經(jīng)可以了。
但是很可能云端沒(méi)有處理,所以返回的響應(yīng)頭中cache-control是no-cache,這時(shí)候你還是無(wú)法做緩存,大家可以用okhttp的寫(xiě)日志攔截器查看響應(yīng)頭的內(nèi)容。
[ Okhttp Interceptors 使用說(shuō)明,我是個(gè)鏈接](https://github.com/square/okhttp/wiki/Interceptors" target="_blank)
如果云端現(xiàn)在不方便處理的話(huà),你也可以自己搞定緩存的,那就是寫(xiě)攔截器修改響應(yīng)頭中的cache-control。我把請(qǐng)求頭中的cache-control讀出來(lái)然后設(shè)置到了響應(yīng)頭中。
設(shè)置攔截器:
REWRITE_CACHE_CONTROL_INTERCEPTOR攔截器需要同時(shí)設(shè)置networkInterceptors和interceptors(OKHTTP3.0配置是否有效待我測(cè)試)
okHttpClient.interceptors().add(LoggingInterceptor);
okHttpClient.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
okHttpClient.interceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
攔截器如下:云端響應(yīng)頭攔截器,用來(lái)配置緩存策略
/**
* 云端響應(yīng)頭攔截器,用來(lái)配置緩存策略
* Dangerous interceptor that rewrites the server's cache-control header.
*/
private final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> {
Request request = chain.request();
if(!NetUtils.hasNetwork(context)){
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
Logger.t(TAG).w("no network");
}
Response originalResponse = chain.proceed(request);
if(NetUtils.hasNetwork(context)){
//有網(wǎng)的時(shí)候讀接口上的@Headers里的配置,你可以在這里進(jìn)行統(tǒng)一的設(shè)置
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
}else{
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=2419200")
.removeHeader("Pragma")
.build();
}
};
最后日志攔截器也貼上來(lái)吧
private final Interceptor LoggingInterceptor = chain -> {
Request request = chain.request();
long t1 = System.nanoTime();
Logger.t(TAG).i(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Logger.t(TAG).i(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
};
以下測(cè)試Cache-Control的配置在請(qǐng)求頭和響應(yīng)頭中都有且一樣。
max-stale在請(qǐng)求頭設(shè)置有效,在響應(yīng)頭設(shè)置無(wú)效。(因?yàn)閙ax-stale是請(qǐng)求頭設(shè)置參數(shù),參考上面的緩存相關(guān)的知識(shí)第二個(gè)鏈接)
max-stale和max-age同時(shí)設(shè)置的時(shí)候,緩存失效的時(shí)間按最長(zhǎng)的算。
關(guān)于max-age和max-stale我這里做了一個(gè)測(cè)試:
測(cè)試結(jié)果:
我在請(qǐng)求頭中設(shè)置了:Cache-Control: public, max-age=60,max-stale=120,響應(yīng)頭的Cache-Control和請(qǐng)求頭一樣。
- 在第一次請(qǐng)求數(shù)據(jù)到一分鐘之內(nèi),響應(yīng)頭有:Cache-Control: public, max-age=60,max-stale=120
- 在1分鐘到3分鐘在之間,響應(yīng)頭有:Cache-Control: public, max-age=60,max-stale=120
Warning: 110 HttpURLConnection "Response is stale"
可以發(fā)現(xiàn)多了一個(gè)Warning。 - 三分鐘的時(shí)候:重新請(qǐng)求了數(shù)據(jù),如此循環(huán),如果到了重新請(qǐng)求的節(jié)點(diǎn)此時(shí)沒(méi)有網(wǎng),則請(qǐng)求失敗。
另外關(guān)于緩存有一個(gè)rxcache也可以試試。
感謝@Picasso_L一起討論研究