在Http協議中,緩存的控制是經過首部的Cache-Control來控制,經過對Cache-Control進行設置,便可實現不一樣的緩存策略。html
Cache-Control和其餘的首部字段同樣,使用key:value結構,同時value可有多個值, 值之間以,分隔(具體參考HTTP詳解)。Cache-Control是一個通用首部字段,在Http請求報文中可以使用,也可在應答報文中使用。java
更詳細內容可參考:Http首部字段定義segmentfault
瞭解了HTTP的理論知識,後面咱們對OkHttp中的緩存進行簡單的介紹。緩存
OkHttp默認對Http緩存進行了支持,只要服務端返回的Response中含有緩存策略,OkHttp就會經過CacheInterceptor攔截器對其進行緩存。可是OkHttp默認狀況下構造的HTTP請求中並無加Cache-Control,即使服務器支持了,咱們仍是不能正常使用緩存數據。因此須要對OkHttp的緩存過程進行干預,使其知足咱們的需求。服務器
OkHttp的優雅之處就在於使用了責任鏈模式,將請求-應答過程當中的每一步都經過一個攔截器來實現,並對此過程的頭部和尾部都提供了擴展,這也爲咱們干預緩存過程提供了可能。因此在實現緩存以前,咱們須要對OkHttp對攔截器的處理過程有個大概的瞭解。cookie
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest, this, eventListener); return chain.proceed(originalRequest); }
以上代碼就是整個攔截器的處理過程,具體的流程可參考源碼,這裏咱們只說一下基本的流程:發起請求時,會按interceptors中加入的順序依次執行,返回Response時按照逆序執行:網絡
自定義攔截器 <-> 內置攔截器(retryAndFollowUpInterceptor...ConnectInterceptor) <-> 網絡攔截器 <-> CallServerInterceptor
其中CallServerInterceptor就是負責發送請求與接收應答的攔截器。因爲咱們關注的只是緩存,因此只考慮內置攔截器中的CacheInterceptor。那麼流程可簡化爲:ide
Request <-> 自定義攔截器 <-> CacheInterceptor <-> 網絡攔截器 <-> Response
從這個流程能夠看出,若是服務端返回的Response中沒有Cache-Control, 那麼咱們可經過添加網絡攔截器來實現。一樣,在訪問緩存數據時,咱們可經過添加自定義攔截器來實現。ui
在開始添加緩存策略以前,咱們先了解一個完整的緩存策略:this
總體來講,在有網絡的狀況下,使用緩存仍是比較複雜,這裏咱們經過簡化版的緩存策略(有網絡時訪問服務器,無網絡時返回緩存數據)來演示OkHttp使用緩存的過程。
首先,咱們經過定義一個網絡攔截器來爲Response添加緩存策略:
public class HttpCacheInterceptor implements Interceptor { private Context context; public HttpCacheInterceptor(Context context) { this.context = context; } @Override public Response intercept(Chain chain) throws IOException { return chain.proceed(chain.request()).newBuilder() .request(newRequest) .removeHeader("Pragma") .header("Cache-Control", "public, max-age=" + 1) .build(); return response; } }
其次,經過自定義攔截器設置Request使用緩存的策略:
public class BaseInterceptor implements Interceptor { private Context mContext; public BaseInterceptor(Context context) { this.mContext = context; } @Override public Response intercept(Chain chain) throws IOException { if (NetworkUtil.isConnected(mContext)) { return chain.proceed(chain.request()); } else { // 若是沒有網絡,則返回緩存未過時一個月的數據 Request newRequest = chain.request().newBuilder() .removeHeader("Pragma") .header("Cache-Control", "only-if-cached, max-stale=" + 30 * 24 * 60 * 60); return chain.proceed(newRequest); } } }
Pragma是Http/1.1以前版本遺留的字段,用於作版本兼容,但不一樣的平臺對此有不一樣的實現,因此在使用緩存策略時須要將其屏蔽,避免對緩存策略形成影響。
將對修改Request和Response緩存策略的攔截器應用於OkHttp:
OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new BaseInterceptor(context)) .addNetworkInterceptor(new HttpCacheInterceptor(context)) .cache(new Cache(context.getCacheDir(), 20 * 1024 * 1024)) // 設置緩存路徑和緩存容量 .build();
接下來就能夠在無網絡的狀況下愉快地使用緩存數據了。
若是以爲OkHttp的緩存太複雜,想本身來緩存數據怎麼辦呢?有兩種方案來實現:
這種方案首先須要考慮應使用普通的攔截器仍是網絡攔截器,上面咱們已經瞭解了整個請求過程當中攔截器的執行順序,須要注意的是:在無網絡的狀況下,請求在執行到CacheIntercepter,若是沒有緩存數據,將會直接返回,並不會執行到自定義的網絡攔截器中,因此不適合在網絡攔截器中緩存數據。那麼咱們可經過自定義普通攔截器來實現,基本的過程以下:
@Override // BaseInterceptor.java public Response intercept(Chain chain) throws IOException { Response response = null; if (NetworkUtil.isConnected(mContext)) { response = chain.proceed(newRequest); saveCacheData(response); // 保存緩存數據 } else { // 不執行chain.proceed會打斷責任鏈,即後面的攔截器不會被執行 response = getCacheData(chain.request().url()); // 獲取緩存數據 } return response; }
OkHttp: 使用這種方案你良心不會痛嗎?
這種方案能夠說摒棄了OkHttp擴展攔截器這一強大的功能,直接與請求和應答進行交互,基本的過程以下:
Request request = new Request.Builder() .url(realUrl) .build(); if (NetworkUtil.isConnected()) { httpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { // 返回緩存數據 } @Override public void onResponse(Response response) throws IOException { // 1. 緩存數據 // 2. 返回請求結果 } }); } else { // 返回緩存數據 }
這兩種方案都拋棄了OkHttp本身實現的緩存策略,因此更加靈活,尤爲是監聽OkHttp請求過程這種方法。但也都有一個很大的缺點:須要實現一個緩存模塊。在開發中具體使用哪一種緩存策略,根據已有代碼模塊和需求衡量便可。
@Override // BaseInterceptor.java public Response intercept(Chain chain) throws IOException { // 添加公共參數 HttpUrl.Builder urlBuilder = chain.request().url().newBuilder() .addQueryParameter("a", "a") .addQueryParameter("b", "b"); Request.Builder requestBuilder = chain.request().newBuilder(); if (NetworkUtil.isConnected(mContext)) { urlBuilder.addQueryParameter("network", NetworkUtil.getNetwokType(mContext)); } else { // 無網絡時不添加可變的公共參數 requestBuilder.removeHeader("Pragma") .header("Cache-Control", "only-if-cached, max-stale=" + 30 * 24 * 60 * 60); } Request newRequest = requestBuilder .url(urlBuilder.build()) .build(); return chain.proceed(newRequest); } @Override // HttpCacheInterceptor.java public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); HttpUrl newUrl = chain.request().url().newBuilder() .removeAllQueryParameters("network") .build(); // 緩存數據前刪除可變的公共參數 Request newRequest = chain.request().newBuilder() .url(newUrl) .build(); return response.newBuilder() .request(newRequest) .removeHeader("Pragma") .header("Cache-Control", "public, max-age=" + 1) .build(); }