}
}
上面的代码中,我只保留了关键部分。其中有两个continue,一个return.
当请求到达了这个拦截器,它会进入一个
while(true)
循环,
- 当发生了
RouteException
异常(这是由于请求尚未发出去,路由异常,连接未成功),就会去判断recover
方法的返回值,根据返回值决定要不要continue
.- 当发生
IOException
(请求已经发出去,但是和服务器通信失败了)之后,同样去判断recover
方法的返回值,根据返回值决定要不要continue
.如果这两个
continue
都没有执行,就有可能走到最后的return response
结束本次请求.
那么 是不是要重试
,其判断逻辑就在recover()
方法内部:
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
//todo 1、在配置OkhttpClient是设置了不允许重试(默认允许),则一旦发生请求失败就不再重试
//The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
//todo 2、由于requestSendStarted只在http2的io异常中为false,http1则是 true,
//在http1的情况下,需要判定 body有没有实现UnrepeatableRequestBody接口,而body默认是没有实现,所以后续instanceOf不成立,不会走return false.
//We can’t send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)
return false;
//todo 3、判断是不是属于重试的异常
//This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
//todo 4、有没有可以用来连接的路由路线
//No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
简单解读一下这个方法:
- 如果okhttpClient已经set了不允许重试,那么这里就返回false,不再重试。
- 如果requestSendStarted 只在http2.0的IO异常中是true,不过HTTP2.0还没普及,先不管他,这里默认通过。
- 判断是否是重试的异常,也就是说,是不是之前重试之后发生了异常。
这里解读一下,之前重试发生过异常,抛出了Exception,这个isRecoverable
方法会根据这个异常去判定,是否还有必要去重试。
- 协议异常,如果发生了协议异常,那么没必要重试了,你的请求或者服务器本身可能就存在问题,再重试也是白瞎。
- 超时异常,只是超时而已,直接判定重试(这里requestSendStarted是http2才会为true,所以这里默认就是false)
- SSL异常,HTTPS证书出现问题,没必要重试。
- SSL握手未授权异常,也不必重试
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 出现协议异常,不能重试
if (e instanceof ProtocolException) {
return false;
}
// requestSendStarted认为它一直为false(不管http2),异常属于socket超时异常,直接判定可以重试
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted;
}
// SSL握手异常中,证书出现问题,不能重试
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// SSL握手未授权异常 不能重试
if (e instanceof SSLPeerUnverifiedException) {
return false;
}
return true;
}
- 有没有可以用来连接的路由路线,也就是说,如果当DNS解析域名的时候,返回了多个IP,那么这里可能一个一个去尝试重试,直到没有更多ip可用。
重定向
依然是RetryAndFollowUpInterceptor
的核心方法 interceptor()
方法,这次我截取后半段:
public Response intercept(Chain chain) throws IOException {
while (true) {
…省略前面的重试判定
//todo 处理3和4xx的一些状态码,如301 302重定向
Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
//todo 限制最大 followup 次数为20次
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());
}
//todo 判断是不是可以复用同一份连接
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
- " didn’t close its backing stream. Bad interceptor?");
}
}
}
上面源码中, followUpRequest()
方法中规定了哪些响应码可以重定向:
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
// 407 客户端使用了HTTP代理服务器,在请求头中添加 “Proxy-Authorization”,让代理服务器授权
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException(“Received HTTP_PROXY_AUTH (407) code while not using proxy”);
}
return client.proxyAuthenticator().authenticate(route, userResponse);
// 401 需要身份验证 有些服务器接口需要验证使用者身份 在请求头中添加 “Authorization”
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
// 308 永久重定向
// 307 临时重定向
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// 如果请求方式不是GET或者HEAD,框架不会自动重定向请求
if (!method.equals(“GET”) && !method.equals(“HEAD”)) {
return null;
}
// 300 301 302 303
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// 如果用户不允许重定向,那就返回null
if (!client.followRedirects()) return null;
// 从响应头取出location
String location = userResponse.header(“Location”);
if (location == null) return null;
// 根据location 配置新的请求 url
HttpUrl url = userResponse.request().url().resolve(location);
// 如果为null,说明协议有问题,取不出来HttpUrl,那就返回null,不进行重定向
if (url == null) return null;
// 如果重定向在http到https之间切换,需要检查用户是不是允许(默认允许)
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
Request.Builder requestBuilder = userResponse.request().newBuilder();
/**
- 重定向请求中 只要不是 PROPFIND 请求,无论是POST还是其他的方法都要改为GET请求方式,
- 即只有 PROPFIND 请求才能有请求体
*/
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
实战系列
话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示,获取学习笔记链接:点击我的GitHub免费获取
IDOPA-1711092261197)]
实战系列
话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示,获取学习笔记链接:点击我的GitHub免费获取
[外链图片转存中…(img-MCiSh6JA-1711092261197)]