1. 程式人生 > >android OkHttp攔截器(Interceptor)的使用

android OkHttp攔截器(Interceptor)的使用

一 概念

    在OkHttp內部是使用攔截器來完成請求和響應,利用的是責任鏈設計模式,可以用來轉換,重試,重寫請求的機制。現在主流的網路框架非Retrofit莫屬,它的內部請求也是基於OkHttp的。在每一個攔截器中,一個關鍵部分就是使用chain.proceed(request)發起請求。這個簡單的方法就是所有Http工作發生的地方,生成和請求對應的響應。

    多個攔截器可以連結使用。假設一個壓縮攔截器和一個校驗和攔截器:需要決定資料是否先被壓縮,然後校驗或者先校然後再壓縮。OkHttp使用列表來跟蹤攔截器,並按順序呼叫攔截器。

如上圖所示,就是OkHttp中資料流的傳輸方向,裡面包含了兩種攔截器

  • Application Interceptors應用程式攔截器
  • Network Interceptors網路攔截器。

二 分類

1.應用攔截器通過下面兩種方式註冊的為應用攔截器:
//方式一:在OkHttpClient.Builder中新增
new OkHttpClient.Builder().addInterceptor(interceptor)


//方式二:在okHttpClient中直接新增
okHttpClient.interceptors().add(interceptor)

         主要用於檢視請求資訊及返回資訊,如連結地址、頭資訊、引數資訊等,如下面的log-攔截器定義:

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request();

    long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s", request.url(), 
chain.connection(), request.headers()));

    Response response = chain.proceed(request);
    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}

2.網路攔截器

通過下面兩種方式註冊的為網路攔截器:

//方式一:在OkHttpClient.Builder中新增
new OkHttpClient.Builder().addNetworkInterceptor(interceptor)

//方式二:在okHttpClient中直接新增
okHttpClient. networkInterceptors().add(interceptor)

       可以新增、刪除或替換請求頭資訊,還可以改變的請求攜帶的實體。

   1)新增請求頭,假設後臺要求我們請求API介面時,要在每一個介面請求頭上新增對應的Token。使用Retrofit的話可能條件反射出以下程式碼:

@FormUrlEncoded
@POST("/mobile/login.htm")
Call<ResponseBody> login(@Header("token") String token, @Field("mobile") String phoneNumber, @Field("smsCode") String smsCode);
這樣寫是可以,但是如果介面很多的話每一個都需要傳入token,重複很多遍,很容易導致程式碼的冗餘。    下面使用攔截器就可以一勞永逸了,程式碼如下:
public class TokenHeaderInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        // get token
        String token = AppService.getToken();
        Request originalRequest = chain.request();
        // get new request, add request header
        Request updateRequest = originalRequest.newBuilder()
                .header("token", token)
                .build();
        return chain.proceed(updateRequest);
    }
    先攔截得到originalRequest,然後利用originalRequest生成新的updateRequest,再交給chain處理進行下一環。最後在OkhttpClient中使用:
OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new TokenHeaderInterceptor())
                .build();

Retrofit retrofit = new Retrofit.Builder().baseUrl(BuildConfig.BASE_URL)
                .client(client).addConverterFactory(GsonConverterFactory.create()).build();
2)修改請求體假設有以下需求:在上面login介面基礎上,後臺要求我們傳過去的請求引數要按照一定規則經過加密的。
規則如下:
  • 請求引數名統一為content;
  • content值:JSON 格式的字串經過 AES 加密後的內容;
舉個例子,根據上面login介面,現有
{"mobile":"157xxxxxxxx", "smsCode":"xxxxxx"}
Json字串,然後再將其加密。最後以content = [加密後json字串]方式傳送給後臺。看完了上面的 TokenHeaderInterceptor 之後,這需求對於我們來說可以算是信手拈來:
public class RequestEncryptInterceptor implements Interceptor {
    private static final String FORM_NAME = "content";
    private static final String CHARSET = "UTF-8";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //獲取請求體 
        RequestBody body = request.body();
        if (body instanceof FormBody) {
            FormBody formBody = (FormBody) body;
            Map<String, String> formMap = new HashMap<>();
            // 從 formBody 中拿到請求引數,放入 formMap 中 
            for (int i = 0; i < formBody.size(); i++) {
                formMap.put(formBody.name(i), formBody.value(i));
            }
            // 將 formMap通過Gson.toJson() 轉化為 json 然後 AES 加密 
            Gson gson = new Gson();
            String jsonParams = gson.toJson(formMap);
            String encryptParams = AESCryptUtils.encrypt(jsonParams.getBytes(CHARSET), AppConstant.getAESKey());
            // 重新修改 body 的內容 
            body = new FormBody.Builder().add(FORM_NAME, encryptParams).build();
        }
        // 若請求體不為Null,重新構建post請求,並傳入修改後的引數體 
        if (body != null) {
            request = request.newBuilder().post(body).build();
        }
        return chain.proceed(request);
    }
}

三 選擇

    每個攔截器都有各自的優點

Application interceptors應用程式攔截器

  • 不需要擔心比如重定向和重試的中間響應。
  • 總是被呼叫一次,即使HTTP響應結果是從快取中獲取的。
  • 監控應用程式的原始意圖。不關心例如OkHttp注入的頭部欄位If-None-Match。
  • 允許短路,不呼叫Chain.proceed()。
  • 允許重試並多次呼叫Chain.proceed()。

Network Interceptors網路攔截器

  • 能夠對中間的響應進行操作比如重定向和重試。
  • 當發生網路短路時,不呼叫快取的響應結果。
  • 監控資料,就像資料再網路上傳輸一樣。
  • 訪問承載請求的連線Connection。