android网络重试机制,okhttp源码解析(四):重试机制

前言

这一篇我们分析okhttp的重试机制,一般如果网络请求失败,我们会考虑连续请求多次,增大网络请求成功的概率,那么okhttp是怎么实现这个功能的呢?

正文

首先还是回到之前的InterceptorChain:

Response getResponseWithInterceptorChain() throws IOException {

// Build a full stack of interceptors.

List interceptors = new ArrayList<>();

interceptors.addAll(client.interceptors());

// 重试的Interceptor,在构造方法中创建

interceptors.add(retryAndFollowUpInterceptor);

// 其他的interceptor

...

return chain.proceed(originalRequest);

}

其中的RetryAndFollowUpInterceptor是负责重试的Interceptor,他处于责任链的顶端,负责网络请求的开始工作,也负责收尾的工作。

他的创建是在RealCall.java构造方法中:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

this.client = client;

this.originalRequest = originalRequest;

this.forWebSocket = forWebSocket;

this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

}

创建的时机就是我们调用OkhttpClient的newCall方法,每一次发起网络请求,我们都需要调用:

@Override public Call newCall(Request request) {

return RealCall.newRealCall(this, request, false /* for web socket */);

}

了解了他的创建过程,我们接着分析RetryAndFollowUpInterceptor的工作过程:

Request request = chain.request();

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Call call = realChain.call();

// 我们设置的eventListener回调

EventListener eventListener = realChain.eventListener();

// 从参数上看,可以推测StreamAllocation中保存了此次网络请求的信息

// 连接池(),地址,网络请求,eventListenenr

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),

createAddress(request.url()), call, eventListener, callStackTrace);

this.streamAllocation = streamAllocation;

一开始,创建了StreamAllocation对象,他封装了网络请求相关的信息:连接池,地址信息,网络请求,事件回调,负责网络连接的连接、关闭,释放等操作。callStackTrace是一个Throwable对象,他主要是记录运行中异常信息,帮助我们识别网络请求的来源。

之后就进入到网络连接的循环,代码稍微有点长:

// 计数器

int followUpCount = 0;

Response priorResponse = null;

// 开始进入while循环

while (true) {

// 如果请求已经被取消了,释放连接池的资源

if (canceled) {

streamAllocation.release();

throw new IOException("Canceled");

}

Response response;

boolean releaseConnection = true;

try {

// 得到最终的网络请求结果

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(), streamAllocation, 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, streamAllocation, 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.

// 如果不为空,保存到response中

if (priorResponse != null) {

response = response.newBuilder()

.priorResponse(priorResponse.newBuilder()

.body(null)

.build())

.build();

}

Request followUp;

try {

// 判断返回结果response,是否需要继续完善请求,例如证书验证等等

followUp = followUpRequest(response, streamAllocation.route());

} catch (IOException e) {

streamAllocation.release();

throw e;

}

// 如果不需要继续完善网络请求,返回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);

}

// 如果body内容只能发送一次,释放连接

if (followUp.body() instanceof UnrepeatableRequestBody) {

streamAllocation.release();

throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());

}

// 如果返回response的URL地址和追加请求的url地址不一致

if (!sameConnection(response, followUp.url())) {

// 释放之前你的url地址连接

streamAllocation.release();

// 创建新的网络请求封装对象StreamAllocation

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?");

}

// 更新下一次的网络请求对象

request = followUp;

// 保存上一次的请求结果

priorResponse = response;

}

followUpCount是用来记录我们发起网络请求的次数的,为什么我们发起一个网络请求,可能okhttp会发起多次呢?例如https的证书验证,我们需要经过:发起 -> 验证 -> 响应,三个步骤需要发起至少两次的请求,或者我们的网络请求被重定向,在我们第一次请求得到了新的地址后,再向新的地址发起网络请求。

但是多次相应的次数是有限制的,我们看一下okhttp的注释是怎么解释的:

/**

* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,

* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.

*/

不同的浏览器推荐的次数是不同的,还特别强调了HTTP 1.0协议推荐5次。不过我们一般也不会设置这么多次,这会导致网络请求的效率很低。

在网络请求中,不同的异常,重试的次数也不同,okhttp捕获了两种异常:RouteException和IOException。

RouteException:所有网络连接失败的异常,包括IOException中的连接失败异常;

IOException:除去连接异常的其他的IO异常。

这个时候我们需要判断是否需要重试:

private boolean recover(IOException e, StreamAllocation streamAllocation,

boolean requestSendStarted, Request userRequest) {

streamAllocation.streamFailed(e);

// The application layer has forbidden retries.

// 如果设置了不需要重试,直接返回false

if (!client.retryOnConnectionFailure()) return false;

// We can't send the request body again.

// 如果网络请求已经开始,并且body内容只可以发送一次

if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)

return false;

// This exception is fatal.

// 判断异常类型,是否要继续尝试,

// 不会重试的类型:协议异常、Socketet异常并且网络情况还没开始,ssl认证异常

if (!isRecoverable(e, requestSendStarted)) return false;

// No more routes to attempt.

// 已经没有其他可用的路由地址了

if (!streamAllocation.hasMoreRoutes()) return false;

// For failure recovery, use the same route selector with a new connection.

// 其他情况返回true

return true;

}

其中的路由地址我们先忽略,这个之后我们还会讨论。假定没有其他路由地址的情况下:

1、连接失败,并不会重试;

2、如果连接成功,因为特定的IO异常(例如认证失败),也不会重试

其实这两种情况是可以理解的,如果连接异常,例如无网络状态,重试也只是毫秒级的任务,不会有特别明显的效果,如果是网络很慢,到了超时时间,应该让用户及时了解失败的原因,如果一味重试,用户就会等待多倍的超时时间,用户体验并不好。认证失败的情况就更不用多说了。

如果我们非要重试多次怎么办?

自定义Interceptor,增加计数器,重试到你满意就可以了:

/**

* 重试拦截器

*/

public class RetryInterceptor implements Interceptor {

/**

* 最大重试次数

*/

private int maxRetry;

RetryInterceptor(int maxRetry) {

this.maxRetry = maxRetry;

}

@Override

public Response intercept(@NonNull Chain chain) throws IOException {

Request request = chain.request();

Response response = null;

int count = 0;

while (count < maxRetry) {

try {

//发起网络请求

response = chain.proceed(request);

// 得到结果跳出循环

break;

} catch (Exception e) {

count ++;

response = null;

}

}

if(response == null){

throw Exception

}

return response;

}

}

这是一份伪代码,具体的逻辑大家可以自行完善。

总结

到这里okhttp的重试机制就分析结束了,我们发现只有在特定情况下,okhttp才会重试,如果想要自定义重试机制,可以设置Intercptor来解决这个问题。

接下来我们了解和研究一下okhttp的Dns。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构造入门

构造死磕 什么是构造 小学中学奥数先用数学解决再编程实现的构造题一般算法无法解决\(NOI\)难度\(PJ-\)代码量坑构造举例 CF743C Vladik and fractions 题目让我们构造一组数字,满足\(\frac{2}{n} \frac{1}{x} \frac{1}{y} \frac{1}{z}\)第一眼看到就想到听老师讲了半天才知…

王成录华为鸿蒙系统,华为手机销量仍在增长!华为王成录:手机会是鸿蒙OS系统的中心...

【天极网手机频道】由于制裁&#xff0c;华为遭到前所未有的断供&#xff0c;外界有不少声音都在担心华为手机出货量将会出现暴跌。不过昨日华为开发者大会举办期间&#xff0c;华为消费者业务软件部总裁王成录表示&#xff0c;华为手机销量仍在增长中。昨天的华为开发者大会&a…

28线程

进程&#xff1a;计算机执行的任务 线程&#xff1a;执行任务中的小任务 多线程 计算机再执行过程中&#xff0c;再同一时间只能让cpu的一个核执行一个进程。进程有多个线程构成&#xff0c;再同一时刻Cpu只能处理一个线程。 引入多线程 当线程被cpu执行时cpu开始工作&#xff…

什么是javax.ws.rs.core.context? [第3部分]

如何使用Context批注 在什么是javax.ws.rs.core.context的第2部分中&#xff1f; 您学习了如何使用Context批注从SecurityContext类的注入实例检索安全信息&#xff0c;以及如何通过ResourceContext实例使用JAX-RS资源类。 在本文中&#xff0c;您将学习如何将Context批注与请…

html 字幕飘动效果,html 滚动字幕 制作滚动字幕效果 参数

制作滚动字幕效果&#xff1a;marquee标签 如下:&#xff1c;MARQUEE directionup height146 οnmοuseοutstart() οnmοuseοverstop() scrollAmount4&#xff1e; &#xff1c;/marquee&#xff1e;参数说明&#xff1a;direction滚动方向&#xff1a;up向上滚动&#xff0…

静态点分治总结

点分治是世界上最好的算法QwQ 点分治可以解决各种树上的边权点权问题&#xff0c;然后如果你发现这个题好像问的特别玄学&#xff0c;lca&#xff0c;树差都做不了&#xff0c;树上动‘龟’更做不了&#xff0c;只能暴力时&#xff0c;这个题大多数情况就是点分治了 点分治的思…

html节点上下移动,关于前端:数组元素上下移动

/*** 上、下挪动* param {number} code 下标* param {number} dir 1上移 0下移*/onMove(code, dir) {let moveComm (curIndex, nextIndex) > {let arr this.commodityInfoarr[curIndex] arr.splice(nextIndex, 1, arr[curIndex])[0]return arr}this.commodityInfo.some((…

mybatis插入数据后返回自增主键ID详解

1.场景介绍: ​ 开发过程中我们经常性的会用到许多的中间表,用于数据之间的对应和关联.这个时候我们关联最多的就是ID,我们在一张表中插入数据后级联增加到关联表中.我们熟知的mybatis在插入数据后返回的是插入成功的条数,那么这个时候我们想要得到相应的这条新增数据的ID,该怎…

spring 属性占位符_Spring属性占位符配置器–一些不太明显的选项

spring 属性占位符Spring的PropertySourcesPlaceholderConfigurer用于从XML或Java Config中定义的Spring bean定义外部化属性。 PlaceholderConfigurer支持的一些选项在文档中并不明显&#xff0c;但很有趣&#xff0c;并且可能有用。 首先&#xff0c;以Spring文档中的示例为…

红包雨效果html,js+css实现红包雨效果

//每一个红包都是相对于父元素定位&#xff0c;通过z-index来设置层级let zIndex 1;function bindEvent() {$redPackage.on(click,.js-RedPackageBox,function() {//拿到每个红包的数据const data $(this).data(txt);}}//生成mix-max的随机数function getRandom(min,max) {re…

Delphi下实现全屏快速找图找色 二、矩阵遍历

二、矩阵遍历  矩阵遍历是一个数据结构方面的问题。假设有一个矩阵Matrix&#xff0c;它共有RowCount行&#xff0c;每行有ColCount列&#xff0c;当利用y表示行数&#xff0c;x表示列数&#xff0c;那么利用Matrix[y,x]就可以访问矩阵中的任意元素。假设有一个1010大小的矩阵…

Eclipse MicroProfile:您需要了解的5件事

针对微服务架构优化企业Java Eclipse MicroProfile计划是在JavaOne 2016上发起的&#xff0c;JavaOne是服务器供应商和Java用户组的创意&#xff0c;目的是解决企业Java微服务领域的缺点。 Java EE的发布速度减慢到无法应对Swift发展的微服务趋势的挑战的程度。 MicroProfile通…

html 选择不能重复,人生,就是一次无法重复的选择(深度好文)

作者&#xff1a;彩云追月欧洲著名的政治家托马斯 莫尔说&#xff1a;“在人生中最艰难的是选择”。漫漫人生路&#xff0c;有无数的选择&#xff0c;不同的选择&#xff0c;可能会决定我们不同的人生道路。下面的故事也许对你有所启迪&#xff1a;一天&#xff0c;几个学生问…

用HTML做软件UI用到的的一些技术

做WEB开发的想把网页做成应用程序的界面&#xff0c;开发应用程序的又想把程序界面做得和WEB一样。本文介绍一下用HTML做软件UI用到的的一些技术。 其实HTML UI也不是什么新鲜事了&#xff0c;Norton Antivirus从几年前的版本就开始用了&#xff0c;vs.net2002中的开始页也用了…

html css导航栏字体图标,HTML+CSS入门之两种图标字体库

本篇教程介绍了HTMLCSS入门之两种图标字体库&#xff0c;希望阅读本篇文章以后大家有所收获&#xff0c;帮助大家HTMLCSS入门。<## 0. 前言比较基础的图标加载&#xff1a;和块元素的背景background: url(./x.png).页面多图标时&#xff0c;使用雪碧图(多个png压缩成一个png…

垃圾收集算法,垃圾收集器_弱,弱,最弱,利用专家参考来管理垃圾收集器

垃圾收集算法,垃圾收集器何时以及何时不使用Java中的专家引用 弱引用&#xff0c;软引用和幻像引用既危险又强大。 如果以错误的方式使用它们&#xff0c;则会破坏JVM性能。 但是&#xff0c;如果使用正确的方法&#xff0c;它们可以大大提高性能和程序清晰度。 弱引用和软引用…

ESP8266—“ICACHE_FLASH_ATTR”宏

问&#xff1a;ESP8266_NONOS_SDK中ICACHE_FLASH_ATTR宏的用途是什么&#xff1f;我看到它取决于ICACHE_FLASH&#xff0c;但我不知道何时应该定义该符号。什么时候需要包括它&#xff1f;答&#xff1a;对于ESP8266_NONOS_SDK&#xff0c;用ICACHE_FLASH_ATTR编译的函数编译到…

layui网页html编辑器,layui使用富文本编辑器

HTML代码&#xff1a;这里的原理是你输入的内容会经过处理插入到文本区域textarea中js代码&#xff1a;/*** 文本编辑器*/layui.use([form, layedit], function(){var layedit layui.layedit;//上传图片,必须放在 创建一个编辑器前面layedit.set({uploadImage: {url: upload /…

Java应用程序性能监视:复杂的分布式应用程序的端到端性能

通过从应用程序中学习企业APM产品&#xff0c;发现更快&#xff0c;更高效的性能监控。 参加AppDynamics APM导览&#xff01; 在最复杂和分布式环境中端到端监视Java应用程序性能-专注于业务事务。 自动发现的业务交易&#xff0c;动态基准&#xff0c;代码级诊断和虚拟作战室…

C#设计模式(2)——简单工厂模式

一、引言 这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式——简单工厂模式。 二、简单工厂模式的介绍 说到简单工厂&#xff0c;自然的第一个疑问当然就是什…