天天看點

OkHttp3之緩存應用

1、概論

在上一篇文章裡面,我們詳細的剖析了HTTP協定的緩存機制。但那主要是從伺服器端進行分析的,這有助于我們了解HTTP的緩存機制,并為我們用好OkHttp3這一用戶端的封裝庫提供更為清晰的思路。知其原理,才能事半功倍。如果對于HTTP協定的緩存機制還不是很不清楚,可以去看下上一篇部落格HTTP協定進階之緩存

本篇文章主要從用戶端的緩存控制出發,探讨如何利用OkHttp3是如何對緩存進行控制的。

2、請求封包的Cache-Control

在寫代碼之前,我們先通過下表大概回顧一下HTTP請求封包中與緩存相關的首部字段Cache-Control中的值都有哪些,它們的作用分别都是什麼,它們的詳細解釋在上一篇部落格HTTP協定進階之緩存中:

指令 目的
max-stale=s 在s時間段内,文檔不會過期。該指令放松了緩存的規則
min-fresh=s 至少在未來的s秒内文檔要保持新鮮。這使緩存規則更加嚴格了
max-age=s 緩存無法傳回緩存時間長于s秒的文檔。這條指令會使規則更加嚴格,除非同時還發送了max-stale指令,在這種情況下,使用期可能會超過其過期時間。
no-cache 除非資源進行了再驗證,否則這個用戶端不會接受已緩存的資源
no-store 緩存應該盡快從存儲器中删除文檔的所有痕迹,因為其中可能會包含敏感資訊
only-if-cached 隻有當緩存中有副本存在時,用戶端才會擷取一份副本

注:參見《HTTP權威指南》第194頁,表中沒有包括相容性的Progma: no-cache

3、OkHttp3緩存簡單使用

OkHttp3中關于緩存的類,我們使用的最多的是:Cache類和ControlCache類,其中前者用于指定緩存的位址和大小,後者用于對緩存進行各種控制,ControlCache又有其建構者類Builder。

先利用Cache類定義緩存位址和緩存最大尺寸:

long maxCacheSize =  *  * ;
Cache cache = new Cache(
        new File("E:/Soft_Develop/iMooc/NetworkFrameDesign/Test"),
        maxCacheSize);
           

然後将其注入到OkHttpClient執行個體中:

接着,建構我們的請求封包Request,我們使用鳳凰網首頁的網址,因為鳳凰網首頁的伺服器支援緩存:

Request request = new Request.Builder()
                .url("http://www.ifeng.com")
                .build();
           

為了驗證緩存是有效的,我們進行兩次拉取,分别使用Response類的networkResponse()方法和cacheResponse()方法來檢視從網絡或緩存中讀取到的内容情況:

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
Response response2 = client.newCall(request).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());
           

控制台輸出的響應如下:

OkHttp3之緩存應用

由結果可知,由于在第一次請求時,緩存位址中還沒有響應緩存,是以是從網絡拉取資料,而第二次因為緩存位址中已有資料,是以是從緩存中拉取資料。

需要注意的是,在執行完請求之後,Response的Body需要關閉(Body的string()方法内嵌了關閉功能),否則緩存将不會如期生效,比如我們将其中關閉主體的兩行去掉,那麼結果将不會從緩存中讀取:

OkHttp3之緩存應用

我們到緩存位址中檢視緩存檔案,發現有三個:

OkHttp3之緩存應用

我們檢視d0dae*.0這個檔案可:

OkHttp3之緩存應用

它的内容我們是熟悉的,它裡面包含了幾個與緩存相關的字段,該檔案所包含的就是緩存中存儲的響應頭。而主體就是對應的d0dae*.1這個檔案了

journal檔案這裡暫且不管。

4、OkHttp3控制緩存

上一節說到,OkHttp3控制緩存的類為CacheControl。這一小節具體講講怎麼使用該類。

要對緩存進行控制,我們需要在建立Request執行個體的時候就為其注入相應的緩存控制機制。這個注入是通過Request的cacheControl(CacheControl )方法實作的。例如,加入我不想存儲緩存,而是直接從伺服器拉取,并且不儲存緩存。要實作這樣的功能,HTTP是通過首部字段Cache-Control: no-store實作的。使用OkHttp3我們隻需要在構造Request執行個體時按如下方式增加一行代碼:

Request request = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noStore().build())
        .build();
           

浏覽CacheControl.Builder類的方法,我們發現和第2節的表格高度對應:

OkHttp3之緩存應用

針對這些方法,OkHttp3 API也給了我們這樣兩條建議:

(1)強制從網絡擷取資源

  • 如果想要跳過緩存直接從網絡中擷取資源,可以通過noCache()方法;
  • 如果需要每次請求都進行再驗證環節,如果驗證通過還是使用緩存,那麼可以使用maxAge(0, TimeUnit.SECONDS)來建構CacheControl。

(2)強制從緩存擷取網絡資源

  • 可以使用onlyIfCached()方法,使用該方法,如果緩存中沒有時,将傳回504 Unsatisfiable Request;
  • 也可以采用maxStale(365, TimeUnit.DAYS),這樣我們就使用了一個很長的緩存放松時間。

5、no-cache, no-store以及max-age=0辨析

上一節,我們使用了no-cache, no-store和max-age=0來強制和伺服器進行溝通,但他們之間是有差別的,尤其是no-cache和max-age=0之間,更是令我費解了好一陣子。這裡我們就來辨析他們的不同,好在這裡我們通過OkHttp3可以很自由的變動我們的請求首部字段,也更容易觀察現象。

按照上一章的了解(我參照的是《HTTP權威指南》和RFC7234),no-cache的意思是,在每次請求緩存時需要經過再驗證。而OkHttp3給我們的建議中卻說no-cache是直接從伺服器拉取資料,這樣便和再驗證機制沒什麼關系了。

我原先來了解是,no-cache的作用和max-age=0的作用是一樣的。但是事實驗證不是如此。

我們通過如下實驗來說明他們究竟說明的是什麼?

5.1、no-cache和max-age=0的差別

首先,我們建立兩個URL一樣的請求,但第二個請求我們使用no-cache,代碼如下:

Request request = new Request.Builder()
                .url("http://www.ifeng.com")
                .build();

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");

Request request2 = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noCache().build())
        .build();

Response response2 = client.newCall(request2).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());
           

結果為:

OkHttp3之緩存應用

可以看出,第二次也是直接從網絡讀取,而不會從緩存中讀取。如果将第二個請求中的noCache()換成maxAge(0, TimeUnit.SECONDS)。結果為:

OkHttp3之緩存應用

可以清楚的看到,這次在第二次請求中,再驗證機制起作用了,從伺服器傳回了304 Not Modified,然後再向緩存發起請求,并從響應傳回了副本。

由此可見no-cache和max-age=0差別在于,前者直接從伺服器拉取資料,後者使用了再驗證機制。

RFC 2616中的一段話為這一結論帶來了理論基礎:

OkHttp3之緩存應用

用通俗的話來說,max-age=0的功能是重新整理而no-cache的功能是重新加載。使用chrome浏覽器打開www.ifeng.com,然後利用開發者工具來抓取封包。當我們按F5時,請求封包部分如下:

OkHttp3之緩存應用

而當我們按CTRL+F5時,請求封包部分如下:

OkHttp3之緩存應用

是以可以得出結論max-age=0等于F5(重新整理),no-cache等于CTRL+F5(重載)。

5.2、no-cache和no-store的差別

我們隻使用一個Request請求,首先給它指定緩存控制為no-cache:

Request request = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noCache().build())
        .build();

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
           

檢視緩存位址中産生了緩存檔案,但是當把noCache()換成noStore()之後沒有産生緩存檔案。

由此可見,max-age=0和no-cache的主要差別在于是否進行再驗證。而no-cache和no-store的差別在于是否會緩存副本。

6、總結

本文主要從應用層面探讨了OkHttp3使用緩存時的基本用法,還有一些比較弄混的問題。但總體說來,我還是沒有太弄清楚RFC 7243和《HTTP權威指南》中關于請求封包中的no-cache中的解釋為什麼會和再驗證有關。如果有人看到這篇文章,并且知道為什麼,還請指點一二,萬分感激!

參考資料

《HTTP權威指南》

RFC 2616