OKHTTP之緩存配置詳解

前言

在Android開(kāi)發(fā)中我們經(jīng)常要進(jìn)行各種網(wǎng)絡(luò)訪問(wèn),比如查看各類新聞、查看各種圖片。但有一種情形就是我們每次重復(fù)發(fā)送的網(wǎng)絡(luò)請(qǐng)求其實(shí)返回的內(nèi)容都是一樣的。比如一個(gè)電影類APP,每一次向服務(wù)器申請(qǐng)某個(gè)電影的相關(guān)信息,如封面、簡(jiǎn)介、演員表等等,它們的信息都是一樣的。顯然,這樣有點(diǎn)浪費(fèi)資源,最主要的是這些重復(fù)的請(qǐng)求產(chǎn)生了沒(méi)有必要的流量。流量、流量、流量?。?!重要的事情說(shuō)三遍!剛開(kāi)始工作的我也不懂,后來(lái)才發(fā)現(xiàn),流量是要付費(fèi)的,而且超貴,公司那么小,一個(gè)月要支付寬帶運(yùn)營(yíng)商巨額的流量費(fèi)用。所以領(lǐng)導(dǎo)們都想方設(shè)法地要節(jié)省帶寬。

其實(shí)這在整個(gè)軟件開(kāi)發(fā)中隨時(shí)可見(jiàn),解決的方法就是把重復(fù)請(qǐng)求的數(shù)據(jù)緩存在本地,并設(shè)置超時(shí)時(shí)間,在規(guī)定時(shí)間內(nèi),客戶端不再向遠(yuǎn)程請(qǐng)求數(shù)據(jù),而是直接從本地緩存中取數(shù)據(jù)。這樣一來(lái)提高了響應(yīng)速度,二來(lái)節(jié)省了網(wǎng)絡(luò)帶寬(也就是節(jié)省了錢)。 本文就是講解在OKHTTP中如何配置緩存。

HTTP協(xié)議中緩存相關(guān)

為了更好的講解OKHTTP怎么設(shè)置緩存,我們追根溯源先從瀏覽器的緩存說(shuō)起,這樣后面的OKHTTP緩存內(nèi)容自然更加好理解。
我這部分內(nèi)容也是經(jīng)網(wǎng)絡(luò)上查閱,這一篇寫得很詳細(xì)瀏覽器 HTTP 協(xié)議緩存機(jī)制詳解。以下內(nèi)容基本出自于此文章。

緩存分類

http請(qǐng)求有服務(wù)端和客戶端之分。因此緩存也可以分為兩個(gè)類型服務(wù)端側(cè)和客戶端側(cè)。

服務(wù)端側(cè)緩存

常見(jiàn)的服務(wù)端有Ngix和Apache。服務(wù)端緩存又分為代理服務(wù)器緩存和反向代理服務(wù)器緩存。常見(jiàn)的CDN就是服務(wù)器緩存。這個(gè)好理解,當(dāng)瀏覽器重復(fù)訪問(wèn)一張圖片地址時(shí),CDN會(huì)判斷這個(gè)請(qǐng)求有沒(méi)有緩存,如果有的話就直接返回這個(gè)緩存的請(qǐng)求回復(fù),而不再需要讓請(qǐng)求到達(dá)真正的服務(wù)地址,這么做的目的是減輕服務(wù)端的運(yùn)算壓力。

客戶端側(cè)緩存

客戶端主要指瀏覽器(如IE、Chrome等),當(dāng)然包括我們的OKHTTPClient.客戶端第一次請(qǐng)求網(wǎng)絡(luò)時(shí),服務(wù)器返回回復(fù)信息。如果數(shù)據(jù)正常的話,客戶端緩存在本地的緩存目錄。當(dāng)客戶端再次訪問(wèn)同一個(gè)地址時(shí),客戶端會(huì)檢測(cè)本地有沒(méi)有緩存,如果有緩存的話,數(shù)據(jù)是有沒(méi)有過(guò)期,如果沒(méi)有過(guò)期的話則直接運(yùn)用緩存內(nèi)容。
而我們講的就是客戶端的緩存。
緩存中重要的概念

Cache-Control

Cache-Control是什么呢?先別急, 我們先用觀察一個(gè)結(jié)果,用Fiddler監(jiān)聽(tīng)瀏覽器訪問(wèn)http://blog.csdn.net/briblue。如下圖:

這里寫圖片描述
然后,查看服務(wù)端返回來(lái)的信息如下:

HTTP/1.1 200 OK
Server: openresty
Date: Mon, 24 Oct 2016 09:00:34 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
X-Powered-By: PHP 5.4.28
Content-Encoding: gzip

可以看到頭信息中有這么一行:
Cache-Control: private

Cache-control是由服務(wù)器返回的Response中添加的頭信息,它的目的是告訴客戶端是要從本地讀取緩存還是直接從服務(wù)器摘取消息。它有不同的值,每一個(gè)值有不同的作用。

  • max-age:這個(gè)參數(shù)告訴瀏覽器將頁(yè)面緩存多長(zhǎng)時(shí)間,超過(guò)這個(gè)時(shí)間后才再次向服務(wù)器發(fā)起請(qǐng)求檢查頁(yè)面是否有更新。對(duì)于靜態(tài)的頁(yè)面,比如圖片、CSS、Javascript,一般都不大變更,因此通常我們將存儲(chǔ)這些內(nèi)容的時(shí)間設(shè)置為較長(zhǎng)的時(shí)間,這樣瀏覽器會(huì)不會(huì)向?yàn)g覽器反復(fù)發(fā)起請(qǐng)求,也不會(huì)去檢查是否更新了。

  • s-maxage:這個(gè)參數(shù)告訴緩存服務(wù)器(proxy,如Squid)的緩存頁(yè)面的時(shí)間。如果不單獨(dú)指定,緩存服務(wù)器將使用max-age。對(duì)于動(dòng)態(tài)內(nèi)容(比如文檔的查看頁(yè)面),我們可告訴瀏覽器很快就過(guò)時(shí)了(max-age=0),并告訴緩存服務(wù)器(Squid)保留內(nèi)容一段時(shí)間(比如,s-maxage=7200)。一旦我們更新文檔,我們將告訴Squid清除老的緩存版本。

  • must-revalidate:這告訴瀏覽器,一旦緩存的內(nèi)容過(guò)期,一定要向服務(wù)器詢問(wèn)是否有新版本。

  • proxy-revalidate:proxy上的緩存一旦過(guò)期,一定要向服務(wù)器詢問(wèn)是否有新版本。

  • no-cache:不做緩存。

  • no-store:數(shù)據(jù)不在硬盤中臨時(shí)保存,這對(duì)需要保密的內(nèi)容比較重要。

  • public:告訴緩存服務(wù)器, 即便是對(duì)于不該緩存的內(nèi)容也緩存起來(lái),比如當(dāng)用戶已經(jīng)認(rèn)證的時(shí)候。所有的靜態(tài)內(nèi)容(圖片、Javascript、CSS等)應(yīng)該是public的。

  • private:告訴proxy不要緩存,但是瀏覽器可使用private cache進(jìn)行緩存。一般登錄后的個(gè)性化頁(yè)面是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。說(shuō)明服務(wù)器希望客戶端不要緩存消息,但是可以進(jìn)行private cache方法進(jìn)行緩存。這是因?yàn)?a target="_blank" rel="nofollow">http://blog.csdn.net/briblue是我的博客頁(yè)面,與用戶系統(tǒng)相關(guān),所以為了安全起見(jiàn),建議用private cache的方式緩存。
在OKHttp開(kāi)發(fā)中我們常見(jiàn)到的有下面幾個(gè):

  • max-age
  • no-cache
  • max-stale

expires

expires的效果等同于Cache-Control,不過(guò)它是Http 1.0的內(nèi)容,它的作用是告訴瀏覽器緩存的過(guò)期時(shí)間,在此時(shí)間內(nèi)瀏覽器不需要直接訪問(wèn)服務(wù)器地址直接用緩存內(nèi)容就好了。 expires最大的問(wèn)題在于如果服務(wù)器時(shí)間和本地瀏覽器相差過(guò)大的問(wèn)題。那樣誤差就很大。所以基本上用Cache-Control:max-age=多少秒的形式代替。

Last-Modified/If-Modified-Since

這個(gè)需要配合Cache-Control使用

  • Last-Modified:標(biāo)示這個(gè)響應(yīng)資源的最后修改時(shí)間。web服務(wù)器在響應(yīng)請(qǐng)求時(shí),告訴瀏覽器資源的最后修改時(shí)間。
  • If-Modified-Since:當(dāng)資源過(guò)期時(shí)(使用Cache-Control標(biāo)識(shí)的max-age),發(fā)現(xiàn)資源具有Last-Modified聲明,則再次向web服務(wù)器請(qǐng)求時(shí)帶上頭 If-Modified-Since,表示請(qǐng)求時(shí)間。web服務(wù)器收到請(qǐng)求后發(fā)現(xiàn)有頭If-Modified-Since 則與被請(qǐng)求資源的最后修改時(shí)間進(jìn)行比對(duì)。若最后修改時(shí)間較新,說(shuō)明資源又被改動(dòng)過(guò),則響應(yīng)整片資源內(nèi)容(寫在響應(yīng)消息包體內(nèi)),HTTP 200;若最后修改時(shí)間較舊,說(shuō)明資源無(wú)新修改,則響應(yīng)HTTP 304 (無(wú)需包體,節(jié)省瀏覽),告知瀏覽器繼續(xù)使用所保存的cache。

Etag/If-None-Match

這個(gè)也需要配合Cache-Control使用
Etag對(duì)應(yīng)請(qǐ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ù)器請(qǐng)求數(shù)據(jù)并添加In-None-Match消息頭,它的值就是之前Etag值。服務(wù)器通過(guò)Etag來(lái)定位資源文件,根據(jù)它是否更新的情況給瀏覽器返回200或者是304。
Etag機(jī)制比Last-Modified精確度更高,如果兩者同時(shí)設(shè)置的話,Etag優(yōu)先級(jí)更高。

Pragma

Pragma頭域用來(lái)包含實(shí)現(xiàn)特定的指令,最常用的是Pragma:no-cache。
在HTTP/1.1協(xié)議中,它的含義和Cache- Control:no-cache相同。
以上是Http中關(guān)于緩存的相關(guān)信息。接下來(lái)我們進(jìn)入主題,如何配置OkHttp的緩存。

OKHTTP之Cache

OKHTTP如果要設(shè)置緩存,首要的條件就是設(shè)置一個(gè)緩存文件夾,在Android中為了安全起見(jiàn),一般設(shè)置為私密數(shù)據(jù)空間。通過(guò)getExternalCacheDir()獲取。如然后通過(guò)調(diào)用OKHttpClient.Builder中的cache()方法。如下面代碼所示:

//緩存文件夾
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//緩存大小為10M
int cacheSize = 10 * 1024 * 1024;
//創(chuàng)建緩存對(duì)象
Cache cache = new Cache(cacheFile,cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();

設(shè)置好Cache我們就可以正常訪問(wèn)了。我們可以通過(guò)獲取到的Response對(duì)象拿到它正常的消息和緩存的消息。

Response的消息有兩種類型,CacheResponse和NetworkResponse。CacheResponse代表從緩存取到的消息,NetworkResponse代表直接從服務(wù)端返回的消息。示例代碼如下:

private void testCache(){
        //緩存文件夾
        File cacheFile = new File(getExternalCacheDir().toString(),"cache");
        //緩存大小為10M
        int cacheSize = 10 * 1024 * 1024;
        //創(chuàng)建緩存對(duì)象
        final Cache cache = new Cache(cacheFile,cacheSize);

        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient.Builder()
                        .cache(cache)
                        .build();
                //官方的一個(gè)示例的url
                String url = "http://publicobject.com/helloworld.txt";

                Request request = new Request.Builder()
                        .url(url)
                        .build();
                Call call1 = client.newCall(request);
                Response response1 = null;
                try {
                    //第一次網(wǎng)絡(luò)請(qǐng)求
                    response1 = call1.execute();
                    Log.i(TAG, "testCache: response1 :"+response1.body().string());
                    Log.i(TAG, "testCache: response1 cache :"+response1.cacheResponse());
                    Log.i(TAG, "testCache: response1 network :"+response1.networkResponse());
                    response1.body().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                Call call12 = client.newCall(request);

                try {
                    //第二次網(wǎng)絡(luò)請(qǐng)求
                    Response response2 = call12.execute();
                    Log.i(TAG, "testCache: response2 :"+response2.body().string());
                    Log.i(TAG, "testCache: response2 cache :"+response2.cacheResponse());
                    Log.i(TAG, "testCache: response2 network :"+response2.networkResponse());
                    Log.i(TAG, "testCache: response1 equals response2:"+response2.equals(response1));
                    response2.body().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

我們?cè)谏厦娴拇a中,用同一個(gè)url地址分別進(jìn)行了兩次網(wǎng)絡(luò)訪問(wèn),然后分別用Log打印它們的信息。

10-24 21:17:04.720 9901-17925/? I/SeniorActivity: testCache: response1 :
                                                                           \\           //
                                                                            \\  .ooo.  //
                                                                             .@@@@@@@@@.
                                                                           :@@@@@@@@@@@@@:
                                                                          :@@. '@@@@@' .@@:
                                                                          @@@@@@@@@@@@@@@@@
                                                                          @@@@@@@@@@@@@@@@@

                                                                     :@@ :@@@@@@@@@@@@@@@@@. @@:
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                          @@@@@@@@@@@@@@@@@
                                                                          '@@@@@@@@@@@@@@@'
                                                                             @@@@   @@@@
                                                                             @@@@   @@@@
                                                                             @@@@   @@@@
                                                                             '@@'   '@@'

                                                       :@@@.
                                                     .@@@@@@@:   +@@       `@@      @@`   @@     @@
                                                    .@@@@'@@@@:  +@@       `@@      @@`   @@     @@
                                                    @@@     @@@  +@@       `@@      @@`   @@     @@
                                                   .@@       @@: +@@   @@@ `@@      @@` @@@@@@ @@@@@@  @@;@@@@@
                                                   @@@       @@@ +@@  @@@  `@@      @@` @@@@@@ @@@@@@  @@@@@@@@@
                                                   @@@       @@@ +@@ @@@   `@@@@@@@@@@`   @@     @@    @@@   :@@
                                                   @@@       @@@ +@@@@@    `@@@@@@@@@@`   @@     @@    @@#    @@+
                                                   @@@       @@@ +@@@@@+   `@@      @@`   @@     @@    @@:    @@#
                                                    @@:     .@@` +@@@+@@   `@@      @@`   @@     @@    @@#    @@+
                                                    @@@.   .@@@  +@@  @@@  `@@      @@`   @@     @@    @@@   ,@@
                                                     @@@@@@@@@   +@@   @@@ `@@      @@`   @@@@   @@@@  @@@@#@@@@
                                                      @@@@@@@    +@@   #@@ `@@      @@`   @@@@:  @@@@: @@'@@@@@
                                                                                                       @@:
                                                                                                       @@:
                                                                                                       @@:
10-24 21:17:04.720 9901-17925/? I/SeniorActivity: testCache: response1 cache :null
10-24 21:17:04.720 9901-17925/? I/SeniorActivity: testCache: response1 network :Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
10-24 21:17:05.031 9901-17925/? I/SeniorActivity: testCache: response2 :
                                                                           \\           //
                                                                            \\  .ooo.  //
                                                                             .@@@@@@@@@.
                                                                           :@@@@@@@@@@@@@:
                                                                          :@@. '@@@@@' .@@:
                                                                          @@@@@@@@@@@@@@@@@
                                                                          @@@@@@@@@@@@@@@@@

                                                                     :@@ :@@@@@@@@@@@@@@@@@. @@:
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                     @@@ '@@@@@@@@@@@@@@@@@, @@@
                                                                          @@@@@@@@@@@@@@@@@
                                                                          '@@@@@@@@@@@@@@@'
                                                                             @@@@   @@@@
                                                                             @@@@   @@@@
                                                                             @@@@   @@@@
                                                                             '@@'   '@@'

                                                       :@@@.
                                                     .@@@@@@@:   +@@       `@@      @@`   @@     @@
                                                    .@@@@'@@@@:  +@@       `@@      @@`   @@     @@
                                                    @@@     @@@  +@@       `@@      @@`   @@     @@
                                                   .@@       @@: +@@   @@@ `@@      @@` @@@@@@ @@@@@@  @@;@@@@@
                                                   @@@       @@@ +@@  @@@  `@@      @@` @@@@@@ @@@@@@  @@@@@@@@@
                                                   @@@       @@@ +@@ @@@   `@@@@@@@@@@`   @@     @@    @@@   :@@
                                                   @@@       @@@ +@@@@@    `@@@@@@@@@@`   @@     @@    @@#    @@+
                                                   @@@       @@@ +@@@@@+   `@@      @@`   @@     @@    @@:    @@#
                                                    @@:     .@@` +@@@+@@   `@@      @@`   @@     @@    @@#    @@+
                                                    @@@.   .@@@  +@@  @@@  `@@      @@`   @@     @@    @@@   ,@@
                                                     @@@@@@@@@   +@@   @@@ `@@      @@`   @@@@   @@@@  @@@@#@@@@
                                                      @@@@@@@    +@@   #@@ `@@      @@`   @@@@:  @@@@: @@'@@@@@
                                                                                                       @@:
                                                                                                       @@:
                                                                                                       @@:
10-24 21:17:05.031 9901-17925/? I/SeniorActivity: testCache: response2 cache :Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
10-24 21:17:05.031 9901-17925/? I/SeniorActivity: testCache: response2 network :null
10-24 21:17:05.031 9901-17925/? I/SeniorActivity: testCache: response1 equals response2:false

打印的結(jié)果非常有意思是一個(gè)機(jī)器人和一個(gè)Okhttp的字符串。打印的結(jié)果主要說(shuō)明了一個(gè)現(xiàn)象,第一次訪問(wèn)的時(shí)候,Response的消息是NetworkResponse消息,此時(shí)CacheResponse的值為Null.而第二次訪問(wèn)的時(shí)候Response是CahceResponse,而此時(shí)NetworkResponse為空。也就說(shuō)明了上面的示例代碼能夠進(jìn)行網(wǎng)絡(luò)請(qǐng)求的緩存。
那么OKHTTP中的緩存就這么點(diǎn)內(nèi)容嗎?到此為至嗎?顯然不是。本篇文章開(kāi)頭講了大段的Http協(xié)議中的相關(guān)知識(shí)點(diǎn),貌似它們還沒(méi)有出現(xiàn)。
其實(shí)控制緩存的消息頭往往是服務(wù)端返回的信息中添加的如”Cache-Control:max-age=60”。所以,會(huì)有兩種情況。

  1. 客戶端和服務(wù)端開(kāi)發(fā)能夠很好溝通,按照達(dá)成一致的協(xié)議,服務(wù)端按照規(guī)定添加緩存相關(guān)的消息頭。
  2. 客戶端與服務(wù)端的開(kāi)發(fā)根本就不是同一家公司,沒(méi)有辦法也不可能要求服務(wù)端按照客戶端的意愿進(jìn)行開(kāi)發(fā)。

第一種辦法當(dāng)然很好,只要服務(wù)器在返回消息的時(shí)候添加好Cache-Control相關(guān)的消息便好。
第二種情況,就很麻煩,你真的無(wú)法左右別人的行為。怎么辦呢?好在OKHTTP能夠很輕易地處理這種情況。那就是定義一個(gè)攔截器,人為地添加Response中的消息頭,然后再傳遞給用戶,這樣用戶拿到的Response就有了我們理想當(dāng)中的消息頭Headers,從而達(dá)到控制緩存的意圖,正所謂移花接木。

緩存之?dāng)r截器

因?yàn)閿r截器可以拿到Request和Response,所以可以輕而易舉地加工這些東西。在這里我們?nèi)藶榈靥砑覥ache-Control消息頭。

class CacheInterceptor implements Interceptor{

        @Override
        public Response intercept(Chain chain) throws IOException {

            Response originResponse = chain.proceed(chain.request());

            //設(shè)置緩存時(shí)間為60秒,并移除了pragma消息頭,移除它的原因是因?yàn)閜ragma也是控制緩存的一個(gè)消息頭屬性
            return originResponse.newBuilder().removeHeader("pragma")
                    .header("Cache-Control","max-age=60").build();
        }
    }

定義好攔截器中后,我們可以添加到OKHttpClient中了。

private void testCacheInterceptor(){
        //緩存文件夾
        File cacheFile = new File(getExternalCacheDir().toString(),"cache");
        //緩存大小為10M
        int cacheSize = 10 * 1024 * 1024;
        //創(chuàng)建緩存對(duì)象
        final Cache cache = new Cache(cacheFile,cacheSize);

        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new CacheInterceptor())
                .cache(cache)
                .build();
        .......
}

代碼后面部分有省略。主要通過(guò)在OkHttpClient.Builder()中addNetworkInterceptor()中添加。而這樣也挺簡(jiǎn)單的,就幾步完成了緩存代碼。

攔截器進(jìn)行緩存的缺點(diǎn)

網(wǎng)上有人說(shuō)用攔截器進(jìn)行緩存是野路子,是HOOK行為。這個(gè)我不大同意,前面我有分析過(guò)情況,如果客戶端能夠同服務(wù)端一起協(xié)商開(kāi)發(fā),當(dāng)然以服務(wù)器控制的緩存消息頭為準(zhǔn),但問(wèn)題在于你沒(méi)法這樣做。所以,能夠解決問(wèn)題才是最實(shí)在的。

好了,回到正題。用攔截器控制緩存有什么不好的地方呢?我們先看看下面的情況。

  1. 網(wǎng)絡(luò)訪問(wèn)請(qǐng)求的資源是文本信息,如新聞列表,這類信息經(jīng)常變動(dòng),一天更新好幾次,它們用的緩存時(shí)間應(yīng)該就很短。
  2. 網(wǎng)絡(luò)訪問(wèn)請(qǐng)求的資源是圖片或者視頻,它們變動(dòng)很少,或者是長(zhǎng)期不變動(dòng),那么它們用的緩存時(shí)間就應(yīng)該很長(zhǎng)。

那么,問(wèn)題來(lái)了。 因?yàn)镺KHTTP開(kāi)發(fā)建議是同一個(gè)APP,用同一個(gè)OKHTTPCLIENT對(duì)象這是為了只有一個(gè)緩存文件訪問(wèn)入口。這個(gè)很容易理解,單例模式嘛。但是問(wèn)題攔截器是在OKHttpClient.Builder當(dāng)中添加的。如果在攔截器中定義緩存的方法會(huì)導(dǎo)致圖片的緩存和新聞列表的緩存時(shí)間是一樣的,這顯然是不合理的,這屬于一刀切,就像這兩天專家說(shuō)的要把年收入12萬(wàn)元的人群劃分為高收入人群而不區(qū)別北上廣深的房?jī)r(jià)物價(jià)情況。真實(shí)的情況不應(yīng)該是圖片請(qǐng)求有它的緩存時(shí)間,新聞列表請(qǐng)求有它的緩存時(shí)間,應(yīng)該是每一個(gè)Request有它的緩存時(shí)間。 那么,有解決的方案嗎? 有的,okhttp官方有建議的方法。

okhttp官方文檔建議緩存方法

okhttp中建議用CacheControl這個(gè)類來(lái)進(jìn)行緩存策略的制定。 它內(nèi)部有兩個(gè)很重要的靜態(tài)實(shí)例。

/**強(qiáng)制使用網(wǎng)絡(luò)請(qǐng)求*/
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

  /**
   * 強(qiáng)制性使用本地緩存,如果本地緩存不滿足條件,則會(huì)返回code為504
   */
  public static final CacheControl FORCE_CACHE = new Builder()
      .onlyIfCached()
      .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
      .build();

我們看到FORCE_NETWORK常量用來(lái)強(qiáng)制使用網(wǎng)絡(luò)請(qǐng)求。FORCE_CACHE只取本地的緩存。它們本身都是CacheControl對(duì)象,由內(nèi)部的Buidler對(duì)象構(gòu)造。下面我們來(lái)看看CacheControl.Builder

CacheControl.Builder

它有如下方法:

- noCache();//不使用緩存,用網(wǎng)絡(luò)請(qǐng)求
- noStore();//不使用緩存,也不存儲(chǔ)緩存
- onlyIfCached();//只使用緩存
- noTransform();//禁止轉(zhuǎn)碼
- maxAge(10, TimeUnit.MILLISECONDS);//設(shè)置超時(shí)時(shí)間為10ms。
- maxStale(10, TimeUnit.SECONDS);//超時(shí)之外的超時(shí)時(shí)間為10s
- minFresh(10, TimeUnit.SECONDS);//超時(shí)時(shí)間為當(dāng)前時(shí)間加上10秒鐘

知道了CacheControl的相關(guān)信息,那么它怎么使用呢?不同于攔截器設(shè)置緩存,CacheControl是針對(duì)Request的,所以它可以針對(duì)每個(gè)請(qǐng)求設(shè)置不同的緩存策略。比如圖片和新聞列表。下面代碼展示如何用CacheControl設(shè)置一個(gè)60秒的超時(shí)時(shí)間。

private void testCacheControl(){
        //緩存文件夾
        File cacheFile = new File(getExternalCacheDir().toString(),"cache");
        //緩存大小為10M
        int cacheSize = 10 * 1024 * 1024;
        //創(chuàng)建緩存對(duì)象
        final Cache cache = new Cache(cacheFile,cacheSize);

        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient.Builder()
                        .cache(cache)
                        .build();
                //設(shè)置緩存時(shí)間為60秒
                CacheControl cacheControl = new CacheControl.Builder()
                        .maxAge(60, TimeUnit.SECONDS)
                        .build();
                Request request = new Request.Builder()
                        .url("http://blog.csdn.net/briblue")
                        .cacheControl(cacheControl)
                        .build();

                try {
                    Response response = client.newCall(request).execute();

                    response.body().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

強(qiáng)制使用緩存

前面有講CacheControl.FORCE_CACHE這個(gè)常量。

public static final CacheControl FORCE_CACHE = new Builder()
      .onlyIfCached()
      .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
      .build();

它內(nèi)部其實(shí)就是調(diào)用onlyIfCached()和maxStale方法。
它的使用方法為

Request request = new Request.Builder()
            .url("http://blog.csdn.net/briblue")
            .cacheControl(Cache.FORCE_CACHE)
            .build();

但是如前面后提到的,如果緩存不符合條件會(huì)返回504.這個(gè)時(shí)候我們要根據(jù)情況再進(jìn)行編碼,如緩存不行就再進(jìn)行一次網(wǎng)絡(luò)請(qǐng)求。

Response forceCacheResponse = client.newCall(request).execute();
     if (forceCacheResponse.code() != 504) {
       // 資源已經(jīng)緩存了,可以直接使用
     } else {
       // 資源沒(méi)有緩存,或者是緩存不符合條件了。
     } 

不使用緩存

前面也有講CacheControl.FORCE_NETWORK這個(gè)常量。

public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

它的內(nèi)部其實(shí)是調(diào)用noCache()方法,也就是不緩存的意思。
它的使用方法為

Request request = new Request.Builder()
            .url("http://blog.csdn.net/briblue")
            .cacheControl(Cache.FORCE_NETWORK)
            .build();

還有一種情況將maxAge設(shè)置為0,也不會(huì)取緩存,直接走網(wǎng)絡(luò)。

Request request = new Request.Builder()
            .url("http://blog.csdn.net/briblue")
            .cacheControl(new CacheControl.Builder()
            .maxAge(0, TimeUnit.SECONDS))
            .build();

總結(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)行緩存的控制。

轉(zhuǎn)載至http://blog.csdn.net/briblue/article/details/52920531

Ref

http://www.cnblogs.com/l1pe1/archive/2010/07/14/1777621.html
http://www.cnblogs.com/whoislcj/p/5537640.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文就是講解在OKHTTP中如何配置緩存。 HTTP協(xié)議中緩存相關(guān) 為了更好的講解OKHTTP怎么設(shè)置緩存,我們追...
    TendaZhang閱讀 3,117評(píng)論 7 19
  • 1.OkHttp源碼解析(一):OKHttp初階 2 OkHttp源碼解析(二):OkHttp連接的"前戲"——H...
    隔壁老李頭閱讀 8,460評(píng)論 12 43
  • API定義規(guī)范 本規(guī)范設(shè)計(jì)基于如下使用場(chǎng)景: 請(qǐng)求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請(qǐng)求頻率非常高,建議使用雙通...
    有涯逐無(wú)涯閱讀 2,917評(píng)論 0 6
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 大家好,之前我們講解了Okhttp網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求相關(guān)的內(nèi)容,這一節(jié)我們講講數(shù)據(jù)緩存的處理。本節(jié)按以下內(nèi)容講解Okht...
    Ihesong閱讀 10,625評(píng)論 6 26

友情鏈接更多精彩內(nèi)容