吐槽
下午睡醒来了,我还是赶紧继续我的OkHttp学习吧
本文主体
先看下本文的思维导图
之前用OKHttp的时候,没有用过拦截器,所以,先把基本用法和源码分析一块学习下吧。
拦截器的概念和思想
官方定义: Interceptors area powerful mechanism that can monitor, rewrite, and retry calls.
简单的翻译下就是, 拦截器可以用来转换,重试,重写请求的机制。
然后看到网上有个人写的对拦截器的通俗的解释是,觉得很有意思
打个比方,镖局押着一箱元宝在行走在一个山间小路上,突然从山上下来一群山贼拦住了镖局的去路,将镖局身上值钱的东西搜刮干净后将其放行。其中山贼相当于拦截器,镖局相当于一个正在执行任务的网络请求,请求中的参数就是镖局携带的元宝。拦截器可以将网络请求携带的参数进行修改验证,然后放行。
我们还是要回到山贼这个角色上,如果让你做一次山贼,你会在什么地方埋伏?肯定是在镖局必经之路上埋伏。也就是说,拦截器就是在所有的网络请求的必经之地上进行拦截。
拦截器的好处:
- 拦截器可以一次性对所有的请求和返回值进行修改。
- 拦截器可以一次性对请求的参数和返回的结果进行编码,比如统一设置为UTF-8.
- 拦截器可以对所有的请求做统一的日志记录,不需要在每个请求开始或者结束的位置都添加一个日志操作。
- 其他需要对请求和返回进行统一处理的需求….
拦截器的分类和作用
OkHttp中的拦截器分2个:
APP层面的拦截器(Application Interception
网络请求层面的拦截器(Network Interception)
两种的区别:
Application:
- 不需要担心是否影响OKHttp的请求策略和请求速度
- 即使从缓存中取数据,也会执行Application拦截器
- 允许重试,即Chain.proceed()可以执行多次。
- 可以监听观察这个请求的最原始的未改变的意图(请求头,请求体等),无法操作OKHttp为我们自动添加额外的请求头
- 无法操作中间的响应结果,比如当URL重定向发生以及请求重试,只能操作客户端主动第一次请求以及最终的响应结果
Network Interceptors:
- 可以修改OkHttp框架自动添加的一些属性,即允许操作中间响应,比如当请求操作发生重定向或者重试等
- 可以观察最终完整的请求参数(也就是最终服务器接收到的请求数据和熟悉)
拦截器的基本使用
1 .先实例化appInterceptor拦截器
/**
* app拦截器 是在请求执行刚开始,还没有执行OkHttp的核心代码前进行拦截
*/
Interceptor appInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url();
String s = url.url().toString();
Response response = chain.proceed(request);//请求
return response;
}
};
2 然后再实例化networkInterceptor拦截器
/**
* 网络拦截器 在连接网络之前
*/
Interceptor netinterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request response = chain.request();
Response response1 = chain.proceed(response); //请求
return response1;
}
};
3最后再把这个配置在okHttpClient的对象里面
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(appInterceptor)
.addNetworkInterceptor(netinterceptor)
.build();
Request request = new Request
.Builder()
.url("http://www.baidu.com")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// call 是一个接口, 是一个准备好的可以执行的request 可以取消,对位一个请求对象,只能单个请求
Log.d("233","请求失败");
}
最后的结果是 先执行appInterceptor拦截器,然后在其里面执行networkInterceptor拦截器 最后从appInterceptor拦截器里面出去
拦截器的用法
统一添加Header
后台要求在请求API时,在每一个接口的请求头添加上对于的Token
这时候就可以使用拦截器对他们进行统一配置
先实例化拦截器:
Interceptor TokenHeaderInterceptor = new 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);
}
};
在OKHttpClient中配置
okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(TokenHeaderInterceptor)
.addNetworkInterceptor(networkInterceptor)
.build();
改变请求体
后台要求我们传过去的请求参数是要按照一定规则经过加密的。
比如 请求参数名统一为contentcontent值:JSON 格式的字符串经过 AES 加密后的内容
nterceptor RequestEncryptInterceptor = new Interceptor() {
private static final String FORM_NAME = "content";
private static final String CHARSET = "UTF-8";
@Override
public Response intercept(Chain chain) throws IOException {
// get token
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);
}
};
在OKHttpClient中配置和上面一样
拦截器的源码分析
在我之前写的博客里面写的最后的网络请求都是getResponseWithInterceptorChain()才是真正发送网络请求和解析返回结果
所以,我们先看下getResponseWithInterceptorChain()方法
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
//拦截器
List<Interceptor> interceptors = new ArrayList<>();
//应用拦截器
interceptors.addAll(client.interceptors());
//添加重试和重定向拦截器
interceptors.add(retryAndFollowUpInterceptor);
//添加转换拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//添加缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//添加连接拦截器
interceptors.add(new ConnectInterceptor(client));
//添加网络拦截器
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
//生成拦截器链
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
逻辑就是
- 创建一系列拦截器,并将其放入一个拦截器数组中。这部分拦截器即包括用户自定义的拦截器也包括框架内部拦截器
- 在这句代码里面发现 chain.proceed(originalRequest)创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法
然后我们再去看下这个拦截器链RealInterceptorChain的proceed()源代码
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
//如果我们已经有一个stream。确定即将到来的request会使用它
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
//如果我们已经有一个stream, 确定chain.proceed()唯一的call
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
//调用链的下一个拦截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
其中最关键的代码是在这块
//调用链的下一个拦截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);// 1
Interceptor interceptor = interceptors.get(index); //2
Response response = interceptor.intercept(next); //3
- 在1处实例化下一个拦截器对应的RealIterceptorChain对象,这个对象会在传递给当前的拦截器
- 在2处得到当前的拦截器:interceptors是存放拦截器的ArryList
- 在3处调用当前拦截器的intercept()方法,并将下一个拦截器的RealIterceptorChain对象传递下去
其他几个拦截器的源代码分析
RealInterceptorChain 负责失败重试以及重定向的
RetryAndFollowUpInterceptor负责两部分逻辑:
- 在网络请求失败后进行重试
- 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接
进入这块的源码,看下它的intercept()方法
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
//执行下一个拦截器链的proceed方法
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
在这串代码中,最关键的是
response = realChain.proceed(request, streamAllocation, null, null);
这行代码就是执行下一个拦截器链的proceed方法。而我们知道在下一个拦截器链中又会执行下一个拦截器的intercept方法。所以整个执行链就在拦截器与拦截器链中交替执行,最终完成所有拦截器的操作
一个拦截器的intercept方法所执行的逻辑大致分为三部分:
- 在发起请求前对request进行处理
- 调用下一个拦截器,获取response
- 对response进行处理,返回给上一个拦截器
这就是OkHttp拦截器机制的核心逻辑。所以一个网络请求实际上就是一个个拦截器执行其intercept方法的过程。
类似一个串串一样的东西,一个串一个,然后按顺序处理完之后就再返回给上一个拦截器
BridgeInterceptor
BridgeInterceptor主要负责以下几部分内容:
- 设置内容长度,内容编码
- 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
- 添加cookie
- 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤
//源代码分析以后再看,现在功力不够唉,网络那块好多都不懂
CacheInterceptor
CacheInterceptor的职责很明确,就是负责Cache的管理//缓存
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
- 当网络请求有符合要求的Cache时直接返回Cache
- 当服务器返回内容有改变时更新当前cache
- 如果当前cache失效,删除
ConnectInterceptor
这个拦截器是代码最少,,,但是最难懂的
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
ConnectInterceptor的intercept方法只有一行关键代码:
RealConnection connection = streamAllocation.connection();
即为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。
CallServerInterceptor
CallServerInterceptor负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。
//但是还是看不懂唉代码
[CallServerInterceptor.java]
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
......
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
总结
拦截器这块真的是很总要,封装了网络这块请求和返回的内容,自己网络这块没学太懂感觉就是我们要传的数据像流一样,每过一次拦截器,就加点东西,然后再返回回来在调度那块。