本文就是講解在OKHTTP中如何配置緩存。
HTTP協(xié)議中緩存相關(guān)
為了更好的講解OKHTTP怎么設(shè)置緩存研底,我們追根溯源先從瀏覽器的緩存說起轰传,這樣后面的OKHTTP緩存內(nèi)容自然更加好理解。
我這部分內(nèi)容也是經(jīng)網(wǎng)絡(luò)上查閱跪楞,這一篇寫得很詳細(xì):
瀏覽器 HTTP 協(xié)議緩存機(jī)制詳解
https://my.oschina.net/leejun2005/blog/369148
以下內(nèi)容基本出自于此文章。
緩存分類
http請求有服務(wù)端和客戶端之分侣灶。因此緩存也可以分為兩個(gè)類型服務(wù)端側(cè)和客戶端側(cè)甸祭。
服務(wù)端緩存
常見的服務(wù)端有Ngix和Apache。服務(wù)端緩存又分為代理服務(wù)器緩存和反向代理服務(wù)器緩存褥影。常見的CDN就是服務(wù)器緩存池户。這個(gè)好理解,當(dāng)瀏覽器重復(fù)訪問一張圖片地址時(shí),CDN會(huì)判斷這個(gè)請求有沒有緩存校焦,如果有的話就直接返回這個(gè)緩存的請求回復(fù)赊抖,而不再需要讓請求到達(dá)真正的服務(wù)地址,這么做的目的是減輕服務(wù)端的運(yùn)算壓力寨典。
客戶端緩存
客戶端主要指瀏覽器(如IE氛雪、Chrome等),當(dāng)然包括我們的OKHTTPClient.客戶端第一次請求網(wǎng)絡(luò)時(shí)耸成,服務(wù)器返回回復(fù)信息报亩。如果數(shù)據(jù)正常的話,客戶端緩存在本地的緩存目錄井氢。當(dāng)客戶端再次訪問同一個(gè)地址時(shí)弦追,客戶端會(huì)檢測本地有沒有緩存,如果有緩存的話花竞,數(shù)據(jù)是有沒有過期劲件,如果沒有過期的話則直接運(yùn)用緩存內(nèi)容。
而我們講的就是客戶端的緩存左胞。
緩存中重要的概念
Cache-Control
Cache-Control是什么呢?先別急举户,我們先用觀察一個(gè)結(jié)果烤宙,用Fiddler監(jiān)聽瀏覽器訪問http://blog.csdn.net/briblue。如下圖:
然后俭嘁,查看服務(wù)端返回來的信息如下:
可以看到頭信息中有這么一行:
Cache-Control:private
Cache-control是由服務(wù)器返回的 Response 中添加的頭信息躺枕,它的目的是告訴客戶端是要從本地讀取緩存還是直接從服務(wù)器摘取消息。它有不同的值供填,每一個(gè)值有不同的作用拐云。
max-age:這個(gè)參數(shù)告訴瀏覽器將頁面緩存多長時(shí)間稠屠,超過這個(gè)時(shí)間后才再次向服務(wù)器發(fā)起請求檢查頁面是否有更新通惫。對于靜態(tài)的頁面,比如圖片提完、CSS粘捎、Javascript薇缅,一般都不大變更,因此通常我們將存儲(chǔ)這些內(nèi)容的時(shí)間設(shè)置為較長的時(shí)間攒磨,這樣瀏覽器會(huì)不會(huì)向?yàn)g覽器反復(fù)發(fā)起請求泳桦,也不會(huì)去檢查是否更新了。
s-maxage:這個(gè)參數(shù)告訴緩存服務(wù)器(proxy娩缰,如Squid)的緩存頁面的時(shí)間灸撰。如果不單獨(dú)指定,緩存服務(wù)器將使用max-age。對于動(dòng)態(tài)內(nèi)容(比如文檔的查看頁面)浮毯,我們可告訴瀏覽器很快就過時(shí)了(max-age=0)完疫,并告訴緩存服務(wù)器(Squid)保留內(nèi)容一段時(shí)間(比如,s-maxage=7200)亲轨。一旦我們更新文檔趋惨,我們將告訴Squid清除老的緩存版本。
must-revalidate:這告訴瀏覽器惦蚊,一旦緩存的內(nèi)容過期器虾,一定要向服務(wù)器詢問是否有新版本。
proxy-revalidate:proxy上的緩存一旦過期蹦锋,一定要向服務(wù)器詢問是否有新版本兆沙。
no-cache:不做緩存。
no-store:數(shù)據(jù)不在硬盤中臨時(shí)保存莉掂,這對需要保密的內(nèi)容比較重要葛圃。
public:告訴緩存服務(wù)器, 即便是對于不該緩存的內(nèi)容也緩存起來,比如當(dāng)用戶已經(jīng)認(rèn)證的時(shí)候憎妙。所有的靜態(tài)內(nèi)容(圖片库正、Javascript、CSS等)應(yīng)該是public的厘唾。
private:告訴proxy不要緩存褥符,但是瀏覽器可使用private cache進(jìn)行緩存。一般登錄后的個(gè)性化頁面是private的抚垃。
no-transform: 告訴proxy不進(jìn)行轉(zhuǎn)換喷楣,比如告訴手機(jī)瀏覽器不要下載某些圖片。
max-stale:指示客戶機(jī)可以接收超出超時(shí)期間的響應(yīng)消息鹤树。如果指定max-stale消息的值铣焊,那么客戶機(jī)可以接收超出超時(shí)期指定值之內(nèi)的響應(yīng)消息。
我們上面的例子是 Cache-Control:private罕伯。說明服務(wù)器希望客戶端不要緩存消息曲伊,但是可以進(jìn)行 private cache 方法進(jìn)行緩存。這是因?yàn)閔ttp://blog.csdn.net/briblue是我的博客頁面追他,與用戶系統(tǒng)相關(guān)熊昌,所以為了安全起見,建議用 private cache 的方式緩存湿酸。
在OKHttp開發(fā)中我們常見到的有下面幾個(gè):
max-age
no-cache
max-stale
expires
expires 的效果等同于 Cache-Control婿屹,不過它是Http 1.0的內(nèi)容,它的作用是告訴瀏覽器緩存的過期時(shí)間推溃,在此時(shí)間內(nèi)瀏覽器不需要直接訪問服務(wù)器地址直接用緩存內(nèi)容就好了昂利。
expires 最大的問題在于如果服務(wù)器時(shí)間和本地瀏覽器相差過大的問題。那樣誤差就很大。所以基本上用Cache-Control:max-age=多少秒的形式代替蜂奸。
Last-Modified/If-Modified-Since
這個(gè)需要配合Cache-Control使用
Last-Modified:標(biāo)示這個(gè)響應(yīng)資源的最后修改時(shí)間犁苏。web服務(wù)器在響應(yīng)請求時(shí),告訴瀏覽器資源的最后修改時(shí)間扩所。
If-Modified-Since:當(dāng)資源過期時(shí)(使用Cache-Control標(biāo)識(shí)的max-age)围详,發(fā)現(xiàn)資源具有Last-Modified聲明,則再次向web服務(wù)器請求時(shí)帶上頭 If-Modified-Since祖屏,表示請求時(shí)間助赞。web服務(wù)器收到請求后發(fā)現(xiàn)有頭If-Modified-Since 則與被請求資源的最后修改時(shí)間進(jìn)行比對。若最后修改時(shí)間較新袁勺,說明資源又被改動(dòng)過雹食,則響應(yīng)整片資源內(nèi)容(寫在響應(yīng)消息包體內(nèi)),HTTP 200期丰;若最后修改時(shí)間較舊群叶,說明資源無新修改,則響應(yīng)HTTP 304 (無需包體钝荡,節(jié)省瀏覽)街立,告知瀏覽器繼續(xù)使用所保存的cache。
Etag/If-None-Match
這個(gè)也需要配合Cache-Control使用
Etag 對應(yīng)請求的資源在服務(wù)器中的唯一標(biāo)識(shí)(具體規(guī)則由服務(wù)器決定)埠通,比如一張圖片赎离,它在服務(wù)器中的標(biāo)識(shí)為 ETag: W/”ACXbWXd1n0CGMtAd65PcoA==”。
If-None-Match 如果瀏覽器在 Cache-Control:max-age=60 設(shè)置的時(shí)間超時(shí)后植阴,發(fā)現(xiàn)消息頭中還設(shè)置了Etag值蟹瘾。然后圾浅,瀏覽器會(huì)再次向服務(wù)器請求數(shù)據(jù)并添加 In-None-Match 消息頭掠手,它的值就是之前Etag值。服務(wù)器通過Etag來定位資源文件狸捕,根據(jù)它是否更新的情況給瀏覽器返回200或者是304喷鸽。
Etag機(jī)制比Last-Modified精確度更高,如果兩者同時(shí)設(shè)置的話灸拍,Etag優(yōu)先級(jí)更高做祝。
Pragma
Pragma 頭域用來包含實(shí)現(xiàn)特定的指令,最常用的是 Pragma:no-cache鸡岗。
在HTTP/1.1協(xié)議中混槐,它的含義和 Cache- Control:no-cache 相同。
以上是Http中關(guān)于緩存的相關(guān)信息轩性。接下來我們進(jìn)入主題声登,如何配置OkHttp的緩存。
OKHTTP之Cache
OKHTTP如果要設(shè)置緩存,首要的條件就是設(shè)置一個(gè)緩存文件夾悯嗓,在Android中為了安全起見件舵,一般設(shè)置為私密數(shù)據(jù)空間。通過 getExternalCacheDir() 獲取脯厨。如然后通過調(diào)用 OKHttpClient.Builder 中的 cache() 方法铅祸。如下面代碼所示:
設(shè)置好Cache我們就可以正常訪問了。我們可以通過獲取到的Response對象拿到它正常的消息和緩存的消息合武。
Response的消息有兩種類型临梗,CacheResponse 和 NetworkResponse。CacheResponse代表從緩存取到的消息眯杏,NetworkResponse 代表直接從服務(wù)端返回的消息夜焦。示例代碼如下:
我們在上面的代碼中,用同一個(gè)url地址分別進(jìn)行了兩次網(wǎng)絡(luò)訪問岂贩,然后分別用Log打印它們的信息茫经。
打印的結(jié)果非常有意思是一個(gè)機(jī)器人和一個(gè) Okhttp 的字符串。打印的結(jié)果主要說明了一個(gè)現(xiàn)象萎津,第一次訪問的時(shí)候卸伞,Response 的消息是 NetworkResponse 消息,此時(shí) CacheResponse 的值為Null锉屈。而第二次訪問的時(shí)候 Response 是 CahceResponse荤傲,而此時(shí) NetworkResponse 為空。也就說明了上面的示例代碼能夠進(jìn)行網(wǎng)絡(luò)請求的緩存颈渊。
那么 OKHTTP 中的緩存就這么點(diǎn)內(nèi)容嗎遂黍?到此為至嗎?顯然不是俊嗽。本篇文章開頭講了大段的Http協(xié)議中的相關(guān)知識(shí)點(diǎn)雾家,貌似它們還沒有出現(xiàn)。
其實(shí)控制緩存的消息頭往往是服務(wù)端返回的信息中添加的如”Cache-Control:max-age=60”绍豁。所以芯咧,會(huì)有兩種情況:
客戶端和服務(wù)端開發(fā)能夠很好溝通,按照達(dá)成一致的協(xié)議竹揍,服務(wù)端按照規(guī)定添加緩存相關(guān)的消息頭敬飒。
客戶端與服務(wù)端的開發(fā)根本就不是同一家公司,沒有辦法也不可能要求服務(wù)端按照客戶端的意愿進(jìn)行開發(fā)芬位。
第一種辦法當(dāng)然很好无拗,只要服務(wù)器在返回消息的時(shí)候添加好Cache-Control相關(guān)的消息便好。
第二種情況昧碉,就很麻煩英染,你真的無法左右別人的行為阴孟。怎么辦呢?好在OKHTTP能夠很輕易地處理這種情況税迷。那就是定義一個(gè)攔截器永丝,人為地添加Response中的消息頭,然后再傳遞給用戶箭养,這樣用戶拿到的Response就有了我們理想當(dāng)中的消息頭Headers慕嚷,從而達(dá)到控制緩存的意圖,正所謂移花接木毕泌。
緩存之?dāng)r截器
因?yàn)閿r截器可以拿到 Request 和 Response喝检,所以可以輕而易舉地加工這些東西。在這里我們?nèi)藶榈靥砑?Cache-Control 消息頭撼泛。
定義好攔截器中后挠说,我們可以添加到OKHttpClient中了。
代碼后面部分有省略愿题。主要通過在OkHttpClient.Builder()中addNetworkInterceptor()中添加损俭。而這樣也挺簡單的,就幾步完成了緩存代碼潘酗。
攔截器進(jìn)行緩存的缺點(diǎn)
網(wǎng)上有人說用攔截器進(jìn)行緩存是野路子杆兵,是HOOK行為。這個(gè)我不大同意仔夺,前面我有分析過情況琐脏,如果客戶端能夠同服務(wù)端一起協(xié)商開發(fā),當(dāng)然以服務(wù)器控制的緩存消息頭為準(zhǔn)缸兔,但問題在于你沒法這樣做日裙。所以,能夠解決問題才是最實(shí)在的惰蜜。
好了昂拂,回到正題。用攔截器控制緩存有什么不好的地方呢蝎抽?我們先看看下面的情況政钟。
1.網(wǎng)絡(luò)訪問請求的資源是文本信息路克,如新聞列表樟结,這類信息經(jīng)常變動(dòng),一天更新好幾次精算,它們用的緩存時(shí)間應(yīng)該就很短瓢宦。
2.網(wǎng)絡(luò)訪問請求的資源是圖片或者視頻,它們變動(dòng)很少灰羽,或者是長期不變動(dòng)驮履,那么它們用的緩存時(shí)間就應(yīng)該很長鱼辙。
那么,問題來了玫镐。因?yàn)镺KHTTP開發(fā)建議是同一個(gè)APP倒戏,用同一個(gè)OKHTTPCLIENT對象這是為了只有一個(gè)緩存文件訪問入口。這個(gè)很容易理解恐似,單例模式嘛杜跷。但是問題攔截器是在OKHttpClient.Builder當(dāng)中添加的。如果在攔截器中定義緩存的方法會(huì)導(dǎo)致圖片的緩存和新聞列表的緩存時(shí)間是一樣的矫夷,這顯然是不合理的葛闷,這屬于一刀切,就像這兩天專家說的要把年收入12萬元的人群劃分為高收入人群而不區(qū)別北上廣深的房價(jià)物價(jià)情況双藕。真實(shí)的情況不應(yīng)該是圖片請求有它的緩存時(shí)間淑趾,新聞列表請求有它的緩存時(shí)間,應(yīng)該是每一個(gè)Request有它的緩存時(shí)間忧陪。
那么扣泊,有解決的方案嗎??有的嘶摊,okhttp官方有建議的方法旷赖。
okhttp官方文檔建議緩存方法
okhttp中建議用 CacheControl 這個(gè)類來進(jìn)行緩存策略的制定。它內(nèi)部有兩個(gè)很重要的靜態(tài)實(shí)例更卒。
我們看到 FORCE_NETWORK 常量 用來強(qiáng)制使用網(wǎng)絡(luò)請求等孵。FORCE_CACHE 只取本地的緩存。它們本身都是 CacheControl 對象蹂空,由內(nèi)部的Buidler對象構(gòu)造俯萌。下面我們來看看CacheControl.Builder
CacheControl.Builder
它有如下方法:
知道了 CacheControl 的相關(guān)信息,那么它怎么使用呢上枕?不同于攔截器設(shè)置緩存咐熙, CacheControl 是針對 Request 的,所以它可以針對每個(gè)請求設(shè)置不同的緩存策略辨萍。比如圖片和新聞列表棋恼。下面代碼展示如何用 CacheControl 設(shè)置一個(gè)60秒的超時(shí)時(shí)間。
強(qiáng)制使用緩存
前面有講CacheControl.FORCE_CACHE這個(gè)常量锈玉。
它內(nèi)部其實(shí)就是調(diào)用 onlyIfCached() 和 maxStale 方法爪飘。它的使用方法為:
但是如前面后提到的,如果緩存不符合條件會(huì)返回504.這個(gè)時(shí)候我們要根據(jù)情況再進(jìn)行編碼拉背,如緩存不行就再進(jìn)行一次網(wǎng)絡(luò)請求师崎。
不使用緩存
前面也有講CacheControl.FORCE_NETWORK這個(gè)常量。
publicstaticfinalCacheControlFORCE_NETWORK=newBuilder().noCache().build();
它的內(nèi)部其實(shí)是調(diào)用noCache()方法椅棺,也就是不緩存的意思犁罩。它的使用方法為:
還有一種情況將maxAge設(shè)置為0齐蔽,也不會(huì)取緩存,直接走網(wǎng)絡(luò)床估。
總結(jié)
本文其實(shí)內(nèi)容不多含滴,前面講了很多http協(xié)議下的緩存機(jī)制,我認(rèn)為是值得的丐巫,知道了Cache-Control這些定義蛙吏,才能更好的懂得OKHTTP中的緩存設(shè)置。能夠明白為什么它要這樣做鞋吉,為什么它可以這樣做鸦做。
最后歸納下要點(diǎn):
http協(xié)議下Cache-Control等消息頭的作用
okhttp如何用攔截器添加Cache-Control消息頭進(jìn)行緩存定制
okhttp如何用CacheControl進(jìn)行緩存的控制。