探索OkHttp系列 (一) 请求的发起与响应
前言
OkHttp
是个人使用的比较多的网络请求库,但是一直没有探究它的实现原理,这次就对OkHttp
的源码进行分析,探究其实现原理。
分析的okhttp
源码版本:4.9.2
。
基本使用
GET
同步地发起请求,会阻塞线程,不能直接在主线程当中调用
private fun getData() {
thread {
val client: OkHttpClient = OkHttpClient()
val request: Request = Request.Builder()
.get()
.url("https://www.baidu.com")
.build()
val response: Response = client.newCall(request).execute()
val data: String? = response.body?.string()
Log.d(TAG, "OkHttp 同步请求:$data")
}
}
获取OkHttpClient
实例,有两种方法,一是像上面代码一样,直接new
一个OkHttpClient
对象;二是new
一个OkHttpClient
的Builder
对象,可以对client
配置一些参数,如下
val client: OkHttpClient = OkHttpClient.Builder().build()
异步地发起请求,会自动切换线程
private fun getData() {
val client: OkHttpClient = OkHttpClient()
val request: Request = Request.Builder()
.get()
.url("https://www.baidu.com")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val data: String? = response.body?.string()
Log.d(TAG, "OkHttp 同步请求:$data")
}
})
}
POST
POST请求,相比于GET请求,多了一个构造RequestBody
的步骤,并且请求方法要从get
改为post
。
同步请求,会阻塞线程,不能直接在主线程当中调用
private fun getData() {
thread{
val client: OkHttpClient = OkHttpClient()
val requestBody: RequestBody = FormBody.Builder()
.add("username", "EDGNB")
.add("password", "123456")
.build()
val request: Request = Request.Builder()
.url("https://www.wanandroid.com/user/login")
.post(requestBody)
.build()
val response: Response = client.newCall(request).execute()
val data: String? = response.body?.string()
Log.d(TAG, "OkHttp 同步请求:$data")
}
}
异步请求
private fun getData() {
val client: OkHttpClient = OkHttpClient()
val requestBody: RequestBody = FormBody.Builder()
.add("username", "EDGNB")
.add("password", "123456")
.build()
val request: Request = Request.Builder()
.url("https://www.wanandroid.com/user/login")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val data: String? = response.body?.string()
Log.d(TAG, "OkHttp 同步请求:$data")
}
})
}
对象的创建
OkHttpClient
和Request
的构造均使用到了建造者Builder
模式,调用者可直接通过链式调用对这些对象进行自定义初始化。Request
类和OkHttpClient
类的本质都是一个描述对象。
OkHttpClient
前面提到,构造一个OkHttpClient
有两种方式
// 方式一
val client: OkHttpClient = OkHttpClient()
// 方式二
val client: OkHttpClient = OkHttpClient.Builder().build()
我们查看方式一,它调用了下面的构造方法
constructor() : this(Builder())
构造了一个Builder
实例,并调用了主构造函数,OkHttpClient
的成员变量会利用构造的Builder
,进行初始化
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
val dispatcher: Dispatcher = builder.dispatcher
val connectionPool: ConnectionPool = builder.connectionPool
val interceptors: List<Interceptor> =
builder.interceptors.toImmutableList()
...
}
OkHttpClient.Builder
定义的属性如下
class Builder constructor() {
// 调度器
internal var dispatcher: Dispatcher = Dispatcher()
// 连接池
internal var connectionPool: ConnectionPool = ConnectionPool()
// 整体流程拦截器
internal val interceptors: MutableList<Interceptor> = mutableListOf()
// 网络请求拦截器
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
// 流程监听器
internal var eventListenerFactory: EventListener.Factory =
EventListener.NONE.asFactory()
// 连接失败是否自动重试
internal var retryOnConnectionFailure = true
// 服务器认证设置
internal var authenticator: Authenticator = Authenticator.NONE
// http是否重定向
internal var followRedirects = true
// 是否可以从HTTP重定向到HTTPS
internal var followSslRedirects = true
// Cookie策略,是否保存Cookie
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
// 缓存配置
internal var cache: Cache? = null
// Dns配置
internal var dns: Dns = Dns.SYSTEM
// 代理
internal var proxy: Proxy? = null
// 代理选择器
internal var proxySelector: ProxySelector? = null
// 代理服务器认证设置
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
// socket工厂
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
// sslSocket工厂 用于https
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
// 证书信息管理
internal var x509TrustManagerOrNull: X509TrustManager? = null
// 传输层版本和连接协议 TLS等
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
// 支持的协议
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
// 主机名校验
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
// 证书链
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
// 证书确认
internal var certificateChainCleaner: CertificateChainCleaner? = null
// 调用超时时间( 0的含义? )
internal var callTimeout = 0
// 连接超时时间
internal var connectTimeout = 10_000
// 读取超时时间
internal var readTimeout = 10_000
// 写入超时时间
internal var writeTimeout = 10_000
// 针对HTTP2和web socket的ping间隔
internal var pingInterval = 0
internal var minWebSocketMessageToCompress =
RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
// 路由数据库
internal var routeDatabase: RouteDatabase? = null
...
}
Request
Request
用于描述单次请求的参数信息,使用方法如下
val request: Request = Request.Builder()
.get()
.url("https://www.baidu.com")
.build()
在Request.Builder
的build
方法中,利用自身保存的属性,去构造一个Request
对象
open fun build(): Request {
return Request(
checkNotNull(url) { "url == null" },
method,
headers.build(),
body,
tags.toImmutableMap()
)
}
Request.Builder
有如下属性
open class Builder {
// 请求url
internal var url: HttpUrl? = null
// 请求方法
internal var method: String
// 请求头
internal var headers: Headers.Builder
// 请求体
internal var body: RequestBody? = null
/** A mutable map of tags, or an immutable empty map if we don't have any. */
// 请求tag
internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
...
}
发起请求
无论是同步请求
val response: Response = client.newCall(request).execute()
还是异步请求
client.newCall(request).enqueue(...)
都需要先调用OkHttpClient
的newCall
方法,该方法如下
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket =
false)
该方法返回一个RealCall
对象,定义如下
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {...}
它是Call
接口的唯一实现类,内部保存了
- client:也就是前面的
OkHttpClient
- originalRequest:我们前面创建的
Request
,用于描述单次请求的参数信息 - forWebSocket:是否使用
Web Socket
,默认值为false
RealCall
包装了Request
和OKHttpClient
这两个类的实例,使得后面的方法中可以很方便的使用这两者。
插叙:ArrayDeque
我们后面会使用到ArrayDeque
,这里先简单介绍ArrayDeque
是什么,以及它的作用
ArrayDeque
是JDK
的一个容器,它有队列和栈的性质的抽象数据类型,继承结构如下
在Java
中,我们使用栈一般会使用Stack
类,而队列的话,Java
提供了Queue
接口,我们可以使用LinkedList
来实现队列的功能(LinkedList
实现了Deque
接口,Deque
继承了Queue
接口)。
从ArrayDeque
继承结构可以看出,它实现了Deque
接口,因此它也有队列的功能,在ArrayDeque
类中有这样的一段注释
/*
* This class is likely to be faster than
* {@link Stack} when used as a stack, and faster than {@link LinkedList}
* when used as a queue.
* /
它表达了两层意思:
- 当作为「栈」来使用的时候,它的性能比
Stack
更快 - 当作为「队列」来使用的时候,它的性能比
LinkedList
更快
在Stack
类的注释中,我们也可以看到它推荐我们使用ArrayDeque
:
/*
* <p>A more complete and consistent set of LIFO stack operations is
* provided by the {@link Deque} interface and its implementations, which
* should be used in preference to this class. For example:
* <pre> {@code
* Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
*/
ArrayDeque
实现了Deque
接口,Deque
是double-ended queue
的缩写,意为「双端队列」,什么意思呢?我们查看Deque
定义了哪些方法
public interface Deque<E> extends Queue<E> {
/**
* 在 首、尾 添加元素
*/
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
/**
* 移除并返回 首、尾 的元素
*/
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
/**
* 返回(但不移除) 首、尾 的元素
*/
E getFirst();
E getLast();
E peekFirst();
E peekLast();
...
}
我们在Deque
的「首、尾」两端都可以做元素的「插入、删除」操作,比如
- 我们在双端队列的队头插入,队头弹出,那么它就可以作为一个栈来使用
- 我们在双端队列的队头插入,队尾弹出,那么它就可以作为一个队列来使用
或者在双端队列的队尾插入,队头弹出,也是可以作为一个队列来使用
从ArrayDeque
类的注释中,我们还可以得到下面有关ArrayDeque
的信息
- 没有容量限制,根据实际需要自动扩容
- 是线程不安全的
- 不允许放入
null
的元素
另外,ArrayDeque
内部是使用循环数组来实现的。
同步请求
同步请求要调用RealCall
的execute
方法,定义如下
override fun execute(): Response {
// 使用CAS,保证这个RealCall对象只发起一次请求
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
// 将请求添加到任务队列当中
client.dispatcher.executed(this)
// 通过一系列拦截器的请求处理和响应处理,获取返回结果,并return
return getResponseWithInterceptorChain()
} finally {
// 不论是否成功,通知Dispatcher请求完成
client.dispatcher.finished(this)
}
}
我们首先调用了OkHttpClient
的调度器dispatcher
的executed
方法,来将请求添加到任务队列当中,Dispatcher::executed
如下
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
runningSyncCalls
是一个ArrayDeque
,定义如下
private val runningSyncCalls = ArrayDeque<RealCall>()
前面我们已经介绍过ArrayDeque
了,executed
方法调用了runningSyncCalls
的add
方法,我们注意到executed
方法使用了同步锁,使用同步锁的原因是因为ArrayDeque
是线程不安全的。
为什么在OkHttp
中选择ArrayDeque
这种容器作为任务队列?
答:因为ArrayDeque
效率高,在「插叙:ArrayDeque」中,我们提到ArrayDeque
可以作为栈和队列,并且性能优于Stack
、LinkedList
。
client.dispatcher.executed(this)
所做的事情,就是将这个请求添加到一个双端队列中。
回到RealCallback
的execute
方法中,又调用了getResponseWithInterceptorChain
方法,该方法如下
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
// 按顺序添加一系列的拦截器
val interceptors = mutableListOf<Interceptor>()
// 添加用户自定义拦截器
interceptors += client.interceptors
// 添加重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 桥拦截器
interceptors += BridgeInterceptor(client.cookieJar)
// 添加缓存拦截器,从缓存中拿数据
interceptors += CacheInterceptor(client.cache)
// 添加网络连接拦截器,负责建立网络连接
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
// 服务器请求拦截器,负责向服务器发送请求数据、从服务器读取响应数据
interceptors += CallServerInterceptor(forWebSocket)
// 构建一条责任链,注意前面构建的拦截器列表interceptors作为参数传入
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// 执行责任链,得到请求的响应
val response = chain.proceed(originalRequest)
// 判断请求是否取消,若没有取消则返回响应
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
getResponseWithInterceptorChain
方法会按一定的顺序构建拦截器列表interceptors
,接着利用interceptors
创建RealInterceptorChain
对象,该对象是一条责任链,使用了责任链模式,拦截器会按顺序依次调用,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里。
最后在RealCallback
的execute
方法中调用client.dispatcher.finished(this)
,通知dispatcher
请求完成。
这就是一个同步请求发起与响应的过程,关于Dispatcher
和拦截器责任链是如何工作的,稍后分析。
小结
发起一个同步请求的语句是
client.newCall(request).execute()
- 首先调用
OkHttpClient
的newCall
方法,传入Request
,返回一个RealCall
对象 - 调用
RealCall
对象的execute
方法。在该方法中,先调用OkHttpClient
的dispatcher
的executed
方法,将请求加入dispatcher
中的双端队列中;再调用getResponseWithInterceptorChain()
,通过拦截器责任链获取响应,最后通知dispatcher
请求完成。
异步请求
发起异步请求的方法
client.newCall(request).enqueue(object : Callback {...})
这里也是构造一个RealCall
对象,然后调用RealCall
对象的enqueue
方法,传入Callback
。
我们查看RealCall
的enqueue
方法
override fun enqueue(responseCallback: Callback) {
// 使用CAS,保证这个RealCall对象只发起一次请求
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
// 创建AsyncCall对象,然后通过dispatcher分发任务
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
这里创建了一个AsyncCall
对象,构造参数是用户传入的Callback
,然后调用OkHttpClient
的dispatcher
的enqueue
方法,传入我们创建的AsyncCall
对象。
Dispatcher
的enqueue
方法如下
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 将AsyncCall添加到请求队列中
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running
// call to the same host.
if (!call.call.forWebSocket) {
// 寻找同一host的Call
val existingCall = findExistingCallWithHost(call.host)
// 复用Call的callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
// 尝试执行等待队列中的任务
promoteAndExecute()
}
readyAsyncCalls
同样是一个双端队列,其类型为ArrayDeque<AsyncCall>()
,注意,同步任务是添加到runningSyncCalls
当中,异步任务是添加到readyAsyncCalls
当中。
Dispatcher::promoteAndExecute
如下
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
// 创建一个待执行任务列表
val executableCalls = mutableListOf<AsyncCall>()
// 标识是否有任务在执行
val isRunning: Boolean
synchronized(this) {
// 前面就是将异步任务AsyncCall添加到readyAsyncCalls当中
val i = readyAsyncCalls.iterator()
// 遍历异步任务等待队列
while (i.hasNext()) {
val asyncCall = i.next()
// 当前总请求数没有达到上限
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 当前同一主机名请求数没有达到上限
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
// 从等待队列中移除AsyncCall
i.remove()
// 同一主机名请求数+1
asyncCall.callsPerHost.incrementAndGet()
// 将该请求添加到待执行任务列表
executableCalls.add(asyncCall)
// 将该请求添加到执行任务队列
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// 遍历待执行任务列表,传入线程池执行任务
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
到现在为止,我们接触到了三个队列,在这里区分一下
runningSyncCalls
:类型是ArrayDeque<RealCall>
,「同步任务」的执行队列readyAsyncCalls
:类型是ArrayDeque<AsyncCall>
,「异步任务」的等待队列runningAsyncCalls
:类型是ArrayDeque<AsyncCall>
,「异步任务」的执行队列
它们都是ArrayDeque
,双端队列。
AsyncCall
表示一个异步任务,它是RealCall
的一个内部类,并且实现了Runnable
接口,定义如下
internal inner class AsyncCall(
// responseCallback就是用户传入的获取处理结果的回调
private val responseCallback: Callback
) : Runnable {
...
/**
* Attempt to enqueue this async call on [executorService]. This will attempt to
* clean up if the executor has been shut down by reporting the call as failed.
*/
// 对外暴露的接口,通过传入一个线程池来执行该任务
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
// 尝试在线程池中执行该任务
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
// 线程池拒绝执行该任务,抛出异常
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
// 回调onFailure方法,通知用户执行失败
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
// 通知dispatcher请求完成
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
// 通过拦截器责任链获取请求响应
val response = getResponseWithInterceptorChain()
signalledCallback = true
// 通过回调,返回响应结果给用户
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
// 出现异常,回调onFailure方法,通知用户执行失败
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
// 出现异常,回调onFailure方法,通知用户执行失败
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
// 通知dispatcher请求完成
client.dispatcher.finished(this)
}
}
}
}
异步线程池
异步请求在线程池中执行,我们向AsyncCall
的executeOn
方法传入的参数就是「异步任务执行的线程池」,传入的参数是Dispatcher
的executorService
,executorService
对应的线程池可以在构造Dispatcher
的时候指定,若用户没有指定线程池,那么默认是使用下面的线程池
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
构造线程池的几个参数如下
- 核心线程数
corePoolSize
:表示线程池中空闲线程也不会被回收的数量,这里设置为0说明线程池中所有空闲线程都会被回收。 - 最大线程数
maximumPoolSize
:线程池最大支持创建的线程数,这里设置为Int.MAX_VALUE
。 - 线程存活时间
keepAliveTime
:非核心线程空闲后超过该时间就会被回收,这里设置为60个时间单位(60s)。 - 时间单位
TimeUnit
:空闲线程存活的时间单位,这里设置为秒TimeUnit.SECONDS
。 - 线程池中的任务队列
workQueue
:该队列主要用来存储已经被提交但是尚未执行的任务,这里设置为SynchronousQueue()
。 - 线程工厂
ThreadFactory
:线程的创建工厂,这个线程工厂指定了创建的线程的「名字」,以及创建的线程「非守护线程」。
对于上面指定的参数,我们思考下面的几个问题:
为什么要选用SynchronousQueue作为任务队列?
首先我们要了解一下SynchronousQueue
是什么,它是一个队列,但它内部不存在任何的容器,它是一种经典的生产者-消费者模型,它有多个生产者和消费者,当一个生产线程进行生产操作(put)时,若没有消费者线程进行消费(take),那么该线程会阻塞,直到有消费者进行消费。也就是说,它仅仅实现了一个传递的操作,这种传递功能由于没有了中间的放入容器,再从容器中取出的过程,因此是一种快速传递元素的方式,这对于我们网络请求这种高频请求来说,是十分合适的。关于 SynchronousQueue
可以看这篇文章:java并发之SynchronousQueue实现原理_-CSDN博客.
线程池的最大线程数不受限制,可以无限制的进行并发网络请求吗?
在OkHttp
的设计中,线程个数的维护工作不再交给线程池,而是由Dispatcher
实现,通过外部设置的maxRequests
以及maxRequestsPerHost
来调整异步请求的等待队列以及执行队列,从而实现对线程最大数量的控制。Dispatcher
具体实现将在后面分析。
小结
发起一个异步请求的语句是
client.newCall(request).enqueue(object : Callback {...})
- 首先调用
OkHttpClient
的newCall
方法,传入Request
,返回一个RealCall
对象 - 调用
RealCall
对象的enqueue
方法,并传入结果回调Callback
- 在
ReallCall::enqueue
中,利用Callback
构造了一个AsyncCall
对象,并调用了OkHttpClient
的dispatcher
的enqueue
方法,将AsyncCall
作为参数传入 - 在
Dispatcher::enqueue
中,先将AsyncCall
添加到请求队列当中,然后调用自己的promoteAndExecute
方法,尝试执行等待队列中的任务 Dispatcher::promoteAndExecute
方法会遍历异步任务的等待队列,然后尝试在线程池中去执行这些异步任务
异步任务的执行,也是调用getResponseWithInterceptorChain
方法,通过拦截器责任链获取请求的响应,这一点和「同步请求」是一样的。
Dispatcher
前面多次出现了调度器Dispatcher
的身影,这里对它做一个总结。
维护的队列
Dispatcher
中维护了三个队列:
runningSyncCalls
:类型是ArrayDeque<RealCall>
,「同步任务」的执行队列readyAsyncCalls
:类型是ArrayDeque<AsyncCall>
,「异步任务」的等待队列runningAsyncCalls
:类型是ArrayDeque<AsyncCall>
,「异步任务」的执行队列
请求相关
同步请求中,RealCall
的execute
方法会调用到Dispatcher
的executed
方法,将「同步任务」保存到runningSyncCalls
中
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
异步请求中,RealCall
的enqueue
方法会调用Dispatcher
的enqueue
方法
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 将AsyncCall添加到请求队列中
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running
// call to the same host.
if (!call.call.forWebSocket) {
// 寻找同一host的Call
val existingCall = findExistingCallWithHost(call.host)
// 复用Call的callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
// 尝试执行等待队列中的任务
promoteAndExecute()
}
在Dispatcher
的enqueue
方法中,首先将「异步任务」添加到readyAsyncCalls
中,然后调用promoteAndExecute()
方法,该方法会遍历readyAsyncCalls
,寻找能够执行的AsyncCall
,能够执行的条件是「当前总请求数」以及「同一主机名的请求数」都没有达到上限,最后调用能够执行的AsyncCall
的executeOn
方法,在Dispatcher
提供的线程池中执行异步任务。
通知完成
前面我们已经知道,不管是同步请求,还是异步请求,在请求完成后,都会调用Dispatcher
的finished
方法,通知Dispatcher
请求已执行完毕。我们前面没有展开分析Dispatcher
被通知后会做些什么,这里就对它进行分析。
「同步请求」完成后会调用Dispatcher
下面的方法
/** Used by [Call.execute] to signal completion. */
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
「异步请求」完成后会调用Dispatcher
下面的方法
/** Used by [AsyncCall.run] to signal completion. */
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
「异步请求」调用的finished
比「同步请求」调用的finished
多了一个callsPerHost
减1的操作,也就是「主机名请求数」减1的操作。接着,它们都会调用到Dispatcher
下面的这个重载方法
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
// 将「同步任务/异步任务」从「执行队列」当中移除
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
// 尝试执行「等待队列」中的「异步任务」
val isRunning = promoteAndExecute()
// isRunning:在「执行队列」中有「同步/异步任务」待执行
// idleCallback:Dispatcher空闲时的回调
// 若「执行队列」中没有待执行的任务,表明当前的Dispatcher是空闲的,这时候,如果idleCallback
// 不为null,就执行idleCallback的run方法
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
小结
Dispatcher
在 网络请求发起过程中的总结:
Dispatcher
中维护三个任务队列,实现方式都为双端队列,分别为同步请求执行队列,异步请求等待队列,异步请求执行队列。- 当发起一个同步/异步请求,就会将该请求添加到同步请求执行队列/异步请求等待队列中,然后尝试执行这些请求。
- 请求执行完毕就会将这些请求从队列中移除,若后面没有需要执行的任务后,调用
idleCallback.run()
执行一些空闲任务。 - 异步请求执行有两个入口:一是添加了一个异步请求,二是一个请求执行完毕。
请求时序图
同步请求的时序图:
异步请求的时序图:
请求响应
无论是同步请求,还是异步请求,最终都会调用到RealCall
的getResponseWithInterceptorChain
方法,通过该方法可以拿到请求的响应。关于getResponseWithInterceptorChain
方法,我们需要探究两个关键的点
- 核心机制:拦截器机制
- 设计模式:在拦截器中如何运用责任链模式
什么是责任链模式?在典型的责任链设计模式里,很多对象由每一个对象对其下级的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
我们回顾下getResponseWithInterceptorChain
方法
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
// 按顺序添加一系列的拦截器
val interceptors = mutableListOf<Interceptor>()
// 添加用户自定义拦截器
interceptors += client.interceptors
// 添加重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 桥拦截器
interceptors += BridgeInterceptor(client.cookieJar)
// 添加缓存拦截器,从缓存中拿数据
interceptors += CacheInterceptor(client.cache)
// 添加网络连接拦截器,负责建立网络连接
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
// 服务器请求拦截器,负责向服务器发送请求数据、从服务器读取响应数据
interceptors += CallServerInterceptor(forWebSocket)
// 构建一条责任链,注意前面构建的拦截器列表interceptors作为参数传入
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// 执行责任链,得到请求的响应
val response = chain.proceed(originalRequest)
// 判断请求是否取消,若没有取消则返回响应
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
该方法会按一定的顺序构建拦截器列表interceptors
,接着利用interceptors
创建RealInterceptorChain
对象,该对象是一条责任链,使用了责任链模式,拦截器会按顺序依次调用,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里。
Interceptor
我们先看下拦截器Interceptor
的定义
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
companion object {
// 利用lambda,创建一个拦截器
inline operator fun invoke(crossinline block: (chain: Chain) -> Response):
Interceptor = Interceptor { block(it) }
}
interface Chain {
fun request(): Request
@Throws(IOException::class)
fun proceed(request: Request): Response
fun connection(): Connection?
fun call(): Call
fun connectTimeoutMillis(): Int
fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain
fun readTimeoutMillis(): Int
fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain
fun writeTimeoutMillis(): Int
fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
}
}
该接口里面定义了intercept
方法,还有一个Chain
接口,拦截器链RealInterceptorChain
就是实现了Chain
接口。
虽然不同的拦截器实现不同,但是它们往往具有相同的结构:
@Throws(IOException::class)
override fun intercept(chain: Chain): Response {
// 获取Request
val request = chain.request
// 处理:Request阶段,拦截器对Request进行处理
// 调用RealInterceptorChain的proceed方法,在该方法里面,会递归调用下一个拦截器的intercept
// 方法,拿到下一个拦截器的response
val response = (chain as RealInterceptorChain).proceed(request)
// 处理:Response阶段,完成了该拦截器获取Response的过程,将Response返回到上一个拦截器中
return response
}
拦截器链的整体执行流程
我们先来看一个生活中的例子,以便更好地理解「拦截器链」是如何工作的。下面是一个流水线生产的例子
对应到OkHttp
的响应过程就是:包装了Request
的Chain
递归的从每个Interceptor
手中走过去,最后请求网络得到的Response
又会逆序的从每个Interceptor
走回来,把Response
返回到开发者手中。
我们来看看拦截器链RealInterceptorChain
的工作流程
class RealInterceptorChain(
internal val call: RealCall,
private val interceptors: List<Interceptor>,
private val index: Int,
internal val exchange: Exchange?,
internal val request: Request,
internal val connectTimeoutMillis: Int,
internal val readTimeoutMillis: Int,
internal val writeTimeoutMillis: Int
) : Interceptor.Chain {
...
internal fun copy(
index: Int = this.index,
exchange: Exchange? = this.exchange,
request: Request = this.request,
connectTimeoutMillis: Int = this.connectTimeoutMillis,
readTimeoutMillis: Int = this.readTimeoutMillis,
writeTimeoutMillis: Int = this.writeTimeoutMillis
) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
readTimeoutMillis, writeTimeoutMillis)
...
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
...
// Call the next interceptor in the chain.
// copy出一个拦截器链,并且将index设置为(index+1),便于之后「调用责任链的下一个拦截器」
val next = copy(index = index + 1, request = request)
// 获取当前的index对应的拦截器
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
// 执行当前拦截器的intercept方法
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
...
return response
}
}
要很好地理解拦截器链的工作流程,需要将RealInterceptorChain
的proceed
方法与前面拦截器的intercept
方法结合起来看。我们理一下它们的工作流程,其中RealInterceptorChain
简称为Chain
- 在
RealCall
的getResponseWithInterceptorChain
方法中,先调用index
为0的Chain
的proceed
方法,并传入originalRequest
- 在
proceed
方法里,copy
出一个Chain(index+1(1),request)
,然后调用当前index(0)
的Interceptor
的intercept
方法,并且传入新copy
出来的Chain
- 在
Interceptor
的intercept
方法,从Chain
当中获取request
,然后对request
添加一些自己的处理,接着调用Chain
的proceed
方法,传入处理后的request
,等待返回response
- 在
proceed
方法里,copy
出一个Chain(index+1(2),request)
,然后调用当前index(1)
的Interceptor
的intercept
方法,并且传入新copy
出来的Chain
- 在
Interceptor
的intercept
方法,从Chain
当中获取request
,然后对request
添加一些自己的处理,接着调用Chain
的proceed
方法,传入处理后的request
,等待返回response
- 在
proceed
方法里,copy
出一个Chain(index+1(3),request)
,然后调用当前index(2)
的Interceptor
的intercept
方法,并且传入新copy
出来的Chain
- ......
- 最后一个
Interceptor
的intercept
方法处理完后,向上返回处理后的Response
- 倒数第二个
Interceptor
在intercept
方法中,对返回的Response
进行一些加工后向上返回 - 倒数第三个
Interceptor
在intercept
方法中,对返回的Response
进行一些加工后向上返回 - ......
- 最终在
RealCall
的getResponseWithInterceptorChain
方法中,获取到最终Interceptor
处理完后的Response
,然后将该Response
传给用户,通知用户请求成功完成。
在copy
拦截器链的时候,下标index
设置为index+1
就是为了让拦截器责任链能准备地从拦截器容器中取出下一个拦截器进行处理。
整个工作过程就是,当前拦截器对Request
处理完毕后,通过调用传入的责任链的 proceed
方法,将请求交给下一个拦截器处理,然后获得下一个拦截器返回的Response
,再对Response
处理后交给上一个拦截器。
显然在这里使用了一种递归设计,请求从上往下交付,每个拦截器拿到请求都可以对请求做出相应处理,然后交给下一个拦截器,最后一个拦截器处理请求拿到响应后,将响应从下往上交付,每个拦截器拿到响应后再对响应做出响应处理,然后交给上一个拦截器。若中间的过程中若出现了错误,则通过抛出异常来通知上层。
「中间的某个拦截器」也可以选择不将请求Request
交给下一级拦截器处理,这时候「该拦截器」直接返回Response
给上一级的拦截器即可。
Interceptor顺序
从RealCall
的getResponseWithInterceptorChain
方法中,我们可以看出OkHttp
预置了哪些Interceptor
、用户可以自定义哪些Interceptor
,以及这些Interceptor
之间的顺序
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
...
}
在OkHttp
中预置了下面几种Interceptor
:
RetryAndFollowUpInterceptor
:负责实现重定向功能BridgeInterceptor
:将用户构造的请求转换为向服务器发送的请求,将服务器返回的响应转换为对用户友好的响应CacheInterceptor
:读取缓存、更新缓存ConnectInterceptor
:建立与服务器的连接CallServerInterceptor
:从服务器读取响应
可以看出,整个网络请求的过程由各个拦截器互相配合从而实现,通过这种拦截器的机制,可以很方便地调节网络请求的过程及先后顺序,同时也能够很方便地使用户对其进行扩展。
其中用户可以在两个时机插入 Interceptor
:
- 网络请求前后:通过
OkHttpClient.Builder.addInterceptor
方法添加 - 读取响应前后:通过
OkHttpClient.Builder.addNetworkInterceptor
方法添加
其整体流程如图所示:
总结
OkHttp
采用了拦截器机制和责任链模式,它使用多个负责不同功能的拦截器,并将这些拦截器通过责任链连接在一起,采用递归的方式进行调用,每一层的拦截器在请求前和响应后都可以对 请求/响应 做出不同的处理,通过各个拦截器的协调合作,完成了网络请求的响应过程。