前言
關(guān)于okhttp的緩存,網(wǎng)上有大量的文章,或相同,或不同,方式不一,但都八九不離十,原理都是通過(guò)CacheControl的設(shè)置策略不同來(lái)實(shí)現(xiàn)的。
但是,真正實(shí)踐過(guò)的人會(huì)發(fā)現(xiàn),好像有這樣那樣的問(wèn)題。
比如:
- 到底是用addNetInterceptor呢還是用addInterceptor,不同的用法有不同的效果
- 什么有網(wǎng)的時(shí)候是maxAge,無(wú)網(wǎng)的時(shí)候又是maxStale等等
簡(jiǎn)直不明白。
于是乎,我個(gè)人做了很多很多的嘗試,幾乎把網(wǎng)上的方法都試了一遍,看了大量換湯不換藥的文章。下面是借鑒文章出處,但是我的方法都與他們的不同。
http://blog.csdn.net/u014614038/article/details/51210685
http://blog.csdn.net/Picasso_L/article/details/50579884
http://blog.csdn.net/briblue/article/details/52920531
http://www.itdecent.cn/p/412157e236ad
https://stackoverflow.com/questions/23429046/can-retrofit-with-okhttp-use-cache-data-when-offline
https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html
對(duì)于okhttp的緩存解決方案,我的需求是:
1、有網(wǎng)的時(shí)候也可以讀取緩存,并且可以控制緩存的過(guò)期時(shí)間,這樣可以減輕服務(wù)器壓力
2、有網(wǎng)的時(shí)候不讀取緩存,比如一些及時(shí)性較高的接口請(qǐng)求
3、無(wú)網(wǎng)的時(shí)候讀取緩存,并且可以控制緩存過(guò)期的時(shí)間
正文
說(shuō)了那么多,先直接上解決方案
/**
* 有網(wǎng)時(shí)候的緩存
*/
final Interceptor NetCacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
int onlineCacheTime = 30;//在線的時(shí)候的緩存過(guò)期時(shí)間,如果想要不緩存,直接時(shí)間設(shè)置為0
return response.newBuilder()
.header("Cache-Control", "public, max-age="+onlineCacheTime)
.removeHeader("Pragma")
.build();
}
};
/**
* 沒(méi)有網(wǎng)時(shí)候的緩存
*/
final Interceptor OfflineCacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!SystemTool.checkNet(AppContext.context)) {
int offlineCacheTime = 60;//離線的時(shí)候的緩存的過(guò)期時(shí)間
request = request.newBuilder()
// .cacheControl(new CacheControl
// .Builder()
// .maxStale(60,TimeUnit.SECONDS)
// .onlyIfCached()
// .build()
// ) 兩種方式結(jié)果是一樣的,寫(xiě)法不同
.header("Cache-Control", "public, only-if-cached, max-stale=" + offlineCacheTime)
.build();
}
return chain.proceed(request);
}
};
//setup cache
File httpCacheDirectory = new File(AppContext.context.getCacheDir(), "okhttpCache");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(NetCacheInterceptor)
.addInterceptor(OfflineCacheInterceptor)
.cache(cache)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
沒(méi)錯(cuò)最終的解決方案就是,兩個(gè)interceptor,并且針對(duì)不同的網(wǎng)絡(luò)情況進(jìn)行不同的處理。(為什么是兩個(gè)interceptor后面會(huì)講到)
如果趕時(shí)間的朋友,可以直接拿去用,看到這里就行了。如果覺(jué)得用起來(lái)就問(wèn)題,歡迎下面留言。
實(shí)踐和測(cè)試的過(guò)程
首先我最初的寫(xiě)法就是來(lái)源于網(wǎng)上大多數(shù)人的寫(xiě)法,類似
public class HttpCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkHelper.isNetConnected(MainApplication.getContext())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (NetWorkHelper.isNetConnected(MainApplication.getContext())) {
int maxAge = 60 * 60; // read from cache for 1 minute
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return response;
}
}
//設(shè)置緩存100M
Cache cache = new Cache(new File(MainApplication.getContext().getCacheDir(),"httpCache"),1024 * 1024 * 100);
return new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(new HttpCacheInterceptor())
.build();
這段代碼滿足不了我的需求,并且會(huì)發(fā)現(xiàn),有些情況離線的時(shí)候緩存過(guò)期的時(shí)間不可靠,有些情況在線的時(shí)候緩存不可用。對(duì)于我的需求不可兼得。(故網(wǎng)上有人還分了2種不同情況的寫(xiě)法來(lái)針對(duì)不同的需求,但我想,為什么不綜合起來(lái)呢?)
對(duì)于這段代碼疑惑點(diǎn):
1、max-age是啥,maxStale是啥,他們的區(qū)別是啥?
2、為什么沒(méi)有網(wǎng)絡(luò)的情況下,request要cacheControl.FORCE_CACHE
3、為什么又要對(duì)response設(shè)置header的cache-control,到底request的設(shè)置跟response的設(shè)置有什么區(qū)別?
4、addNetInterceptor和addInterceptor有什么區(qū)別?
解答:
1、max-age是啥,maxStale是啥,他們的區(qū)別是啥?
maxAge和maxStale的區(qū)別在于:
maxAge:沒(méi)有超出maxAge,不管怎么樣都是返回緩存數(shù)據(jù),超過(guò)了maxAge,發(fā)起新的請(qǐng)求獲取數(shù)據(jù)更新,請(qǐng)求失敗返回緩存數(shù)據(jù)。
maxStale:沒(méi)有超過(guò)maxStale,不管怎么樣都返回緩存數(shù)據(jù),超過(guò)了maxStale,發(fā)起請(qǐng)求獲取更新數(shù)據(jù),請(qǐng)求失敗返回失敗
2、為什么沒(méi)有網(wǎng)絡(luò)的情況下,request要cacheControl.FORCE_CACHE
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
可以看到FORCE_CACHE是設(shè)置了maxStale的最大時(shí)間為interger的最大時(shí)間,所以,意思就是無(wú)論如何,都不會(huì)超過(guò)這個(gè)時(shí)間,所以就是一直(強(qiáng)制)拿緩存,也是想要實(shí)現(xiàn)緩存的正確邏輯
一般控制緩存有兩種方式:
1、在request里面去設(shè)置cacheControl()策略
2、在header里面去添加cache-control
后面也會(huì)看到,在request里面設(shè)置header的cache-control和調(diào)用cacheControl方法來(lái)設(shè)置其實(shí)是一樣的,我們就是通過(guò)在request里面來(lái)控制無(wú)網(wǎng)緩存的maxStale過(guò)期時(shí)間的
3、為什么又要對(duì)response設(shè)置header的cache-control,到底request的設(shè)置跟response的設(shè)置有什么區(qū)別?
其實(shí)我到現(xiàn)在都還沒(méi)有搞清楚,為啥那些人要這樣寫(xiě),只是后面我在測(cè)試的過(guò)程中推斷出來(lái),有網(wǎng)的時(shí)候和無(wú)網(wǎng)的時(shí)候?qū)τ趇nterceptor的調(diào)用是不同的,產(chǎn)生的結(jié)果也是不同的。比如request設(shè)置的時(shí)候就對(duì)無(wú)網(wǎng)緩存及其時(shí)間控制有效,response就不行
4、addNetInterceptor和addInterceptor有什么區(qū)別?
addNetInterceptor是添加網(wǎng)絡(luò)攔截器,addInterceptor是添加應(yīng)用攔截器,如果看到okhttp的流程分析的知道:應(yīng)用攔截器是在網(wǎng)絡(luò)攔截器前執(zhí)行的。
如果我使用的是addNetInterceptor:
1、有網(wǎng)的情況下,可以在期限內(nèi)拿到緩存,而沒(méi)有去請(qǐng)求接口(通過(guò)測(cè)試數(shù)據(jù)庫(kù)的數(shù)據(jù)改動(dòng)來(lái)判斷的)
2、沒(méi)有網(wǎng)的情況下,直接就ConnectException了,根本不會(huì)走到interceptor里面去了。(網(wǎng)上很多人都提出了這樣的問(wèn)題)
如果我使用的是addInterceptor:
1、有網(wǎng)的情況下,明明設(shè)置的是60秒,但是每次都沒(méi)有去拿緩存而都是請(qǐng)求的接口。(通過(guò)測(cè)試數(shù)據(jù)庫(kù)的數(shù)據(jù)改動(dòng)來(lái)判斷的)
2、沒(méi)有網(wǎng)的情況下,可以拿到緩存數(shù)據(jù)(猜想:可能是因?yàn)閼?yīng)用攔截器在網(wǎng)絡(luò)攔截器前執(zhí)行,沒(méi)有網(wǎng)的情況下,本身就執(zhí)行不到網(wǎng)絡(luò)攔截器里面去),但是緩存過(guò)期時(shí)間是“永久”,因?yàn)镕ORCE_CACHE里面已經(jīng)設(shè)置為了integer的最大值,21億秒左右,堪稱永久
但是,依舊沒(méi)有辦法控制無(wú)網(wǎng)時(shí)候的緩存過(guò)期時(shí)間
面對(duì)這些個(gè)問(wèn)題,我也是很無(wú)奈。只得不停地嘗試,不停地摸索。于是就在嘗試的過(guò)程中發(fā)現(xiàn)了它的一些規(guī)則,于是最終寫(xiě)出了一個(gè)自認(rèn)為“萬(wàn)全”的方法。
- 既然想要有網(wǎng)的情況下拿緩存,那么就需要addNetInterceptor,如果需要無(wú)網(wǎng)的情況下拿緩存,就需要addInterceptor,所以不如直接做兩個(gè)interceptor吧!
- 另外,如果想要控制有網(wǎng)的時(shí)候不去讀取緩存,可以直接通過(guò)在response里設(shè)置maxAge=0來(lái)實(shí)現(xiàn)。
- 這里通過(guò)大量實(shí)驗(yàn)發(fā)現(xiàn),只有在request去設(shè)置其maxStale才能控制無(wú)網(wǎng)時(shí)候的緩存時(shí)間,在response里面去控制是不行的!
延伸
如果無(wú)網(wǎng)的時(shí)候,stale緩存時(shí)間過(guò)了,會(huì)怎么樣呢?
會(huì)報(bào)504錯(cuò)誤(屬于正常的邏輯)。
最后
歡迎留言,歡迎提問(wèn)題、交流。