在網(wǎng)上看到一篇國外的講okhttp緩存的文章,感覺寫的很好,簡明扼要。國內(nèi)雖然也有很多講okhttp緩存的文章,有的寫的很好,有的則寫的比較繁瑣。所以我還是把這篇文章大致翻譯一下,并結(jié)合使用過程中的一些情況以及自己的理解,來談一下okhttp緩存。原來的文章在這里

先放一張圖,這是不考慮離線情況下的okhttp緩存工作流程圖:

okhttp的緩存是基于http協(xié)議的,也就是,假如服務(wù)器返回的http header里表明了是不支持cache的,比如這樣

這是某一臺(tái)服務(wù)器返回的response header,如果返回了這樣的header的話,那么你就無法使用okhttp的cache了,只能考慮通過攔截器來自己實(shí)現(xiàn)。不過我覺得這樣很奇怪,cache本身是避免重復(fù)請求浪費(fèi)服務(wù)器資源的,所以http協(xié)議里對緩存時(shí)間,緩存策略都做了詳細(xì)的規(guī)范,諸如Cache-Control,Expires,Last-Modified,max-age等等。如果繞過http協(xié)議,自身實(shí)現(xiàn),你如何保證你的緩存沒有過期呢?或者換個(gè)說法,你如何確定這是服務(wù)器希望你使用的緩存呢?
然后接下來文章里是一些Quick questions and answers.,通過這些Q&A,你會(huì)對okhttp的緩存有一個(gè)很好的了解
1. 如何啟用okhttp的緩存?
只需要一句代碼就夠了:
int cacheSize = 10 * 1024 * 1024; // 10MB
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(new Cache(context.getCacheDir(), cacheSize)
2. 如何讓okhttp的緩存工作?
答案是Do nothing!okhttp實(shí)現(xiàn)http標(biāo)準(zhǔn)協(xié)議里的緩存策略。所以你開啟緩存之后什么也不用做。這主要取決于后端,只要后端開啟了緩存策略,那么就會(huì)在response里就會(huì)返回相應(yīng)的header,okhttp會(huì)自動(dòng)解析這些header來使用緩存。舉個(gè)例子,假如你的服務(wù)器有這樣的返回:
Date:Wed, 29 Mar 2017 10:54:09 GMT
ETag:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"
Last-Modified:Wed, 29 Mar 2017 03:25:27 GMT
那么下一次okhttp發(fā)起相同的請求的時(shí)候,會(huì)在request里加上這樣的header:
If-Modified-Since:Wed, 29 Mar 2017 03:25:27 GMT
If-None-Match:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"
3. 如何使用離線緩存?
如果服務(wù)器返回了max-age,并且okhttp也緩存了,那么在離線狀態(tài)下,緩存將正常工作。如果沒有max-age或者max-age過期了,但你仍然希望使用緩存,那么你可以這樣構(gòu)造你的request
new Request.Builder().cacheControl(CacheControl.FORCE_CACHE)
然后okhttp的緩存策略就變成了這樣:

如果你一定要在沒有網(wǎng)的情況下,使用緩存,那么你可以這樣寫
public class ForceCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (!NetworkUtils.internetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}
okHttpClient.addInterceptor(new ForceCacheInterceptor());
注意,這里使用的是Interceptor 而不是network interceptor,如果使用network interceptor,則沒有機(jī)會(huì)觸發(fā)它。
上面這些是文章里的原文,我談?wù)勛约旱目捶?。像這樣通過攔截器在沒有網(wǎng)的時(shí)候使用緩存數(shù)據(jù),不失為一個(gè)辦法,很多講okhttp緩存的文章里都會(huì)見到這樣一個(gè)攔截器。但我個(gè)人認(rèn)為其實(shí)沒什么必要,因?yàn)檫@里你首先要曾經(jīng)獲取過緩存,那么當(dāng)你使用FORCE_CACHE才會(huì)生效。其次,使用一個(gè)不知道什么時(shí)候的緩存,真的有必要嗎?
4. 使用okhttp的緩存策略來存儲(chǔ)數(shù)據(jù)是一個(gè)good idea嗎?我們只需要設(shè)置FORCE_CACHE,然后我們就總能獲取到存儲(chǔ)的數(shù)據(jù)
It’s a bad idea。這根本是兩個(gè)概念,應(yīng)該區(qū)別對待。文章在這里提到,token應(yīng)該被存儲(chǔ),而諸如新聞之類的東西才應(yīng)該被緩存。文章的意思是緩存和存儲(chǔ)是完全不同的兩個(gè)概念,其實(shí)這也很好理解。停一下,喝杯咖啡思考一下~
5. 如何確定我們是否需要服務(wù)器來校驗(yàn)緩存?
使用max-age來讓okhttp知道我們要保留這個(gè)緩存多長時(shí)間
For example, you are refreshing your Facebook setting page which has a list of settings. So, that list should stay unchanged for quite sometimes. So instead of return the same response every time, the server can just say max-age=3600 and for all the subsequence requests in the next 1 hour (3600 seconds), the client can just use the local cached data.
上面這一段是文章中的原話,大致是說,例如臉書設(shè)置頁面的一些list,短時(shí)間內(nèi)不會(huì)更改的,可以考慮設(shè)置一個(gè)小時(shí)的cache 時(shí)間。但這是2017年的文章了,我在最新的臉書的設(shè)置頁面里,沒見到類似的需要服務(wù)器返回的列表頁??傊?,通過這個(gè)例子理解max-age的用處就可以了。
6. 服務(wù)器是如何確定客戶端是否應(yīng)該使用緩存的?
通過以下幾步:
1.客戶端在request里帶上timestamp或者Etag這樣的header
2.服務(wù)器檢查在上一次請求和這一次請求里,數(shù)據(jù)是否有變化
3.如果什么也沒改變,那么服務(wù)器可以返回一些特殊碼,例如http協(xié)議里標(biāo)準(zhǔn)的304代碼,表示未修改。同時(shí),不再次返回完整的response。這樣,可以節(jié)省一些帶寬和流量。當(dāng)然,你返回200也可以,帶上Cache-Control,,Content-Location就行。
One example is the Gmail app, because the client has no way to know if there are new emails every time the user refreshes, so it can’t just simply use the cached data. What it always does is to send out the last Etag to tell the server what was the last time the user checked their inbox. Then if there is no new email, the server just tells the client to use its cache to display the same email list.
上面這段話也是原文里的,我就不一句一句翻譯了。大致是說,以gmail為例,可以通過每次請求時(shí)帶上Etag,然后服務(wù)器就知道該不該返回新的數(shù)據(jù)給客戶端,客戶端也知道該不該使用緩存了。