}
…省略
}
}
上面的代码中,我只保留了关键部分。其中有两个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 请求才能有请求体
*/
//请求不是get与head
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
// 除了 PROPFIND 请求之外都改成GET请求
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method(“GET”, null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
// 不是 PROPFIND 的请求,把请求头中关于请求体的数据删掉
if (!maintainBody) {
requestBuilder.removeHeader(“Transfer-Encoding”);
requestBuilder.removeHeader(“Content-Length”);
requestBuilder.removeHeader(“Content-Type”);
}
}
// 在跨主机重定向时,删除身份验证请求头
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader(“Authorization”);
}
return requestBuilder.url(url).build();
// 408 客户端请求超时
case HTTP_CLIENT_TIMEOUT:
// 408 算是连接失败了,所以判断用户是不是允许重试
if (!client.retryOnConnectionFailure()) {
return null;
}
// UnrepeatableRequestBody实际并没发现有其他地方用到
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
// 如果是本身这次的响应就是重新请求的产物同时上一次之所以重请求还是因为408,那我们这次不再重请求了
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
return null;
}
// 如果服务器告诉我们了 Retry-After 多久后重试,那框架不管了。
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
// 503 服务不可用 和408差不多,但是只在服务器告诉你 Retry-After:0(意思就是立即重试) 才重请求
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request();
}
return null;
default:
return null;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
结尾
最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
义View**
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
[外链图片转存中…(img-rvmxTybx-1713620354517)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!