00:00
一開始就不多說廢話了,主要因爲工作時遇到了一些使用 OKHttp 攔截器的問題,所以在此特寫這篇以作記錄。
現如今,做 Android 開發在選擇網絡框架時,大多數都會首推 Retrofit 。Retrofit 以其簡潔優雅的代碼俘獲了大多數開發者的心。
然而 Retrofit 內部請求也是基於 OKHttp 的,所以在做一些自定義修改 HTTP 請求時,需要對 OKHttp 攔截器具有一定了解。相信熟悉 OKHttp 的同學都知道,OKHttp 內部是使用攔截器來完成請求和響應的,利用的是責任鏈設計模式。所以可以說,攔截器是 OKHttp 的精髓所在。
那麼接下來,我們就通過一些例子來學習怎樣編寫 OKHttp 的攔截器吧,其實這些例子也正是之前我遇到的情景。
00:01
添加請求 Header
假設現在後臺要求我們在請求 API 接口時,都在每一個接口的請求頭上添加對應的 token 。使用 Retrofit 比較多的同學肯定會條件反射出以下代碼:
1 2 3 |
@FormUrlEncoded @POST("/mobile/login.htm") Call<ResponseBody> login(@Header("token") String token, @Field("mobile") String phoneNumber, @Field("smsCode") String smsCode); |
這樣的寫法自然可以,無非就是每次調用 login API 接口時都把 token 傳進去而已。但是需要注意的是,假如現在有十多個 API 接口,每一個都需要傳入 token ,難道我們去重複一遍又一遍嗎?
相信有良知的程序員都會拒絕,因爲這會導致代碼的冗餘。
那麼有沒有好的辦法可以一勞永逸呢?答案是肯定的,那就要用到攔截器了。
代碼很簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 中使用:
1 2 3 4 5 6 |
OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new TokenHeaderInterceptor()) .build(); Retrofit retrofit = new Retrofit.Builder().baseUrl(BuildConfig.BASE_URL) .client(client).addConverterFactory(GsonConverterFactory.create()).build(); |
改變請求體
除了增加請求頭之外,攔截器還可以改變請求體。
假設現在我們有如下需求:在上面的 login 接口基礎上,後臺要求我們傳過去的請求參數是要按照一定規則經過加密的。
規則如下:
- 請求參數名統一爲content;
- content值:JSON 格式的字符串經過 AES 加密後的內容;
舉個例子,根據上面的 login 接口,現有
1
|
{"mobile":"157xxxxxxxx", "smsCode":"xxxxxx"}
|
JSON 字符串,然後再將其加密。最後以 content=[加密後的 JSON 字符串] 方式發送給後臺。
看完了上面的 TokenHeaderInterceptor
之後,這需求對於我們來說可以算是信手拈來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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 轉化爲 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(); } if (body != null) { request = request.newBuilder() .post(body) .build(); } return chain.proceed(request); } } |
代碼中已經添加了關鍵的註釋,相信我已經不需要多解釋什麼了。
經過了這兩種攔截器,相信同學們已經充分體會到了 OKHttp 的優點和與衆不同。
最後,自定義攔截器的使用情景通常是對所有網絡請求作統一處理。如果下次你也碰到這種類似的需求,別忘記使用自定義攔截器哦!
00:02
呃呃呃,按道理來講應該要結束了。
但是,我在這裏開啓一個番外篇吧,不過目標不是針對攔截器而是 ConverterFactory 。
還是後臺需求,login 接口返回的數據也是經過 AES 加密的。所以需要我們針對所有響應體都做解密處理。
另外,還有很重要的一點,就是數據正常和異常時返回的 JSON 格式不一致。
在業務數據正常的時候(即 code 等於 200 時):
1 2 3 4 5 6 7 8 |
{ "code":200, "msg":"請求成功", "data":{ "nickName":"Hello", "userId": "1234567890" } } |
業務數據異常時(即 code 不等於 200 時):
1 2 3 4 5 |
{ "code":7008, "msg":"用戶名或密碼錯誤", "data":"用戶名或密碼錯誤" } |
而這會在使用 Retrofit 自動從 JSON 轉化爲 bean 類時報錯。因爲 data 中的正常數據中是 JSON ,而另一個異常數據中是字符串。
那麼,如何解決上述的兩個問題呢?
利用 自定義 ConverterFactory !!
我們先創建包名 retrofit2.converter.gson
,爲什麼要創建這個包名呢?
因爲自定義的 ConverterFactory 需要繼承 Converter.Factory ,而 Converter.Factory 類默認是包修飾符。
代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public final class CustomConverterFactory extends Converter.Factory { private final Gson gson; public static CustomConverterFactory create() { return create(new Gson()); } @SuppressWarnings("ConstantConditions") // Guarding public API nullability. public static CustomConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new CustomConverterFactory(gson); } private CustomConverterFactory(Gson gson) { this.gson = gson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); // attention here! return new CustomResponseConverter<>(gson, adapter); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } } |
從代碼中可知,CustomConverterFactory
內部是根據 CustomResponseConverter
來轉化
JSON 的,這纔是我們的重點。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class CustomResponseConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final TypeAdapter<T> adapter; private static final String CODE = "code"; private static final String DATA = "data"; CustomResponseConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { try { String originalBody = value.string(); // 先 AES 解密 String body = AESCryptUtils.decrypt(originalBody, AppConstant.getAESKey()); // 再獲取 code JSONObject json = new JSONObject(body); int code = json.optInt(CODE); // 當 code 不爲 200 時,設置 data 爲 null,這樣轉化就不會出錯了 if (code != 200) { Map<String, String> map = gson.fromJson(body, new TypeToken<Map<String, String>>() { }.getType()); map.put(DATA, null); body = gson.toJson(map); } return adapter.fromJson(body); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } finally { value.close(); } } } |
代碼也是很簡單的,相信也不需要解釋了。o(∩_∩)o
最後就是使用了 CustomConverterFactory
:
1 2 3 4 5 6 |
OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new TokenHeaderInterceptor()) .addInterceptor(new RequestEncryptInterceptor()) .build(); Retrofit retrofit = new Retrofit.Builder().baseUrl(BuildConfig.BASE_URL) .client(client).addConverterFactory(CustomConverterFactory.create()).build(); |
好了,這下真的把該講的都講完了,大家可以散了。
完結了。