摘要:如表示可接受過期小時內的數據表示指定時間內的緩存數據仍有效,與緩存是否過期無關。所以需要對的緩存過程進行干預,使其滿足我們的需求。將對修改和緩存策略的攔截器應用于設置緩存路徑和緩存容量接下來就可以在無網絡的情況下愉快地使用緩存數據了。
HTTP緩存
在Http協議中,緩存的控制是通過首部的Cache-Control來控制,通過對Cache-Control進行設置,即可實現不同的緩存策略。
Cache-Control和其他的首部字段一樣,使用key:value結構,同時value可有多個值, 值之間以,分隔(具體參考HTTP詳解)。Cache-Control是一個通用首部字段,在Http請求報文中可使用,也可在應答報文中使用。
請求指令集(在請求報文中的取值):no-cache: 不要緩存數據,直接從源服務器獲取數據;
no-store: 不緩存請求或響應的任何內容;
max-age: 表示可接受過期過久的緩存數據,同指定了參數的max-stale;
max-stale: 表示接收過期的緩存,如后面未指定參數,則表示永遠接收緩存數據。如max-stale: 3600, 表示可接受過期1小時內的數據;
min-fresh: 表示指定時間內的緩存數據仍有效,與緩存是否過期無關。如min-fresh: 60, 表示60s內的緩存數據都有效,60s之后的緩存數據將無效。
only-if-cache: 表示直接獲取緩存數據,若沒有數據返回,則返回504(Gateway Timeout)
應答指令集(在應答報文中的取值):public: 可向任一方提供緩存數據;
private: 只向指定用戶提供緩存數據;
no-cache: 緩存前需確認其有效性;
no-store: 不緩存請求或響應的任何內容;
max-age: 表示緩存的最大時間,在此時間范圍內,訪問該資源時,直接返回緩存數據。不需要對資源的有效性進行確認;
must-revalidate: 訪問緩存數據時,需要先向源服務器確認緩存數據是否有效,如無法驗證其有效性,則需返回504。需要注意的是:如果使用此值,則max-stale將無效。
更詳細內容可參考:Http首部字段定義
了解了HTTP的理論知識,后面我們對OkHttp中的緩存進行簡單的介紹。
OkHttp攔截器OkHttp默認對Http緩存進行了支持,只要服務端返回的Response中含有緩存策略,OkHttp就會通過CacheInterceptor攔截器對其進行緩存。但是OkHttp默認情況下構造的HTTP請求中并沒有加Cache-Control,即便服務器支持了,我們還是不能正常使用緩存數據。所以需要對OkHttp的緩存過程進行干預,使其滿足我們的需求。
OkHttp的優雅之處就在于使用了責任鏈模式,將請求-應答過程中的每一步都通過一個攔截器來實現,并對此過程的頭部和尾部都提供了擴展,這也為我們干預緩存過程提供了可能。所以在實現緩存之前,我們需要對OkHttp對攔截器的處理過程有個大概的了解。
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. Listinterceptors = 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。那么流程可簡化為:
Request <-> 自定義攔截器 <-> CacheInterceptor <-> 網絡攔截器 <-> Response
從這個流程可以看出,如果服務端返回的Response中沒有Cache-Control, 那么我們可通過添加網絡攔截器來實現。同樣,在訪問緩存數據時,我們可通過添加自定義攔截器來實現。
使用OkHttp緩存在開始添加緩存策略之前,我們先了解一個完整的緩存策略:
整體來說,在有網絡的情況下,使用緩存還是比較復雜,這里我們通過簡化版的緩存策略(有網絡時訪問服務器,無網絡時返回緩存數據)來演示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的緩存如果覺得OkHttp的緩存太復雜,想自己來緩存數據怎么辦呢?有兩種方案來實現:
自定義攔截器,
監聽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: 使用這種方案你良心不會痛嗎?
這種方案可以說摒棄了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請求過程這種方法。但也都有一個很大的缺點:需要實現一個緩存模塊。在開發中具體使用哪種緩存策略,根據已有代碼模塊和需求衡量即可。
注意點對Response的緩存策略進行修改的攔截器一定要應用于網絡攔截器,否則無法緩存數據,因為在Response返回的過程中,普通的攔截器在內置的CacheInterceptor之后執行;
修改Response的Cache-Control時,max-Age不能太大,否則你將在指定的max-Age時間內訪問的始終是緩存數據(即便是有網的情況下);
實際的開發過程中,我們在網絡請求中會添加一些公共參數,對于一些可變的公共參數,在緩存數據和訪問緩存數據的過程中需要刪除,比如網絡類型,有網絡時其值為Wifi或4G等,無網絡時可能為none, 這時訪問緩存時就會因url不一致導致訪問緩存失敗。
@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(); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/61922.html
摘要:若攔截事件返回為,表示攔截,事件不會向下層的或者傳遞,表示不攔截,繼續分發事件。五注冊反注冊未成對使用引起的內存泄漏。七集合對象沒有及時清理引起的內存泄漏。 原文鏈接:https://blog.csdn.net/wen_hah... 版權聲明:本文為博主原創文章,轉載請附上博文鏈接! 前言 金三銀四到來了,找工作的好時候到了,小伙伴們是不是都在忙著找工作呢,小弟前一陣也是忙著在找工作,...
閱讀 2310·2021-11-22 12:01
閱讀 1983·2021-11-12 10:34
閱讀 4508·2021-09-22 15:47
閱讀 2827·2019-08-30 15:56
閱讀 2861·2019-08-30 15:53
閱讀 2398·2019-08-30 13:53
閱讀 3371·2019-08-29 15:35
閱讀 3119·2019-08-29 12:27