詳解Retrofit Interceptor(攔截器) 攔截請(qǐng)求并做相關(guān)處理
本文介紹Retrofit攔截器(Interceptor)的使用方法及相關(guān)注意事項(xiàng)。如果本文對(duì)您有所幫助,煩請(qǐng)點(diǎn)亮小紅心~
首先看一下Interceptor源碼:
/** * Observes, modifies, and potentially short-circuits requests going out and the corresponding * responses coming back in. Typically interceptors add, remove, or transform headers on the request * or response. */ public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; /** * Returns the connection the request will be executed on. This is only available in the chains * of network interceptors; for application interceptors this is always null. */ @Nullable Connection connection(); } }
先看一下api描述,翻譯過來其實(shí)就是可以通過攔截器攔截即將發(fā)出的請(qǐng)求及對(duì)響應(yīng)結(jié)果做相應(yīng)處理,典型的處理方式是修改header。其實(shí)我們可能不僅要處理header,有時(shí)也需要添加統(tǒng)一參數(shù),都可以在攔截器內(nèi)部完成。
看一下Interceptor接口,只有intercept(Chain chain)方法,其返回值是Response,顧名思義,是響應(yīng)數(shù)據(jù),我們要做的也就是重寫該方法以達(dá)到我們的目的。intercept(Chain chain)方法中有個(gè)Chain參數(shù),Chain是Interceptor接口內(nèi)部中定義的另一個(gè)接口,我們暫且不管Retrofit內(nèi)部是如何實(shí)現(xiàn)該接口的(這部分內(nèi)容將會(huì)在新的文章中統(tǒng)一講解),現(xiàn)在只需要知道調(diào)用其request()方法可以拿到Request,調(diào)用其proceed(Request request)方法可以得到相應(yīng)數(shù)據(jù)即可。
到此為止,Interceptor基本用法已經(jīng)知曉,下面上示例代碼:
public class CommonInterceptor implements Interceptor { private static Map<String, String> commonParams; public synchronized static void setCommonParam(Map<String, String> commonParams) { if (commonParams != null) { if (CommonInterceptor.commonParams != null) { CommonInterceptor.commonParams.clear(); } else { CommonInterceptor.commonParams = new HashMap<>(); } for (String paramKey : commonParams.keySet()) { CommonInterceptor.commonParams.put(paramKey, commonParams.get(paramKey)); } } } public synchronized static void updateOrInsertCommonParam(@NonNull String paramKey, @NonNull String paramValue) { if (commonParams == null) { commonParams = new HashMap<>(); } commonParams.put(paramKey, paramValue); } @Override public synchronized Response intercept(Chain chain) throws IOException { Request request = rebuildRequest(chain.request()); Response response = chain.proceed(request); // 輸出返回結(jié)果 try { Charset charset; charset = Charset.forName("UTF-8"); ResponseBody responseBody = response.peekBody(Long.MAX_VALUE); Reader jsonReader = new InputStreamReader(responseBody.byteStream(), charset); BufferedReader reader = new BufferedReader(jsonReader); StringBuilder sbJson = new StringBuilder(); String line = reader.readLine(); do { sbJson.append(line); line = reader.readLine(); } while (line != null); LogUtil.e("response: " + sbJson.toString()); } catch (Exception e) { e.printStackTrace(); LogUtil.e(e.getMessage(), e); } // saveCookies(response, request.url().toString()); return response; } public static byte[] toByteArray(RequestBody body) throws IOException { Buffer buffer = new Buffer(); body.writeTo(buffer); InputStream inputStream = buffer.inputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] bufferWrite = new byte[4096]; int n; while (-1 != (n = inputStream.read(bufferWrite))) { output.write(bufferWrite, 0, n); } return output.toByteArray(); } private Request rebuildRequest(Request request) throws IOException { Request newRequest; if ("POST".equals(request.method())) { newRequest = rebuildPostRequest(request); } else if ("GET".equals(request.method())) { newRequest = rebuildGetRequest(request); } else { newRequest = request; } LogUtil.e("requestUrl: " + newRequest.url().toString()); return newRequest; } /** * 對(duì)post請(qǐng)求添加統(tǒng)一參數(shù) */ private Request rebuildPostRequest(Request request) { // if (commonParams == null || commonParams.size() == 0) { // return request; // } Map<String, String> signParams = new HashMap<>(); // 假設(shè)你的項(xiàng)目需要對(duì)參數(shù)進(jìn)行簽名 RequestBody originalRequestBody = request.body(); assert originalRequestBody != null; RequestBody newRequestBody; if (originalRequestBody instanceof FormBody) { // 傳統(tǒng)表單 FormBody.Builder builder = new FormBody.Builder(); FormBody requestBody = (FormBody) request.body(); int fieldSize = requestBody == null ? 0 : requestBody.size(); for (int i = 0; i < fieldSize; i++) { builder.add(requestBody.name(i), requestBody.value(i)); signParams.put(requestBody.name(i), requestBody.value(i)); } if (commonParams != null && commonParams.size() > 0) { signParams.putAll(commonParams); for (String paramKey : commonParams.keySet()) { builder.add(paramKey, commonParams.get(paramKey)); } } // ToDo 此處可對(duì)參數(shù)做簽名處理 signParams /** * String sign = SignUtil.sign(signParams); * builder.add("sign", sign); */ newRequestBody = builder.build(); } else if (originalRequestBody instanceof MultipartBody) { // 文件 MultipartBody requestBody = (MultipartBody) request.body(); MultipartBody.Builder multipartBodybuilder = new MultipartBody.Builder(); if (requestBody != null) { for (int i = 0; i < requestBody.size(); i++) { MultipartBody.Part part = requestBody.part(i); multipartBodybuilder.addPart(part); /* 上傳文件時(shí),請(qǐng)求方法接收的參數(shù)類型為RequestBody或MultipartBody.Part參見ApiService文件中uploadFile方法 RequestBody作為普通參數(shù)載體,封裝了普通參數(shù)的value; MultipartBody.Part即可作為普通參數(shù)載體也可作為文件參數(shù)載體 當(dāng)RequestBody作為參數(shù)傳入時(shí),框架內(nèi)部仍然會(huì)做相關(guān)處理,進(jìn)一步封裝成MultipartBody.Part,因此在攔截器內(nèi)部, 攔截的參數(shù)都是MultipartBody.Part類型 */ /* 1.若MultipartBody.Part作為文件參數(shù)載體傳入,則構(gòu)造MultipartBody.Part實(shí)例時(shí), 需使用MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)方法, 其中name參數(shù)可作為key使用(因?yàn)槟憧赡芤淮紊蟼鞫鄠€(gè)文件,服務(wù)端可以此作為區(qū)分)且不能為null, body參數(shù)封裝了包括MimeType在內(nèi)的文件信息,其實(shí)例創(chuàng)建方法為RequestBody.create(final @Nullable MediaType contentType, final File file) MediaType獲取方式如下: String fileType = FileUtil.getMimeType(file.getAbsolutePath()); MediaType mediaType = MediaType.parse(fileType); 2.若MultipartBody.Part作為普通參數(shù)載體,建議使用MultipartBody.Part.createFormData(String name, String value)方法創(chuàng)建Part實(shí)例 name可作為key使用,name不能為null,通過這種方式創(chuàng)建的實(shí)例,其RequestBody屬性的MediaType為null;當(dāng)然也可以使用其他方法創(chuàng)建 */ /* 提取非文件參數(shù)時(shí),以RequestBody的MediaType為判斷依據(jù). 此處提取方式簡(jiǎn)單暴力。默認(rèn)part實(shí)例的RequestBody成員變量的MediaType為null時(shí),part為非文件參數(shù) 前提是: a.構(gòu)造RequestBody實(shí)例參數(shù)時(shí),將MediaType設(shè)置為null b.構(gòu)造MultipartBody.Part實(shí)例參數(shù)時(shí),推薦使用MultipartBody.Part.createFormData(String name, String value)方法,或使用以下方法 b1.MultipartBody.Part.create(RequestBody body) b2.MultipartBody.Part.create(@Nullable Headers headers, RequestBody body) 若使用方法b1或b2,則要求 備注: 您也可根據(jù)需求修改RequestBody的MediaType,但盡量保持外部傳入?yún)?shù)的MediaType與攔截器內(nèi)部添加參數(shù)的MediaType一致,方便統(tǒng)一處理 */ MediaType mediaType = part.body().contentType(); if (mediaType == null) { String normalParamKey; String normalParamValue; try { normalParamValue = getParamContent(requestBody.part(i).body()); Headers headers = part.headers(); if (!TextUtils.isEmpty(normalParamValue) && headers != null) { for (String name : headers.names()) { String headerContent = headers.get(name); if (!TextUtils.isEmpty(headerContent)) { String[] normalParamKeyContainer = headerContent.split("name=\""); if (normalParamKeyContainer.length == 2) { normalParamKey = normalParamKeyContainer[1].split("\"")[0]; signParams.put(normalParamKey, normalParamValue); break; } } } } } catch (Exception e) { e.printStackTrace(); } } } } if (commonParams != null && commonParams.size() > 0) { signParams.putAll(commonParams); for (String paramKey : commonParams.keySet()) { // 兩種方式添加公共參數(shù) // method 1 multipartBodybuilder.addFormDataPart(paramKey, commonParams.get(paramKey)); // method 2 // MultipartBody.Part part = MultipartBody.Part.createFormData(paramKey, commonParams.get(paramKey)); // multipartBodybuilder.addPart(part); } } // ToDo 此處可對(duì)參數(shù)做簽名處理 signParams /** * String sign = SignUtil.sign(signParams); * multipartBodybuilder.addFormDataPart("sign", sign); */ newRequestBody = multipartBodybuilder.build(); } else { try { JSONObject jsonObject; if (originalRequestBody.contentLength() == 0) { jsonObject = new JSONObject(); } else { jsonObject = new JSONObject(getParamContent(originalRequestBody)); } if (commonParams != null && commonParams.size() > 0) { for (String commonParamKey : commonParams.keySet()) { jsonObject.put(commonParamKey, commonParams.get(commonParamKey)); } } // ToDo 此處可對(duì)參數(shù)做簽名處理 /** * String sign = SignUtil.sign(signParams); * jsonObject.put("sign", sign); */ newRequestBody = RequestBody.create(originalRequestBody.contentType(), jsonObject.toString()); LogUtil.e(getParamContent(newRequestBody)); } catch (Exception e) { newRequestBody = originalRequestBody; e.printStackTrace(); } } // 可根據(jù)需求添加或修改header,此處制作示意 // return request.newBuilder() // .addHeader("header1", "header1") // .addHeader("header2", "header2") // .method(request.method(), newRequestBody) // .build(); return request.newBuilder().method(request.method(), newRequestBody).build(); } /** * 獲取常規(guī)post請(qǐng)求參數(shù) */ private String getParamContent(RequestBody body) throws IOException { Buffer buffer = new Buffer(); body.writeTo(buffer); return buffer.readUtf8(); } /** * 對(duì)get請(qǐng)求做統(tǒng)一參數(shù)處理 */ private Request rebuildGetRequest(Request request) { if (commonParams == null || commonParams.size() == 0) { return request; } String url = request.url().toString(); int separatorIndex = url.lastIndexOf("?"); StringBuilder sb = new StringBuilder(url); if (separatorIndex == -1) { sb.append("?"); } for (String commonParamKey : commonParams.keySet()) { sb.append("&").append(commonParamKey).append("=").append(commonParams.get(commonParamKey)); } Request.Builder requestBuilder = request.newBuilder(); return requestBuilder.url(sb.toString()).build(); } }
該攔截器示例代碼提供了插入公共參數(shù)及對(duì)添加header功能(該功能在代碼中被注釋掉,如需要,放開即可)。對(duì)Request的攔截處理在rebuildRequest(Request request) 方法中完成,該方法只處理了GET與POST請(qǐng)求,內(nèi)部有較為詳盡的注釋,較為復(fù)雜的是文件傳輸,有些需要注意的事項(xiàng)也做了盡可能完善的說明;對(duì)響應(yīng)數(shù)據(jù)的處理,代碼示例中只做了結(jié)果輸出處理,僅僅做個(gè)示范。
攔截器部分沒有過多需要做說明的地方,比較簡(jiǎn)單,本文的示例可直接使用。如有疑問,歡迎留言。
后續(xù)將抽時(shí)間,對(duì)Retrofit做流程上的簡(jiǎn)單梳理,了解各個(gè)配置及部分細(xì)節(jié)實(shí)現(xiàn),比如該文中的Chain實(shí)例
完整示例: https://github.com/670832188/TestApp
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java servlet、filter、listener、interceptor之間的區(qū)別和聯(lián)系
- 詳解Java的Hibernate框架中的Interceptor和Collection
- Spring MVC 攔截器 interceptor 用法詳解
- Spring security用戶URL權(quán)限FilterSecurityInterceptor使用解析
- 分享Angular http interceptors 攔截器使用(推薦)
- spring boot加入攔截器Interceptor過程解析
- Springboot+redis+Interceptor+自定義annotation實(shí)現(xiàn)接口自動(dòng)冪等
- SpringBoot中使用Filter和Interceptor的示例代碼
- Spring interceptor攔截器配置及用法解析
相關(guān)文章
Android編程獲取網(wǎng)絡(luò)連接方式及判斷手機(jī)卡所屬運(yùn)營(yíng)商的方法
這篇文章主要介紹了Android編程獲取網(wǎng)絡(luò)連接方式及判斷手機(jī)卡所屬運(yùn)營(yíng)商的方法,涉及Android針對(duì)網(wǎng)絡(luò)的判斷及本機(jī)信息的獲取技巧,需要的朋友可以參考下2016-01-01Android自定義ViewGroup實(shí)現(xiàn)帶箭頭的圓角矩形菜單
這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup帶箭頭的圓角矩形菜單實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07Android 實(shí)現(xiàn)將Bitmap 保存到本地
這篇文章主要介紹了Android 實(shí)現(xiàn)將Bitmap 保存到本地,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03android使用Socket通信實(shí)現(xiàn)多人聊天應(yīng)用
這篇文章主要為大家詳細(xì)介紹了android使用Socket通信實(shí)現(xiàn)多人聊天應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL
這篇文章主要為大家介紹了Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Android如何在root設(shè)備上開啟ViewServer詳解
這篇文章主要給大家介紹了關(guān)于Android中如何在root設(shè)備上開啟ViewServer的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-12-12Android中實(shí)現(xiàn)GPS定位的簡(jiǎn)單例子
這篇文章主要介紹了Android中實(shí)現(xiàn)GPS定位的簡(jiǎn)單例子,例子邏輯清晰,但相對(duì)簡(jiǎn)單了些,需要的朋友可以參考下2014-07-07