「okhttp3 4.9.3 版本简单解析」
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「okhttp3 4.9.3 版本简单解析」相关的知识,希望对你有一定的参考价值。
参考技术A关于okhttp3的解析网上已经有非常多优秀的博文了,每每看完都觉得醍醐灌顶,豁然开朗。但等不了几天再回头看,还是跟当初一样陌生,究其根本原因,我们不过是在享受着别人的成果跟着别人的思路云阅读源码了一遍。okhttp从早期的Java版本到Kotlin版本一直不断优化升级,实现细节上也作出了调整。重读源码加上自身的思考能深刻地理解okhttp的实现原理。
从execute()开始,发现其实是一个接口中的方法(Call),这个很好理解根据官方的解释,Call其实是一个待执行的请求,并且这个请求所要的参数已经被准备好;当然既然是请求,那么它是可以被取消的。其次代表单个请求与响应流,因此不能够被再次执行。
Call接口具体的代码实现,重点关注同步执行方法execute()与异步请求enqueue():
Call作为接口,那么具体的实现细节则需要看它的实现类,RealCall作为Call的实现类,先找到对execute()的重写。
代码很少,逐步分析,首先对同步请求进行了检查-判断请求是否已经被执行过了;而这里使用的是并发包下的原子类CAS乐观锁,这里使用CAS比较算法目的也是为提升效率。其次是超时时间的判断,这个比较简单。在看callStart()的具体实现。上代码:
看名称猜测应该是事件监听之类的,可能是包括一些信息的记录与打印。回到RealCall类中,看看这个eventListener的作用到底是什么:
internal val eventListener: EventListener = client.eventListenerFactory.create(this)
继续向下可以知道这个EventListener是一个抽象类,而项目中其唯一实现类为LoggingEventListener,猜测还是有依据的,继续往下看:
实现类LoggingEventListener中对此方法的具体实现:
总结这个callStart()的作用,当同步请求或者异步请求被加到队列时,callStart()会被立即执行(在没有达到线程限制的情况下)记录请求开始的时间与请求的一些信息。如下:
代卖第四段#4 client.dispatcher.executed(this),看样子是在这里开启执行的,可实际真是如此嘛?回到OkHttpClient类中,看看这个分发器dispatcher到底是什么。
具体实现类Dispatcher代码(保留重要代码):
根据注释的信息可以知道,Dispatcher是处理异步请求的执行的策略,当然开发可以实现自己的策略。
知道了Dispatcher的作用,再回到client.dispatcher.executed(this),也即:
结合execute()与Dispatcher分析
到这里请求其实还没有真正的执行,只是在做一些前期的工作,回到Call接口中看看对方法同步请求方法execute()的说明:同步请求可以立即执行,阻塞直到返回正确的结果,或者报错结束。
到#4步执行后,return getResponseWithInterceptorChain() //#5这个方法才是请求一步步推进的核心。也是okhttp网络请求责任链的核心模块。
分析getResponseWithInterceptorChain()方法之前有必要看看OkHttpClient的构造参数,使用的Builder模式,参数很多,可配置化的东西很多,精简一下主要关注几个参数:
到这里有个疑问,这个添加自定义拦截器与添加自定义网络拦截器有什么区别呢?方法看上去是差不多的,查看官方的说明可以发现一些细节。文档中解释了Application Interceptor与Network Interceptors的细微差别。先回到RealCall中查看getResponseWithInterceptorChain()是如何对拦截器结合组装的:
看#1与#2分别对应添加的自定义拦截器与自定义网络拦截器的位置,自定义拦截器是拦截器链的链头,而自定义网络拦截器在ConnectInterceptor拦截器与CallServerInterceptor拦截器之间被添加。总结一下:
Don’t need to worry about intermediate responses like redirects and retries.
Are always invoked once, even if the HTTP response is served from the cache.
Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
Permitted to short-circuit and not call Chain.proceed().
Permitted to retry and make multiple calls to Chain.proceed().
Can adjust Call timeouts using withConnectTimeout, withReadTimeout, withWriteTimeout.
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection that carries the request.
综上可以得出整个链的顺序结构,如果都包含自定义拦截器与自定义网络拦截器,则为自定义拦截器->RetryAndFollowUpInterceptor->BridgeInterceptor->CacheInterceptor->ConnectInterceptor->自定义网络拦截器->CallServerInterceptor;那么链是如何按照顺序依次执行的呢?okhttp在这里设计比较精妙,在构造RealInterceptorChain对象时带入index信息,这个index记录的就是单个拦截器链的位置信息,而RealInterceptorChain.proceed(request: Request)通过index++自增一步步执行责任链一直到链尾。
简单的分析推进过程:
1.RealInterceptorChain的构造参数中携带了index的信息,index++自增通过proceed方法不断执行。
2.拦截器统一实现Interceptor接口,接口中fun proceed(request: Request): Response保证了链式链接。当然拦截器的顺序是按照一定的规则排列的,逐个分析。
1.重试拦截器规定默认的重试次数为20次
2.以response = realChain.proceed(request)为分界点,包括其他的拦截器,在责任链传递之前所做的工作都是前序工作,然后将request下发到下一个拦截器。
3.response = realChain.proceed(request)后的代码逻辑为后续工作,即拿到上个拦截器的response结果,有点递归的意思,按照责任链的执行一直到最后一个拦截器获得的结果依次上抛每个拦截器处理这个response完成一些后序工作。
4.当然并不是每个请求都会走完整个链,如CacheInterceptor当开启了缓存(存在缓存)拿到了缓存的response那么之后的拦截器就不会在继续传递。
1.桥接拦截器主要对请求的Hader的信息的补充,包括内容长度等。
2.传递请求到下一个链,等待返回的response信息。
3.后序的操作包括Cookie、gzip压缩信息,User-Agent等信息的补充。
1.缓存拦截器默认没有被开启,需要在调用时指定缓存的目录,内部基于DiskLruCache实现了磁盘缓存。
2.当缓存开启,且命中缓存,那么链的调用不会再继续向下传递(此时已经拿到了response)直接进行后序的操作。
3.如果未命中,则会继续传递到下一个链也即是ConnectInterceptor。
1.建立与目标的服务器的TCP或者TCP-TLS的链接。
2.与之前的拦截器不同,前面的拦截器的前序操作基于调用方法realChain.proceed()之前,但是ConnectInterceptor 没有后序操作,下发到下一个拦截器 。
1.实质上是请求与I/O操作,将请求的数据写入到Socket中。
2.从Socket读取响应的数据 TCP/TCP-TLS对应的端口 ,对于I/O操作基于的是okio,而okhttp的高效请求同样离不开okio的支持。
3.拿到数据reponse返回到之前包含有后序操作的拦截器,但ConnectInterceptor除外,ConnectInterceptor是没有后续操作的。
整个拦截器流程图如下:
1.排除极端的情况,System.exit()或者其他, finally 块必然执行,不论发生异常与否,也不论在 finally 之前是否有return。
2.不管在 try 块中是否包含 return, finally 块总是在 return 之前执行。
3.如果 finally 块中有 return ,那么 try 块和 catch 块中的 return 就没有执行机会了。
Tip:第二条的结论很重要,回到execute()方法,dispatcher.finished(this)在结果response结果返回之前执行,看finished()具体实现。
1.#1方法一,calls.remove(call)返回为 true ,也即是这个同步请求被从runningSyncCalls中移除释放;所以idleCallback为空。
2.#3很显然asyncCall的结果为空,没有异步请求,在看#4具体实现,runningSyncCalls的size为1。则isRunning的结果为 true 。idleCallback.run()不会被执行,并且idleCallback其实也是为空。
1.从 OkHttpClient().newCall(request).execute() 开启同步请求任务。
2.得到的 RealCall 对象作为 Call 的唯一实现类,其中同步方法 execute() 是阻塞的,调用到会立即执行 阻塞 到有结果返回,或者发生错误 error 被打断阻塞。
3. RealCall 中同步 execute() 请求方法被执行,而此时 OkHttpClient 实例中的异步任务分发器 Dispatcher 会将请求的实例 RealCall 添加到双端队列 runningSyncCalls 中去。
4.通过 RealCall 中的方法 getResponseWithInterceptorChain() 开启请求拦截器的责任链,将请求逐一下发,通过持有 index 并自增操作,其次除 ConnectInterceptor 与链尾 CallServerInterceptor 其余默认拦截器均有以 chain.proceed(request) 为分界点的前序与后序操作,拿到 response 后依次处理后序操作。
5.最终返回结果 response 之前,对进行中的同步任务做了移除队列的操作也即 finally 中 client.dispatcher.finished(this) 方法,最终得到的结果 response 返回到客户端,至此整个 同步请求 流程就结束了。
Github
Square
以上是关于「okhttp3 4.9.3 版本简单解析」的主要内容,如果未能解决你的问题,请参考以下文章
Jaeger-3.实现一个分布式调用(OkHttp+SpringBoot)