我們?nèi)粘i_發(fā)經(jīng)常和網(wǎng)絡(luò)打交道谢肾,從服務(wù)器上面獲取數(shù)據(jù)。但是如果我們?nèi)绻诙虝r(shí)間內(nèi)多次向服務(wù)器請(qǐng)求的數(shù)據(jù)其實(shí)都是一樣的,我們是沒有必要這么浪費(fèi)用戶的流量的捎谨。為了提高用戶的體驗(yàn),我們需要合理使用緩存憔维,要使用緩存就得搞明白緩存的一些相關(guān)策略機(jī)制涛救,于是就有了這篇文章。
Http的緩存機(jī)制
我們可以先看下面的思維導(dǎo)圖來簡單了解一下Http協(xié)議里面的一些緩存機(jī)制:
正如上圖所示业扒,Http的緩存就僅可用于Get請(qǐng)求检吆。為了方便大家理解上圖,這里我簡單介紹一下一些緩存相關(guān)的重要字段:
Expires:
這個(gè)字段是記錄著緩存的有效期,如Expires:Wed, 26 Jul 2017 13:18:20 GMT.當(dāng)客戶端發(fā)現(xiàn)這個(gè)緩存有效期已經(jīng)過去了,就會(huì)重新向服務(wù)器請(qǐng)求數(shù)據(jù).但是這個(gè)字段存在問題,就是客戶端本地時(shí)間和服務(wù)器端時(shí)間相差過大,就是出現(xiàn)緩存讀取失效的情況.
Cache-Control:
這個(gè)字段是在http1.1之后出現(xiàn)了,用于替代Expires字段的作用.如果Cache-Control:max-age=(X)秒和Expires同時(shí)存在,那么就會(huì)給Cache-Control:max-age(X)秒替代.不過這個(gè)字段并沒有這么簡單程储,它還有其他重要的字段蹭沛,我們來看下面的介紹:
<code>[1] max-age:</code>這個(gè)上面已經(jīng)簡單介紹了一下,這里再具體說明一下:緩存的內(nèi)容將在xxx秒后失效,這個(gè)選項(xiàng)只在HTTP 1.1可用,優(yōu)先級(jí)比Expires高.簡單來說就是判斷緩存內(nèi)容上次訪問時(shí)間是否比這個(gè)max-age要小,小就使用緩存.
<code>[2] no-cache:</code>先不要讀取緩存中的文件,向WEB服務(wù)器請(qǐng)求驗(yàn)證緩存是否新鮮,新鮮則使用緩存
<code>[3] must-revalidate:</code>作用和相同,但是更為嚴(yán)格.每次請(qǐng)求都校驗(yàn)緩存和服務(wù)器源文件,一致就使用緩存,不一致就拿最新
<code>[4] no-store:</code>這個(gè)字段很關(guān)鍵,它表示數(shù)據(jù)不在硬盤中臨時(shí)保存
<code>[5] only-if-cached:</code>就是在客戶端有緩存時(shí)就是用客戶端的緩存,這個(gè)一般都是在無網(wǎng)時(shí)使用
<code>[6] max-stale:</code>只要緩存的時(shí)間沒有超過它(max-stale)指定的時(shí)間,就可以加載使用.我們可以在無網(wǎng)絡(luò)的情況下使用
上面關(guān)于<code>no-cache</code>和<code>must-revalidate</code>的內(nèi)容,我是參考下面這篇文章的:
web性能優(yōu)化之:no-cache與must-revalidate深入探究
Last-Modified / If-Modified-Since
這是需要有緩存存在才能起作用的字段章鲤。
<code>Last-Modified:</code>這個(gè)字段是用來標(biāo)記這個(gè)響應(yīng)資源的最后修改時(shí)間致板。服務(wù)器在響應(yīng)請(qǐng)求時(shí),將會(huì)告訴客戶端這個(gè)響應(yīng)資源的最后修改時(shí)間咏窿,如Last-Modified:Thu, 13 Jul 2017 06:31:05 GMT;
<code>If-Modified-Since:</code>當(dāng)緩存不新鮮時(shí)斟或,發(fā)現(xiàn)緩存具有Last-Modified聲明,服務(wù)器就會(huì)檢查請(qǐng)求頭If-Modified-Since(客戶端緩存頁面數(shù)據(jù)的最后修改時(shí)間)集嵌,服務(wù)器會(huì)把這個(gè)時(shí)間與服務(wù)器上實(shí)際文件的最后修改時(shí)間進(jìn)行比較萝挤。如If-Modified-Since:Mon , 24 Jul 2017 18:53:33 GMT
如果時(shí)間一致,那么返回HTTP狀態(tài)碼304根欧,客戶端接到之后怜珍,就直接把本地緩存文件顯示到屏幕上面。
如果時(shí)間不一致凤粗,就返回HTTP狀態(tài)碼200和新的文件內(nèi)容酥泛,客戶端接到之后,會(huì)丟棄舊文件,把新文件緩存起來并顯示新的內(nèi)容柔袁。
Etag / If-None-Match
這是需要有緩存存在才能起作用的字段呆躲,而且<code>Etag</code>的優(yōu)先級(jí)高于<code>Last-Modified</code>。
<code>Etag:</code>這個(gè)字段是請(qǐng)求變量的實(shí)體標(biāo)記捶索。簡單來說就是服務(wù)器響應(yīng)時(shí)給請(qǐng)求URL標(biāo)記插掂,并在HTTP響應(yīng)頭中將其傳送到客戶端進(jìn)行存儲(chǔ)。服務(wù)器端返回的格式如:Etag:“5d8c72a5edda8d6a:3239″.這是通過對(duì)文件的索引節(jié)(INode)大小(Size)和最后修改時(shí)間(MTime)進(jìn)行Hash后得到的數(shù)值;
<code>If-None-Match:</code>上面說了腥例,<code>Etag</code>是服務(wù)器的報(bào)文才有的,那么客戶端需要給服務(wù)器發(fā)送Etag應(yīng)該怎么辦?
就是在http報(bào)文里面使用If-None-Match這個(gè)字段來存放Etag的值.服務(wù)器收到請(qǐng)求后發(fā)現(xiàn)有If-None-Match則與被請(qǐng)求資源的相應(yīng)校驗(yàn)串進(jìn)行比對(duì),之后才決定返回200或304.如:If-None-Match:"5d8c72a5edda8d6a:3239"
Etag 和 Last-Modified
到這里我們會(huì)發(fā)現(xiàn)一件事辅甥,就是<code>Last-Modified</code>和<code>Etag</code>的功能居然是重復(fù)的,這就奇怪了燎竖。既然使用<code>Last-Modified</code>已經(jīng)足以讓客戶端知道本地的緩存副本是否足夠新璃弄,為什么還需要<code>Etag</code>呢?其實(shí)還是有原因的,因?yàn)?lt;code>Last-Modified</code>有下面幾個(gè)問題難以解決:
1.<code>Last-Modified</code>標(biāo)注的最后修改只能精確到秒級(jí)构回,如果某些文件在1秒鐘以內(nèi)被修改多次的話谢揪,它將不能準(zhǔn)確標(biāo)注文件的修改時(shí)間
2.如果某些文件會(huì)被定期生成,當(dāng)有時(shí)內(nèi)容并沒有任何變化捐凭,但<code>Last-Modified</code>卻改變了拨扶,導(dǎo)致文件沒法使用緩存
3.有可能存在服務(wù)器沒有準(zhǔn)確獲取文件修改時(shí)間或者與代理服務(wù)器時(shí)間不一致等情形
<code>Etag</code>是服務(wù)器自動(dòng)生成或者由開發(fā)者生成的對(duì)應(yīng)資源在服務(wù)器端的唯一標(biāo)識(shí)符,能夠更加準(zhǔn)確的控制緩存;<code>Last-Modified</code>與<code>ETag</code>是可以一起使用的,服務(wù)器會(huì)優(yōu)先驗(yàn)證<code>ETag</code>诽俯,一致的情況下轧苫,才會(huì)繼續(xù)比對(duì)<code>Last-Modified</code>,最后才決定是否返回304或200.
通過okhttp的cache設(shè)置來加深理解
我們現(xiàn)在已經(jīng)了解了部分Http的緩存策略,下面我們來通過代碼來加深了解吧。本人比較喜歡okhttp,所以下面的代碼都是用它來弄了印蓖。
public void click(View view) {
int maxCacheSize = 10 * 1024 * 1024;
//設(shè)置緩存路徑
Cache cache = new Cache(getCacheDir(), maxCacheSize);
OkHttpClient client = new OkHttpClient.Builder()
//設(shè)置緩存
.cache(cache)
.build();
Request request = new Request.Builder()
.url("http://www.qq.com/")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.w(TAG, "start");
Log.w(TAG, "response cache :" + response.cacheResponse());
Log.w(TAG, "response network :" + response.networkResponse());
response.body().close();
}
@Override
public void onFailure(Call call, IOException e) {
}
});
}
我們先來看下qq官網(wǎng)的Cache-Control:
可以看到max-age=60,說明本地緩存在60秒內(nèi)都是新鮮的京腥,那么我們就連續(xù)兩次訪問qq官網(wǎng)來看下Log吧:
從打印中赦肃,我們可以看出第一次訪問我們是從服務(wù)器中獲取最新的數(shù)據(jù);但是第二次訪問時(shí)公浪,就直接使用本地緩存了他宛,畢竟兩次請(qǐng)求相隔時(shí)間最多才1秒。那么okhttp緩存到本地時(shí)欠气,數(shù)據(jù)是以什么格式保存的呢?這里我是將緩存保存在內(nèi)部存儲(chǔ)里的:
為了滿足好奇心厅各,這里我們來逐一來看下:
原來這3個(gè)文件分別是請(qǐng)求的header,加密的response和每一條reponse讀寫操作狀態(tài)記錄预柒。
ok队塘,我們接著來測試袁梗,這次換簡書的官網(wǎng)來測試,先看下簡書官網(wǎng)的一些header信息:
max-age=0說明緩存有效時(shí)間為0秒憔古,但是設(shè)置了Etag校驗(yàn)緩存遮怜,我們看下請(qǐng)求打印的Log吧:
我們重點(diǎn)看第二次訪問的Log,okhttp的cacheResponse可用投放,而且它的networkResponse返回的http碼為304,說明了本地緩存是可用的适贸。
那么灸芳,有什么網(wǎng)站是不支持緩存嗎?當(dāng)然有了拜姿,知乎是一個(gè)很有名氣的網(wǎng)站烙样,它就不支持客戶端使用緩存:
它已經(jīng)在Cache-Control里面設(shè)置不支持硬盤存儲(chǔ)的no-store字段了,我們看下運(yùn)行的Log:
可以看到蕊肥,我們已經(jīng)多次重新訪問知乎的首頁了谒获,但是每次都只能重新向服務(wù)器拿最新的數(shù)據(jù)。