专栏首页恩蓝脚本详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

本文介绍Retrofit拦截器(Interceptor)的使用方法及相关注意事项。如果本文对您有所帮助,烦请点亮小红心~

首先看一下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描述,翻译过来其实就是可以通过拦截器拦截即将发出的请求及对响应结果做相应处理,典型的处理方式是修改header。其实我们可能不仅要处理header,有时也需要添加统一参数,都可以在拦截器内部完成。

看一下Interceptor接口,只有intercept(Chain chain)方法,其返回值是Response,顾名思义,是响应数据,我们要做的也就是重写该方法以达到我们的目的。intercept(Chain chain)方法中有个Chain参数,Chain是Interceptor接口内部中定义的另一个接口,我们暂且不管Retrofit内部是如何实现该接口的(这部分内容将会在新的文章中统一讲解),现在只需要知道调用其request()方法可以拿到Request,调用其proceed(Request request)方法可以得到相应数据即可。

到此为止,Interceptor基本用法已经知晓,下面上示例代码:

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);
// 输出返回结果
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;
}
/**
* 对post请求添加统一参数
*/
private Request rebuildPostRequest(Request request) {
//    if (commonParams == null || commonParams.size() == 0) {
//      return request;
//    }
Map<String, String  signParams = new HashMap< (); // 假设你的项目需要对参数进行签名
RequestBody originalRequestBody = request.body();
assert originalRequestBody != null;
RequestBody newRequestBody;
if (originalRequestBody instanceof FormBody) { // 传统表单
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 此处可对参数做签名处理 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);
/*
上传文件时,请求方法接收的参数类型为RequestBody或MultipartBody.Part参见ApiService文件中uploadFile方法
RequestBody作为普通参数载体,封装了普通参数的value; MultipartBody.Part即可作为普通参数载体也可作为文件参数载体
当RequestBody作为参数传入时,框架内部仍然会做相关处理,进一步封装成MultipartBody.Part,因此在拦截器内部,
拦截的参数都是MultipartBody.Part类型
*/
/*
1.若MultipartBody.Part作为文件参数载体传入,则构造MultipartBody.Part实例时,
需使用MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)方法,
其中name参数可作为key使用(因为你可能一次上传多个文件,服务端可以此作为区分)且不能为null,
body参数封装了包括MimeType在内的文件信息,其实例创建方法为RequestBody.create(final @Nullable MediaType contentType, final File file)
MediaType获取方式如下:
String fileType = FileUtil.getMimeType(file.getAbsolutePath());
MediaType mediaType = MediaType.parse(fileType);
2.若MultipartBody.Part作为普通参数载体,建议使用MultipartBody.Part.createFormData(String name, String value)方法创建Part实例
name可作为key使用,name不能为null,通过这种方式创建的实例,其RequestBody属性的MediaType为null;当然也可以使用其他方法创建
*/
/*
提取非文件参数时,以RequestBody的MediaType为判断依据.
此处提取方式简单暴力。默认part实例的RequestBody成员变量的MediaType为null时,part为非文件参数
前提是:
a.构造RequestBody实例参数时,将MediaType设置为null
b.构造MultipartBody.Part实例参数时,推荐使用MultipartBody.Part.createFormData(String name, String value)方法,或使用以下方法
b1.MultipartBody.Part.create(RequestBody body)
b2.MultipartBody.Part.create(@Nullable Headers headers, RequestBody body)
若使用方法b1或b2,则要求
备注:
您也可根据需求修改RequestBody的MediaType,但尽量保持外部传入参数的MediaType与拦截器内部添加参数的MediaType一致,方便统一处理
*/
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()) {
// 两种方式添加公共参数
// method 1
multipartBodybuilder.addFormDataPart(paramKey, commonParams.get(paramKey));
// method 2
//          MultipartBody.Part part = MultipartBody.Part.createFormData(paramKey, commonParams.get(paramKey));
//          multipartBodybuilder.addPart(part);
}
}
// ToDo 此处可对参数做签名处理 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 此处可对参数做签名处理
/**
* 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();
}
}
//    可根据需求添加或修改header,此处制作示意
//    return request.newBuilder()
//        .addHeader("header1", "header1")
//        .addHeader("header2", "header2")
//        .method(request.method(), newRequestBody)
//        .build();
return request.newBuilder().method(request.method(), newRequestBody).build();
}
/**
* 获取常规post请求参数
*/
private String getParamContent(RequestBody body) throws IOException {
Buffer buffer = new Buffer();
body.writeTo(buffer);
return buffer.readUtf8();
}
/**
* 对get请求做统一参数处理
*/
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();
}
}

该拦截器示例代码提供了插入公共参数及对添加header功能(该功能在代码中被注释掉,如需要,放开即可)。对Request的拦截处理在rebuildRequest(Request request) 方法中完成,该方法只处理了GET与POST请求,内部有较为详尽的注释,较为复杂的是文件传输,有些需要注意的事项也做了尽可能完善的说明;对响应数据的处理,代码示例中只做了结果输出处理,仅仅做个示范。

拦截器部分没有过多需要做说明的地方,比较简单,本文的示例可直接使用。如有疑问,欢迎留言。

后续将抽时间,对Retrofit做流程上的简单梳理,了解各个配置及部分细节实现,比如该文中的Chain实例

完整示例: https://github.com/670832188/TestApp

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • laravel 框架执行流程与原理简单分析

    本文实例讲述了laravel 框架执行流程与原理。分享给大家供大家参考,具体如下:

    砸漏
  • Android 使用fast-verification实现验证码填写功能的实例代码

    验证码的验证环节现在是移动APP中不可缺少的一部分,直接使用EditText组件虽然方便但缺少了一些美感,使用fast-verification,让实现验证码变...

    砸漏
  • ThinkPHP5 的简单搭建和使用详解

    我这里选择的是使用 windows 下的 composer 进行安装,收下首先下载 composer 这个工具,安装完成以后进入我们想要创建项目的文件夹输入下面...

    砸漏
  • Spring MVC的模板方法模式 顶

    模板方法模式是由抽象类或接口定义好执行顺序,由子类去实现,但无论子类如何实现,他都得按照抽象类或者接口定义好的顺序去执行。实例代码请参考 设计模式整理 ,Ser...

    算法之名
  • 一文读懂Spring MVC执行流程

    说到Spring MVC执行流程,网上有很多这方面的文章介绍,但是都不太详细,作为一个初学者去读会有许多不理解的地方,今天这篇文章记录一下我学习Spring M...

    说故事的五公子
  • SpringMVC源码解析(二)

    在上篇文章SpringMVC源码解析(一)中,我们搭建了一个SpringBoot的启动demo,分析了SpringBoot中SpringMVC的自动配置原理以及...

    Java学习录
  • Spring Boot @EnableAutoConfiguration和 @Configuration的区别

    Spring Boot @EnableAutoConfiguration和 @Configuration的区别

    程序那些事
  • spring boot 源码解析-SpringApplication初始化

    就是这么简单的代码,构成了spring boot的世界. 那么代码中只有⼀个@SpringBootApplication 注解 和 调⽤了SpringAppli...

    吴生
  • java中注解的使用

    1. 使代码更加干净易读,易于维护修改。比如,以前使用spring的开发,都是基于xml文件实现了统一的配置管理,但是缺点也是显而易见的,就是随着项目的越来越大...

    java乐园
  • 看透 Spring MVC 源代码分析与实践 —— Spring MVC 组件分析

    组件概览 HandlerMapping 根据 request 找到对应的处理器 Handler 和 Interceptors。内部只有一个方法 Handler...

    zhisheng

扫码关注云+社区

领取腾讯云代金券