OKHTTP緩存機制的學習

這幾天在封裝OKHttp框架,之前項目沒用到數據緩存,所以瞭解一下,現在分享一波,肯定有不正確的地方,希望大家可以糾正。

首先,一般有兩種緩存:服務器端緩存、客戶端緩存

一、概念

①服務器端緩存

服務端緩存又分爲代理服務器緩存和反向代理服務器緩存。常見的CDN就是服務器緩存。當瀏覽器重複訪問一張圖片地址時,CDN會判斷這個請求有沒有緩存,如果有的話就直接返回這個緩存的請求回覆,而不再需要讓請求到達真正的服務地址,這麼做的目的是減輕服務端的運算壓力。通俗的講,一般大點的項目,都會有多個緩存服務器,但是隻有一個源服務器,緩存服務器是根據模塊劃分的,客戶端請求接口是請求的各個緩存服務器,緩存服務器再去源服務器請求數據。

②客戶端緩存

當客戶端首次請求服務器接口時,如果服務器返回的數據正常,那麼客戶端將數據返回到本地,當客戶端再次訪問同一個地址時,客戶端會檢測本地有沒有緩存,如果有緩存的話,查看數據是否過期,如果沒有過期則直接用本地緩存數據。

二、適用場景

數據更新不頻繁的查詢操作,客戶端緩存可以減少訪問服務器次數,減少服務器壓力,並且無網絡狀態也可以顯示歷史數據;服務器端緩存,響應快,方便管理。

三、淺談HTTP知識

一般的響應頭:

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是由服務器返回的Response中添加的頭信息,它的目的是告訴客戶端是要從本地讀取緩存還是直接從服務器摘取消息。它有不同的值,每一個值有不同的作用。

max-age:這個參數告訴瀏覽器將頁面緩存多長時間,超過這個時間後纔再次向服務器發起請求檢查頁面是否有更新。對於靜態的頁面,比如圖片、CSS、Javascript,一般都不大變更,因此通常我們將存儲這些內容的時間設置爲較長的時間,這樣瀏覽器會不會向瀏覽器反覆發起請求,也不會去檢查是否更新了。
s-maxage:這個參數告訴緩存服務器(proxy,如Squid)的緩存頁面的時間。如果不單獨指定,緩存服務器將使用max-age。對於動態內容(比如文檔的查看頁面),我們可告訴瀏覽器很快就過時了(max-age=0),並告訴緩存服務器(Squid)保留內容一段時間(比如,s-maxage=7200)。一旦我們更新文檔,我們將告訴Squid清除老的緩存版本。
must-revalidate:這告訴瀏覽器,一旦緩存的內容過期,一定要向服務器詢問是否有新版本。
proxy-revalidate:proxy上的緩存一旦過期,一定要向服務器詢問是否有新版本。
no-cache:不做緩存。
no-store:數據不在硬盤中臨時保存,這對需要保密的內容比較重要。
public:告訴緩存服務器, 即便是對於不該緩存的內容也緩存起來,比如當用戶已經認證的時候。所有的靜態內容(圖片、Javascript、CSS等)應該是public的。
private:告訴proxy不要緩存,但是瀏覽器可使用private cache進行緩存。一般登錄後的個性化頁面是private的。
no-transform: 告訴proxy不進行轉換,比如告訴手機瀏覽器不要下載某些圖片。
max-stale指示客戶機可以接收超出超時期間的響應消息。如果指定max-stale消息的值,那麼客戶機可以接收超出超時期指定值之內的響應消息。

四、和客戶端有關的緩存設置(本文的核心)

在OKHttp開發中我們常見到的有下面幾個:

  • max-age

  • no-cache

  • max-stale

1.配置okhttp中的Cache文件目錄
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
File cacheFile = new File(content.getExternalCacheDir(),"tangnuer");
Cache cache = new Cache(cacheFile,1024*1024*50);
2.配置okhttp中的Cache

分爲兩種:

服務器支持緩存

如果服務器支持緩存,請求返回的Response會帶有這樣的Header:Cache-Control, max-age=xxx,這種情況下我們只需要手動給okhttp設置緩存就可以讓okhttp自動幫你緩存了。這裏的max-age的值代表了緩存在你本地存放的時間,可以根據實際需要來設置其大小。

使用緩存可以讓我們的app不用長時間地顯示令人厭煩的加載圈,提高了用戶體驗,而且還節省了流量,在數據更新不是很頻繁的地方使用緩存就非常有必要了。想要加入緩存不需要我們自己來實現,Okhttp已經內置了緩存,默認是不使用的,如果想使用緩存我們需要手動設置。

httpClientBuilder
    .cache(cache)
    .connectTimeout(20, TimeUnit.SECONDS)
    .readTimeout(20, TimeUnit.SECONDS)

服務器不支持緩存

如果服務器不支持緩存就可能沒有指定這個頭部,或者指定的值是如no-store等,但是我們還想在本地使用緩存的話要怎麼辦呢?這種情況下我們就需要使用Interceptor來重寫Respose的頭部信息,從而讓okhttp支持緩存。

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        Response response1 = response.newBuilder()
            .removeHeader("Pragma")
            .removeHeader("Cache-Control")
            //cache for 30 days
            .header("Cache-Control", "max-age=" + 3600 * 24 * 30)
            .build();
        return response1;
    }
}

然後將該Intercepter作爲一個NetworkInterceptor加入到okhttpClient中:

httpClientBuilder
    .addNetworkInterceptor(new CacheInterceptor())
    .cache(cache)
    .connectTimeout(20, TimeUnit.SECONDS)
    .readTimeout(20, TimeUnit.SECONDS)

設置好Cache我們就可以正常訪問了。我們可以通過獲取到的Response對象拿到它正常的消息和緩存的消息。
這樣我們就可以在服務器不支持緩存的情況下使用緩存了。

這裏我需要解釋幾個概念

Response的消息有兩種類型,CacheResponse和NetworkResponse。CacheResponse代表從緩存取到的消息,NetworkResponse代表直接從服務端返回的消息。

第一次訪問的時候,Response的消息是NetworkResponse消息,此時CacheResponse的值爲Null.而第二次訪問的時候Response是CahceResponse,而此時NetworkResponse爲空。

所以咱們的思路是:通過拿到NetworkResponse網絡數據,做緩存,剛纔這個方法其實原理就是:定義一個攔截器,人爲地添加Response中的消息頭,然後再傳遞給用戶,這樣用戶拿到的Response就有了我們理想當中的消息頭Headers,從而達到控制緩存的意圖,正所謂移花接木。

缺點:

  1. 網絡訪問請求的資源是文本信息,如新聞列表,這類信息經常變動,一天更新好幾次,它們用的緩存時間應該就很短。
  2. 網絡訪問請求的資源是圖片或者視頻,它們變動很少,或者是長期不變動,那麼它們用的緩存時間就應該很長。

okhttp官方文檔緩存方法

/**強制使用網絡請求*/
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

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

FORCE_NETWORK常量用來強制使用網絡請求。FORCE_CACHE只取本地的緩存。它們本身都是CacheControl對象,由內部的Buidler對象構造。

CacheControl.Builder

- noCache();//不使用緩存,用網絡請求
- noStore();//不使用緩存,也不存儲緩存
- onlyIfCached();//只使用緩存
- noTransform();//禁止轉碼
- maxAge(10, TimeUnit.MILLISECONDS);//設置超時時間爲10ms。
- maxStale(10, TimeUnit.SECONDS);//超時之外的超時時間爲10s
- minFresh(10, TimeUnit.SECONDS);//超時時間爲當前時間加上10秒鐘。

根據這些方法做一個動態的緩存機制。
我這邊的封裝是這樣的:

OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
if(isCache) {
   File cacheFile = new File(BaseApplication.getAppContext().getExternalCacheDir(),"ZhiBookCache");
   Cache cache = new Cache(cacheFile,1024*1024*50);
  Interceptor interceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
         Request request = chain.request();
         if (!SystemState.isNetConnected()) {
             request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build();
         }
         Response response = chain.proceed(request);
         if (SystemState.isNetConnected()) {
             int maxAge = 0 * 60;
             // 有網絡時 設置緩存超時時間0個小時
             response.newBuilder()
               .header("Cache-Control", "public, max-age=" + maxAge)
               .removeHeader("Pragma")// 清除頭信息,因爲服務器如果不支持,會返回一些干擾信息,不清除下面無法生效
               .build();
       } else {
            // 無網絡時,設置超時爲4周
            //int maxStale = 60 * 60 * 24 * 28;
             int maxStale = 0;
            response.newBuilder()
            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
            .removeHeader("Pragma")
            .build();
     }
      return response;
 }
 };
  httpClientBuilder.cache(cache)
 .addInterceptor(interceptor)
  .addNetworkInterceptor(interceptor);
 }

謝謝閱讀,我這裏也是學習的態度在這裏分享,有什麼問題希望大家能提出來能提出來,隨時可以和我交流探討:QQ:707086125 微信:loveme_dp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章