OkHttp4.3源码解析之 - 重试和重定向
回顾
上一篇文章:发起请求
大家还记得OkHttp是如何发起一条请求的吗?上面这篇文章里介绍了OkHttp是在什么时候把多个拦截器加入到责任链中的。如果大家没看过的可以先去了解一下,因为这个流程和本文息息相关。
如果是忘了的话我们再简单的回顾一遍:
- 构建OkHttpClient对象
- 构建Request对象
- 使用enqueue()发起请求,并处理回调
在第一步里,我们可以通过addInterceptor(xxx)来添加自定义的拦截器,在第三步里,我们通过源码可以看到在RealCall中通过getResponseWithInterceptorChain()方法来处理这些拦截器。
一个简单的例子
现在有个需求,希望每个请求都能把请求时间给打印出来,该怎么做呢?
OkHttpTest类
1 | class OkHttpTest { |
LoggingInterceptor
1 | class LoggingInterceptor : Interceptor { |
运行程序然后打开Logcat
1 | com.bluelzy.kotlinlearning I/TaskRunner: Bluelzy Sending request https://www.baidu.com/ |
可以看到这里把我们需要的信息都打印出来了。
思考一下:
- 这个logger拦截器是怎么添加到OkHttp的呢?
- OkHttp如何运用责任链模式进行多个不同网络层之间的责任划分?
- 我们在实际开发中还能运用责任链模式做其他什么操作吗?
OkHttp中的责任链模式
自定义的拦截器是如何添加到OkHttp中的?
还记得上一篇文章说的吗,我们构建一个OkHttpClient对象,使用的就是Builder模式,通过addInterceptor(interceptor: Interceptor) 这个方法,把拦截器加入到队列中,这个拦截器队列就是OkHttpClient中的一个全局变量,不仅用于存放我们自定义的拦截器,也用于存放OkHttp默认实现的拦截器。
1 | fun addInterceptor(interceptor: Interceptor) = apply { |
OkHttp的责任链模式是如何起作用的?
我们看RealCall里的这段代码:
1 |
|
这里做了几件事:
- 把OkHttpClient里面的Interceptor加入到列表中
- 把默认的几个Interceptor加入到列表中
- 判断是不是Socket请求,不是的话把networkInterceptor加入到列表中
- 最后加入CallServerInterceptor,这个拦截器是用来向服务器发起请求的,因此要加在最后
RetryAndFollowUpInterceptor
大家如果看了上一篇文章的话,应该还记得真正处理责任链的是RetryAndFollowUpInterceptor.proceed()
方法,通过index取出对应的拦截器并执行interceptor.intercept()
方法。而无论是我们自定义的Interceptor,还是OkHttp中默认实现的,都会继承Interceptor这个接口,因此都会实现intercept()方法。
接下来,我们来看看RetryAndFollowUpInterceptor里面做了什么?
1
2
3
4
5 > This interceptor recovers from failures and follows redirects as necessary. It may throw an [IOException] if the call was canceled.
>
> 这个拦截器主要是请求失败时尝试恢复连接,还有处理重定向的问题。
> 如果请求被取消了,可能会抛出IOException异常
>
既然主要是处理这两个问题,那么我们就重点关注看看,这个拦截器是如何处理的。
失败时尝试重连
1 |
|
在发起请求的时候,如果出现了异常,根据不同异常会调用recover()方法,我们看看这个方法做了什么
1 | private fun recover( |
- 如果我们在OkHttpClient设置了不能重连,game over
- 这个重连只能发生在连接失败的时候,如果是请求已经发送了,game over
- 如果这个异常是协议相关的问题,那么同样game over
- OkHttp会在连接池里面尝试不同的IP,如果没有其他可用的IP,game over
如果这个异常是在连接的时候发生的,而且还有可用的IP,我们也设置了可以重试(默认为true),那么就会再构造一个Request,用于重试。
这里OkHttp用了一个很巧妙的方法来实现,那就是递归。
首先在intercept()方法开头加入了while(true),只要没有return或者throw excpetion,就会一直执行下去
1
response = realChain.proceed(request, transmitter, null)
通过上面这一句代码,每次构建一个Request,然后调用proceed进行请求
每一个请求的结果都会返回到上一个Response中
每次请求完毕followUpCount 加一,在OkHttp中,这个参数用于控制请求最大数,默认是20,一旦超过这个数,也会抛出异常
如何进行重定向
重定向和重试其实都在intercept()方法中,主要是这句代码:
1 | val followUp = followUpRequest(response, route) |
followUpRequeset():
1 |
|
可以看到,这里通过判断responseCode
来进行不同的处理,我们重点关注重定向,看看buildRedirectRequest()
方法:
1 | private fun buildRedirectRequest(userResponse: Response, method: String): Request? { |
这里就是一个很标准的Builder模式的应用了,通过request.newBuilder()
和后续的builder.xxx()
构建一个Request.Builder对象。最终调用build()
方法返回Request对象。
总结
在RetryAndFollowUpInterceptor中,主要做了两件事
- 重试
- 重定向
这两者都是通过followUpRequest()
方法来构建一个新的Request,然后递归调用proceed()方法进行请求。中间有很多的错误/异常判断,一旦条件不满足就会停止请求并且释放资源。
我们在实际工作中如何使用?
例如,我们现在不想使用OkHttp默认的重试拦截器,希望自己定义重试次数,那么可以这样写:
RetryInterceptor:
1 | /** |
为了能测试重试拦截器,我们定义一个测试请求,每次返回400
TestInterceptor:
1 | /** |
最后发起请求
OkHttpTest:
1 | class OkHttpTest { |
我们可以看到logcat打印出来的信息:
1 | 2020-02-16 16:46:29.338 1914-2113/com.bluelzy.kotlinlearning I/TaskRunner: BlueLzy Sending request https://www.baidu.com/ |
发起了1次默认请求+2次重试的请求 = 3次请求。这样就实现了自由控制重试次数的需求了,当然我们也可以在每次重试中做其他的操作,例如更改请求头和请求体。
总结
本文我们主要说了OkHttp中的重试机制和重定向,分析了RetryAndFollowUpinterceptor的源码。最后举了个在实际工作中应用的小例子。
下一篇我们说一下OkHttp中的缓存机制 - CacheInterceptor
如有不对之处,欢迎大家多多交流。感谢阅读!