詳解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ù).
此處提取方式簡單暴力。默認(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è)示范。
攔截器部分沒有過多需要做說明的地方,比較簡單,本文的示例可直接使用。如有疑問,歡迎留言。
后續(xù)將抽時(shí)間,對(duì)Retrofit做流程上的簡單梳理,了解各個(gè)配置及部分細(xì)節(jié)實(shí)現(xiàn),比如該文中的Chain實(shí)例
完整示例: https://github.com/670832188/TestApp
以上就是本文的全部內(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)營商的方法
這篇文章主要介紹了Android編程獲取網(wǎng)絡(luò)連接方式及判斷手機(jī)卡所屬運(yùn)營商的方法,涉及Android針對(duì)網(wǎng)絡(luò)的判斷及本機(jī)信息的獲取技巧,需要的朋友可以參考下2016-01-01
Android自定義ViewGroup實(shí)現(xiàn)帶箭頭的圓角矩形菜單
這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup帶箭頭的圓角矩形菜單實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android 實(shí)現(xiàn)將Bitmap 保存到本地
這篇文章主要介紹了Android 實(shí)現(xiàn)將Bitmap 保存到本地,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03
android使用Socket通信實(shí)現(xiàn)多人聊天應(yīng)用
這篇文章主要為大家詳細(xì)介紹了android使用Socket通信實(shí)現(xiàn)多人聊天應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL
這篇文章主要為大家介紹了Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Android如何在root設(shè)備上開啟ViewServer詳解
這篇文章主要給大家介紹了關(guān)于Android中如何在root設(shè)備上開啟ViewServer的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-12-12
Android中實(shí)現(xiàn)GPS定位的簡單例子
這篇文章主要介紹了Android中實(shí)現(xiàn)GPS定位的簡單例子,例子邏輯清晰,但相對(duì)簡單了些,需要的朋友可以參考下2014-07-07

