OKHttp3 系列 — 攔截器的使用

2018年4月17日更新
在看okthttp3源碼的時候發現,這邊文章最底下的總結內容中的第一點:
無網絡請求下,okhttp不會走入攔截器中,所以在這里面編寫無網絡代碼邏輯是無效的;
是錯誤的結論,因為攔截器是鏈式執行的,所以每個攔截器中的chain.proceed(request);方法其實是在調用執行下一個攔截器的intercept()方法,于是攔截器中返回的response就是從最底層攔截器開始一層一層的進行封裝,然后原路返回到最上一層攔截器中。所以沒有網絡的情況下還是會執行攔截器,只是說,我打印日志的位置因為在chain.proceed(request);方法后所以沒有任何打印結果,因為執行這里的時候,沒有網絡就已經報了網絡異常。

在此,如給大家造成了誤解,見諒見諒!


最近實在太忙,所以影響了更新速度...

添加Interceptor

在上一篇中我們已經知道了okhttp的基本使用,其中在介紹OkHttpClient初始化的時候,介紹了兩種方式,第二種方式就可以對這個OkHttpClient對象設置攔截器,如下所示:

// 配置一些信息進入OkHttpClient
mOkHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .readTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .writeTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .addInterceptor(new LoggerInterceptor())
                .build();

如上代碼,很簡單,只要利用addInterceptor方法就可以添加攔截器,而自定義的攔截器只需要實現Interceptor接口就行了,如下所示:

public class LoggerInterceptor implements Interceptor {
      ...
}

應用場景

日志打印

可以使用攔截器方便的打印網絡請求時,需要查看的日志。如下所示:

public class LoggerInterceptor implements Interceptor {

    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        // 攔截請求,獲取到該次請求的request
        Request request = chain.request();
        // 執行本次網絡請求操作,返回response信息
        Response response = chain.proceed(request);
        if (Configuration.DEBUG) {
            for (String key : request.headers().toMultimap().keySet()) {
                LogUtil.e("zp_test", "header: {" + key + " : " + request.headers().toMultimap().get(key) + "}");
            }
            LogUtil.e("zp_test", "url: " + request.url().uri().toString());
            ResponseBody responseBody = response.body();

            if (HttpHeaders.hasBody(response) && responseBody != null) {
                BufferedReader bufferedReader = new BufferedReader(new
                        InputStreamReader(responseBody.byteStream(), "utf-8"));
                String result;
                while ((result = bufferedReader.readLine()) != null) {
                    LogUtil.e("zp_test", "response: " + result);
                }
                // 測試代碼
                responseBody.string();
            }
        }
        // 注意,這樣寫,等于重新創建Request,獲取新的Response,避免在執行以上代碼時,
        // 調用了responseBody.string()而不能在返回體中再次調用。
        return response.newBuilder().build();
    }

}

做了一個打印驗證:通過分別打印攔截器與返回體的時間和線程名字,可以知道這兩者處于同一線程中,增加攔截器,請求執行的時間也會增加,所以猜測,其實就是線性的在執行不同攔截器中的代碼,根據需求返回一個相同的或者新的response。

緩存

想要實現緩存,先在創建okhttpclint的時候多加一行代碼.cache(),通過它來設置緩存目錄,當然需要服務器支持緩存功能。

 mOkHttpClient = new OkHttpClient().newBuilder()
                .cache(new Cache(FileUtils.getCacheDirectory(AppApplication
                        .getApplication(), ""), 1024 * 1024))
                .connectTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .readTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .writeTimeout(REQUEST_TIME, TimeUnit.SECONDS)
                .addNetworkInterceptor(new LoggerInterceptor())
                .build();

如果服務器端支持緩存的話,則請求所返回的Response會帶有這樣的頭信息header:cache-control, max-age=xxx,這樣設置。這時可以直接使用緩存功能。其中,max-age設置的緩存時間,過了這個時間,就算有緩存也不會進行使用。

像我公司服務器返回的頭信息中與緩存相關的字段如下:
header: {cache-control : [no-store, private]}
header: {pragma : [no-cache]}

這就說明,服務器默認是不支持緩存的,okhttp就不會對此次請求進行緩存。為了讓okhttp緩存此次響應,就必須重新設置response的請求頭信息。

接下來再看攔截器中如何設置緩存請求頭信息。

public class LoggerInterceptor implements Interceptor {

    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        // 攔截請求,獲取到該次請求的request
        Request request = chain.request();
        // 執行本次網絡請求操作,返回response信息
        Response response = chain.proceed(request);
        if (Configuration.DEBUG) {
            for (String key : request.headers().toMultimap().keySet()) {
                LogUtil.e("zp_test", "header: {" + key + " : " + request.headers().toMultimap().get(key) + "}");
            }
            LogUtil.e("zp_test", "url: " + request.url().uri().toString());
            ResponseBody responseBody = response.body();
        }
      
        return response.newBuilder()
            // 增加一個緩存頭信息,緩存時間為60s
            .header("cache-control", "public, max-age=60")
             // 移除pragma頭信息
            .removeHeader("pragma")
            .build();
    }

}

這樣設置,就等于是在60s內強制設置使用緩存。

注意點:
切記,最開始,我一直在犯一個錯誤,okhttp3不能緩存post接口

攔截器可以理解為,給請求的request和response重新一次封裝的機會,使得你可以在特定條件下,給一些特定的接口或者滿足特定條件的接口一些特殊的操作。

比如有一種場景,有網絡時,進行請求,無網絡時,拿緩存數據。先看網上的一種方法。

if (NetUtils.isNetAvailable(AppApplication.getApplication())) {  
      response.newBuilder()  
               .header("Cache-Control", "public, max-age=" + 0)  
               .removeHeader("Pragma")  
               .build();  
 } else {  
      int maxStale = 60 * 60 * 24; // 無網絡時,設置超時為1天
      response.newBuilder()  
              .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)  
              .removeHeader("Pragma")  
              .build();  
 } 

return response;

max-stale:在max-age指定的失效時間外,額外增加一段指定的時間可以使用失效的response。

網上有很多是上面這種做法,但是,我在攔截器中試了一下,當沒有網絡時,壓根就不會走入攔截器。(我使用的是網絡攔截器,如果有是別的什么原因,歡迎指出錯誤)

最終解決方案是在初始化request(如果初始化不熟悉可以參考我的上一篇文章OKHttp3的基本使用)的時候進行的判斷操作,當有網絡時初始化正常的request,當沒有網絡時初始化強制使用緩存的request:

Request request;
if (NetUtils.isNetAvailable(AppApplication.getApplication())) {
      request = addHeaderInfo().url(requestUrl).build();
} else {
      request = addHeaderInfo().url(requestUrl).cacheControl(CacheControl.FORCE_CACHE).build();
}

攔截器還是使用上面的那種形式,只是將有效時間變成了0,主要是為了在有網絡情況下每次都請求最新的數據。

response.newBuilder()  
             .header("Cache-Control", "public, max-age=" + 0)  
             .removeHeader("Pragma")  
             .build();  

這樣就可以在有網絡的情況下使用最新的數據,在無網絡的情況下使用緩存數據。

總結

第一點,無網絡請求下,okhttp不會走入攔截器中,所以在這里面編寫無網絡代碼邏輯是無效的;
第二點,無網絡情況下,通過給request設置一個強制緩存標志:CacheControl.FORCE_CACHE來告訴okhttp本次請求走緩存,并且還得在之前的網絡請求中已經緩存了數據到本地。如果此時沒有命中緩存文件,則會報504;
第三點,有網絡情況下,咱們可以利用攔截器和服務器的緩存策略進行動態配合。

最后歡迎大家提問一起討論,也歡迎指出文中錯誤,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。