一 概念
在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);
}
}
三 选择
每个拦截器都有各自的优点