Okhttp的使用
源码
阅读大纲
- 同步调用网络请求流程
- 异步调用网络请求流程
- Dispatcher类的代码逻辑
- 几个拦截器的逻辑
查看同步调用主流程
- 从newCall开始
调用RealCall.newRealCall创建Call
- RealCall.newRealCall
- 因为上一步中获取到的Call类型是RealCall,下面我们看看RealCall.execute
下一步我们先看dispatcher().executed然后再看getResponseWithInterceptorChain方法
- dispatcher().executed方法和getResponseWithInterceptorChain方法
dispatcher().executed方法
getResponseWithInterceptorChain方法
这个方法里面先构建了一个连接器,然后执行拦截器
- 同步调用完成
异步调用网络请求流程
- 业务代码中执行异步请求
- RealCall.enqueue
这个方法最终又调到了Dispatcher.enqueue
- Dispatcher.enqueue调度异步请求AsyncCall
- 上面的方法如果调用了executeorService.execute就开始执行线程池了,因为我们的AsyncCall实现了NameRunnable,然后NameRunnable又实现了Runnable,所以去看NameRunnable.run方法吧
- AsyncCall.execute执行请求
Dispatcher类的代码逻辑
Dispatcher的作用是对请求进行分发,也就是他是用来管理Call和分发Call的。同时他还维护了一个线程池
Dispatcher内部维护了三个队列:
- runningSyncCalls同步请求队列 - 用于执行同步请求
- runningAsyncCalls异步请求队列 - 用于执行异步请求
- readyAsyncCalls等待中的请求队列 - 当异步请求并发数量达到限制时将请求放到等待队列中
Dispatcher如何调度异步逻辑
Dispatcher中没有太多的复杂逻辑,主要对其中的几个重要方法进行说明一下
- executorService获取线程池
- setMaxRequests设置支持的最大请求并发数,这个主要是相对于异步请求来说的,和同步请求没关系
- enqueue 调度异步请求
- cancelAll 取消所有请求
- promoteCalls 决定是否将等待队列中的请求加入到异步队列中进行执行
- executed 执行同步请求
- finished 两个finish方法,分别用来结束同步请求和异步请求
拦截器链的执行逻辑和几个拦截器内部的逻辑
拦截器执行逻辑也就是getResponseWithInterceptorChain方法
- 构建和执行责任链
需要说明的是client.interceptors()获取的拦截器无论如何都会被调用 而client.networkInterceptors()获取的拦截器只有请求走到真实网络请求之前才会调用(如果请求使用了缓存,或者真实请求开始之前结束了则不会调用)
- RealInterceptorChain.proceed方法
最终这里的逻辑可以保证对责任链进行执行
RetryAndFollowUpInterceptor重试,重定向拦截器
重定向拦截器实际上就是在内部维护了一个循环。如果当前请求支持重定向,那么会在当前的责任链位置重新执行责任链(会重试多次)。还有就是,它会尝试重用之前的连接
- 看intercept方法
下图中sameConnection实际上就是判断了一下请求的绝对host,port,scheme是否一致
BridgeInterceptor 桥拦截器
- 桥拦截器会从用户的请求参数中,构建请求Request。主要是是对header中的内容进行补充完善,这一点帮我们省去了很多事情(比如使用HttpUrlconnection很多header参数需要我们自己处理)。
- 然后使用这个请求访问网络,最后使用网络返回结果构建响应
intercept方法解读
- 处理请求header
- 请求获取响应并处理响应
CacheInterceptor缓存拦截器
主要做了如下几件事
- 如果无法网络获取请求且缓存为空返回请求失败
- 如果无法通过网络获取请求,且有缓存直接返回缓存
- 如果请求结果返回304,说明缓存仍然在有效期内(返回的response中没有实体内容),直接把缓存返回并且刷新缓存
- 如果网络请求返回了实体内容,并且请求是支持缓存的,则写入缓存
intercept源码
缓存拦截器会根据请求信息来决定是否使用缓存数据,并且在请求完成后决定是否存储缓存
ConnectInterceptor连接拦截器
ConnectInterceptor用来创建或者获取缓存中的请求连接,其实就是三次握手。不过如果缓存池中有可用连接则不需要进行三次握手,这就是连接器中主要功能---节省我们的三次握手时间
简述一下获取连接的过程(读代码的时候跟着这个思路来做):
- 先检查StreamAllocation类中当前的connection是否可用,如果不可用则关闭它的socket
- 尝试从连接池中获取连接赋值给StreamAllocation.connection,如果获取成功了则直接复用
- 如果第2步中没有获取成功,并且请求是http2,则尝试再次使用route获取多路复用的连接
- 如果第三部仍然没有获取到连接,则创建一个连接并加入到缓存池中
- 如果连接是第4步中创建的,则需要进行三次握手连接后才能使用
- 如果我们第5步中连接创建完成后,发现已经有其它请求创建了连接,则我们将我们自己创建的连接干掉,然后使用其它请求创建的连接进行返回。
- intercept代码
- newStream方法
- findHealthyConnection循环获取可用连接
- 最后一步findConnection真实的获取连接的逻辑,方法非常长
findConnection会有两次尝试从连接池中获取连接,第一次是不通过route去获取。而第二次是通过route去获取。
会有第二次获取的原因是http2有了多路复用 的概念,每个连接可以有多个stream的
http2多路复用就是同一个tcp链接可以并发执行多个http请求。开发过程中我还没见过http2呢,所以这里也不用太过深究
CallServerInterceptor请求网络拦截器
intercept方法
总结
Okhttp可以使用同步和异步请求两种方式请求网络来获取数据,请求的过程都是构建请求参数,构建Call对象,然后同步或者异步获取返回数据。
同步和异步请求的方式最终都会将Call调度到Dispatcher类中,
它们的不同之处在于:
同步请求方式会把Call加入到同步请求队列中然后执行请求。
而异步请求会可能会把请求放到正在执行中的异步请求队列中,也可能加入到异步请求准备队列中。如果我们当前正在执行的异步请求数量大于最大并发数,则会把请求加入到准备队列中。每一个异步请求执行完成后都会尝试把准备队列中的请求加入到正在执行的队列中。
需要说明的是异步请求使用线程池执行
两种方式最终都会调用getResponseWithInterceptorChain拿到最终的数据。
getResponseWithInterceptorChain里面维护了一个责任链。这个责任链里面有多个连接器,包括1重试拦截器、2桥拦截器、3缓存拦截器、4连接拦截器、5网络请求拦截器。我们的请求参数会经过这些拦截器的处理,过程就是从1->5,返回Response结果的时候会再从5->1。
实际上还包括自定义拦截器
后面的过程就是将数据返回给调用处了,同步请求直接返回结果,异步请求使用回调的方法回调结果。
拦截器RealInterceptorChain为什么不使用遍历的方式执行
- 遍历的方式不容易处理中断和异常的情况
- 我们的责任链执行有两个方向,正向执行的时候将请求参数向前传递处理。反向的时候将response回传。这一点遍历的方式是做不到的
- 重试的机制,比如我们的责任链每向前一次都会new一个RealInterceptorChain,这样如果第一次执行后发现请求失败了,我们想重试就会重新从RetryAndFollowUpInterceptor位置new一个RealInterceptorChain进行第二次执行。这样第一次请求新建的几个责任链单元的缓存数据不会影响我们第二次执行。如果使用遍历的方式就不可能达到这个效果。
例如当你使用遍历的方式实现责任链,想重试的时候当请求走得到CacheInterceptor,发现CacheInterceptor中包含了我们上一次执行的逻辑残留,他就需要处理付出更多的逻辑处理这种逻辑残留,无形中增加了工作量。