這幾天在使用OKHttp框架作WebView數據緩存,找到一篇好文,分享一波。php
首先,通常有兩種緩存:服務器端緩存、客戶端緩存css
<h5>①服務器端緩存</h5>html
服務端緩存又分爲代理服務器緩存和反向代理服務器緩存。常見的CDN就是服務器緩存。當瀏覽器重複訪問一張圖片地址時,CDN會判斷這個請求有沒有緩存,若是有的話就直接返回這個緩存的請求回覆,而再也不須要讓請求到達真正的服務地址,這麼作的目的是減輕服務端的運算壓力。通俗的講,通常大點的項目,都會有多個緩存服務器,可是隻有一個源服務器,緩存服務器是根據模塊劃分的,客戶端請求接口是請求的各個緩存服務器,緩存服務器再去源服務器請求數據。java
<h5>②客戶端緩存</h5>瀏覽器
當客戶端首次請求服務器接口時,若是服務器返回的數據正常,那麼客戶端將數據返回到本地,當客戶端再次訪問同一個地址時,客戶端會檢測本地有沒有緩存,若是有緩存的話,查看數據是否過時,若是沒有過時則直接用本地緩存數據。緩存
數據更新不頻繁的查詢操做,客戶端緩存能夠減小訪問服務器次數,減小服務器壓力,而且無網絡狀態也能夠顯示歷史數據;服務器端緩存,響應快,方便管理。服務器
通常的響應頭:網絡
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: privateapp
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
<h5>1.配置okhttp中的Cache文件目錄 </h5>
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
File cacheFile = new File(content.getExternalCacheDir(),"tangnuer");
Cache cache = new Cache(cacheFile,1024*1024*50);
複製代碼
<h5>2.配置okhttp中的Cache</h5>
分爲兩種:
若是服務器支持緩存,請求返回的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,從而達到控制緩存的意圖,正所謂移花接木。
缺點:
/**強制使用網絡請求*/
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);
}複製代碼