不开心,就算长生不老也没用,开心,就算只能活几天也足够!——《大话西游之月光宝盒》
前言
本文从源码角度分析OkHttp的工作流程,源码基于4.4.0。
OkHttp基本实现原理
OkHttp的内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦。
OkHttp使用流程
第一步:创建OkHttpClient
OkHttpClient是okhttp3框架的客户端,用于发送http请求(Requests)和读取读取网络返回数据(Responses)。因为OkHttpClient拥有自己的连接池和线程池,所以官方建议使用单例创建OkHttpClient,即一个进程中只创建一次即可,以后的每次网络请求都使用该实例。这样做利于减少延迟和节省内存。
创建OkHttpClient的两种方式:
//方式一:使用默认网络配置创建OkHttpClient实例
OkHttpClient client = new OkHttpClient();
//方式二:通过构建者模式自定义网络配置创建OkHttpClient实例
OkHttpClient client = new OkHttpClient.Builder()
...//通过Builder提供的方法设置网络参数
.build();
OkHttpClient.Builder类的部分变量如下所示,通过其提供的方法可以设置部分参数的值
//调度器,实现同步 /异步的关键,后文会进行讲解
internal var dispatcher: Dispatcher = Dispatcher()
//连接池
internal var connectionPool: ConnectionPool = ConnectionPool()
//各种拦截器
internal val interceptors: MutableList = mutableListOf()
internal val networkInterceptors: MutableList = mutableListOf()
//连接失败后自动重试,默认true
internal var retryOnConnectionFailure = true
internal var authenticator: Authenticator = Authenticator.NONE
internal var followRedirects = true
internal var followSslRedirects = true
//网络请求Cookie
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
//网络请求的缓存
internal var cache: Cache? = null
internal var dns: Dns = Dns.SYSTEM
internal var proxy: Proxy? = null
internal var proxySelector: ProxySelector? = null
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var x509TrustManagerOrNull: X509TrustManager? = null
internal var connectionSpecs: List = DEFAULT_CONNECTION_SPECS
internal var protocols: List = DEFAULT_PROTOCOLS
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
internal var certificateChainCleaner: CertificateChainCleaner? = null
internal var callTimeout = 0
//网络连接超时时间
internal var connectTimeout = 10_000
//数据读取超时时间
internal var readTimeout = 10_000
//数据写入超时时间
internal var writeTimeout = 10_000
internal var pingInterval = 0
internal var routeDatabase: RouteDatabase? = null
第二步:创建网络请求描述对象Request
Request对象通过参数设置用于描述一次网络请求。它的实例创建同样采用了构建者模式,创建Request实例的代码如下:
Request request = new Request.Builder().
url("换成要请求的url地址").
...//通过Builder提供的方法设置网络参数
build();
Request.Builder()的部分源码如下:
public open class Builder {
public constructor() { /* compiled code */ }
internal constructor(request: okhttp3.Request) { /* compiled code */ }
internal final var body: okhttp3.RequestBody? /* compiled code */
internal final var headers: okhttp3.Headers.Builder /* compiled code */
internal final var method: kotlin.String /* compiled code */
internal final var url: okhttp3.HttpUrl? /* compiled code */
...//省略了部分对外提供的参数设置方法
}
可见通过Request.Builder()可以设置如下常用的网络参数:
headers:请求头;
body:请求体;
method:请求方式,比如:get/post/put...等请求方式;
url:请求地址;
第三步:创建网络请求对象Call
Call为一个接口,其实现类为RealCall。一个Call对象封装表示了一次网络请求,通过它能够执行或者取消一次网络请求,并且每一次网络请求都会生产一个新的Call对象,同一个Call对象不能被执行两次。Call对象的创建需要用到前面创建好的OkHttpClient对象的newCall(Request request)方法,并需要将第二步创建好的用于描述此次网络请求的Request对象作为参数传进去,整体代码如下:
OkHttpClient client = new OkHttpClient.Builder()
...//通过Builder提供的方法设置网络参数
.build();
Request request = new Request.Builder().
url("换成要请求的url地址").
...//通过Builder提供的方法设置网络参数
build();
Call call = client.newCall(request);
我们来看OkHttpClient的newCall(Request request)方法,其源码如下:
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
可见最后创建的是RealCall对象。
第四步:通过Call对象执行网络请求
okhttp3中提供了两种网络请求方式:一种是同步请求,调用call.execute()方法;另一种是异步请求,调用call.enqueue(Callback callback)方法。在分析两种请求方式之前,先来看看OkHttp中一个重要成员Dispatcher(调度器)。
Dispatcher(调度器)
Dispatcher是OkHttp的任务调度的核心类,负责管理同步和异步的请求,并管理每一个请求任务的请求状态,其内部维护了一个线程池用于执行相应的请求。Dispatcher定义默认的最大并发请求量 maxRequests = 64 和单个host支持的最大并发量 maxRequestsPerHost = 5。并且在其内部维护了三个双端阻塞队列用于管理网络任务,代码如下:
class Dispatcher constructor() {
...
//默认的最大并发请求量为64
@get:Synchronized var maxRequests = 64
...
//默认单个host支持的最大并发量为5
@get:Synchronized var maxRequestsPerHost = 5
...
/** 存储准备好的异步调用任务,它们将顺序执行 */
private val readyAsyncCalls = ArrayDeque()
/**存储异步运行中的网络任务,包括已经被取消但还未结束的网络任务*/
private val runningAsyncCalls = ArrayDeque()
/**存储同步运行中的网络任务,包括已经被取消但还未结束的网络任务*/
private val runningSyncCalls = ArrayDeque()
...
}
关于这3个双端阻塞队列的作用,可以这么理解:把Dispatcher当成生产者,把线程池当成消费者,当生产者生产的网络请求数量大于消费者所能承受的最大范围,就把未能及时执行的任务保存在readyAsyncCalls队列中,当时机成熟,也就是线程池有空余线程可以执行时,会调用promoteCall()这个方法把等待队列中的任务取出放到线程池中执行,并且把这个任务转移到runningAsyncCalls或者runningSyncCalls 队列中去。
为什么要使用双端队列?很简单因为网络请求执行顺序跟排队一样,讲究先来后到,新来的请求放队尾,执行请求从对头部取。我们知道LinkedList同样也实现了Deque接口,内部是用链表实现的双端队列,那为什么不用LinkedList呢?实际上这与readyAsyncCalls向runningAsyncCalls转换有关,当执行完一个请求或调用enqueue方法入队新的请求时,会对readyAsyncCalls进行一次遍历,将那些符合条件的等待请求转移到runningAsyncCalls队列中并交给线程池执行。尽管二者都能完成这项任务,但是由于链表的数据结构致使元素离散的分布在内存的各个位置,CPU缓存无法带来太多的便利,另外在垃圾回收时,使用数组结构的效率要优于链表。
接下来咱们结合上面说的Dispatcher分别看看同步请求和异步请求的实现过程,并详细说一下他们是如何实现的。
请求方式一:同步请求
call.execute()源码如下:
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
timeout.enter()
callStart()
try {
//①调用调度器dispatcher的executed(Call call)方法
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
//②调用分配器Dispatcher的finish()方法
client.dispatcher.finished(this)
}
}
注释①部分调用了 client.dispatcher的executed()方法,再来看client.dispatcher的executed(call: RealCall)方法:
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
由此可见,注释①部分是将这次网络请求RealCall实例存进了runningSyncCalls这双端阻塞队列;
接着方法又调用了getResponseWithInterceptorChain()方法并返回。关于getResponseWithInterceptorChain()先放一边,在异步请求中再一起讲解。
接下来看注释②,不管结果交易结果如何,都会调用finally中的client.dispatcher.finished(this)将本次请求从队列中移除。
请求方式二:异步请求
call.enqueue(Callback callback)源码如下:
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
callStart()
//①调用调度器dispatcher的enqueue(AsyncCall call)方法
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
可见其内部调用了client.dispatcher.enqueue(AsyncCall call)方法,分析该方法之前先来看看其参数AsyncCall类的定义:
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
private set
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
val host: String
get() = originalRequest.url.host
val request: Request
get() = originalRequest
val call: RealCall
get() = this@RealCall
/**
* 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)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
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 {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
}
跟踪源码发现AsyncCall是RealCall的一个内部类,其实现了Runnable接口,所以AsyncCall就是一个Runnable的实现,当在线程池中执行该对象时就会执行其实现的run()方法,通过注释①处源码了解到和同步请求方法一样它也会去调用getResponseWithInterceptorChain()方法。
现在回到client.dispatcher.enqueue(AsyncCall call)方法,其源码如下:
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// ①尝试判断并复用同一host请求的实例
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
可见方法会先将请求放入readyAsyncCalls双端阻塞队列,然后会在注释①出尝试判断并复用同一host请求的实例,findExistingCallWithHost(String host)源码如下:
private fun findExistingCallWithHost(host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) return existingCall
}
for (existingCall in readyAsyncCalls) {
if (existingCall.host == host) return existingCall
}
return null
}
其作用为循环遍历readyAsyncCalls和runningAsyncCalls列表寻找host值相同的call实例。如果找到Call实例就会调用call.reuseCallsPerHostFrom(AsyncCall other)方法,其源码如下:
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
private set
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
}
其作用为给AsyncCall实例的成员变量callsPerHost赋值,即共享同一host下AsyncCall请求实例的AtomicInteger值。
接着client.dispatcher.enqueue( AsyncCall call)最终会调用promoteAndExecute()方法,其源码如下:
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//遍历readyAsyncCalls
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.
//符合条件 从readyAsyncCalls列表中删除
i.remove()
//per host 计数加1
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
//移入runningAsyncCalls列表
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//①提交任务到线程池
asyncCall.executeOn(executorService)
}
return isRunning
}
可见其主要逻辑为遍历readyAsyncCalls队列,其中满足阈值校验的AsyncCall对象被移至runningAsyncCalls队列同时提交到线程池执行。注释①中可见提交到线程池使用到了executorService对象,现在来看看executorService的定义:
@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!!
}
这不是一个newCachedThreadPool吗?没错,除了最后一个threadFactory参数之外与newCachedThreadPool的源码实现一模一样,只不过是多设置了线程名字方便排查问题。其阻塞队列采用的SynchronousQueue,它的特点是不存储数据,当添加一个元素时,必须等待一个消费线程取出它,否则一直阻塞,如果当前有空闲线程则直接在这个空闲线程执行,如果没有则新启动一个线程执行任务。通常用于需要快速响应任务的场景,在网络请求要求低延迟的大背景下比较合适。可见这个executorService对象就是Dispatcher自身维护的用于执行网络任务的线程池。
接着来看注释①中执行的AsyncCall.executeOn(ExecutorService executorService)方法的源码:
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)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
可见其功能为将AsyncCall对象加入到executorService线程池并执行,并在线程结束或者执行失败后清除AsyncCall请求对象。通过上面对AsyncCall类的分析可知,AsyncCall类是一个Runnable的实现,将asyncCall对象加入线程池执行会去执行其内部实现的run()方法并最终调getResponseWithInterceptorChain()
方
法来获取网络请求的返回值。AsyncCall内部实现的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 {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
在分析获取网络请求返回值getResponseWithInterceptorChain()方法之前再插一句,通过上面的分析,不管同步请求还是异步请求,在请求正常或者异常结束后都会调用了client.dispatcher.finished(call: RealCall)或client.dispatcher.finished(call: AsyncCall)方法,那我们就来看看其源码做了什么:
//异步任务执行结束
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
//同步任务执行结束
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
//同步异步任务 统一汇总到这里
private fun finished(calls: Deque, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//将完成的任务从队列中删除
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//这个方法在前面已经分析过了,用于将readyAsyncCalls队列中的请求移入runningAsyncCalls队列中,并交由线程池执行。
val isRunning = promoteAndExecute()
//如果没有请求需要执行,回调闲置callback
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
方法的主要逻辑已在注释中写出,需要注意的是promoteAndExecute()方法在enqueue和finish方法中都会调用,即当有新的请求入队和当前请求完成后,需要重新提交一遍任务到线程池。
到目前为止我们通过源码分析了解到,其实OkHttp的同步请求call.execute()和异步请求call.enqueue(Callback callback)其实都是通过OkHttpClient中维护的Dispatcher调度器对象来实现的,而异步任务又是队列的方式在Dispatcher调度器对象自身维护的ExecutorService线程池对象中按顺序执行的。不同网络任务的执行状态又分别保存在Dispatcher调度器对象自身维护的3个不同的双端阻塞队列中。
下面终于到了OkHttp原理的重点和难点部分了,OkHttp的内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦。而这个责任链模式的具体实现就在getResponseWithInterceptorChain()方法中。
getResponseWithInterceptorChain方法(OKHTTP责任链模式的实现)
getResponseWithInterceptorChain方法是整个OkHttp实现责任链模式的核心。下面来看看其源码:
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
//创建拦截器数组
val interceptors = mutableListOf()
//添加应用拦截器
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
)
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)
}
}
}
这里我们先不关心每个拦截器具体作用是什么,主流程最终走到chain.proceed(originalRequest)。我们先来看一下这个procceed方法:
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
// 统计当前拦截器调用proceed方法的次数
calls++
if (exchange != null) {
// exchage是对请求流的封装,在执行ConnectInterceptor前为空,连接和流已经建立但此时此连接不再支持当前url说明之前的网络拦截器对url或端口进行了修改,这是不允许的!!
check(exchange.connection.supportsUrl(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
// 这里是对拦截器调用proceed方法的限制,在ConnectInterceptor及其之后的拦截器最多只能调用一次proceed!!
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// 创建下一层责任链 注意index + 1
val next = copy(index = index + 1, request = request)
//取出下标为index的拦截器,并调用其intercept方法,将新建的链传入。
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
if (exchange != null) {
// 保证在ConnectInterceptor及其之后的拦截器至少调用一次proceed!!
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
//保证未返回含有空body值的response
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
代码中的注释已经写得比较清楚了,总结起来就是创建下一级责任链,然后取出当前拦截器,调用其intercept方法并传入创建的责任链。++为保证责任链能依次进行下去,必须保证除最后一个拦截器(CallServerInterceptor)外,其他所有拦截器intercept方法内部必须调用一次chain.proceed()方法++,如此一来整个责任链就运行起来了。
比如ConnectInterceptor源码中:
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//创建连接和流
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
//执行下一级责任链
return connectedChain.proceed(realChain.request)
}
}
除此之外在责任链不同节点对于proceed的调用次数有不同的限制,ConnectInterceptor拦截器及其之后的拦截器能且只能调用一次,因为网络握手、连接、发送请求的工作发生在这些拦截器内,表示正式发出了一次网络请求;而在这之前的拦截器可以执行多次proceed,比如错误重试。
经过责任链一级一级的递推下去,最终会执行到CallServerInterceptor的intercept方法,此方法会将网络响应的结果封装成一个Response对象并return。之后沿着责任链一级一级的回溯,最终就回到getResponseWithInterceptorChain方法的返回。
拦截器分类
大致总结责任链的各个节点拦截器的作用:
拦截器 | 作用 |
---|---|
应用拦截器 | 拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。 |
RetryAndFollowUpInterceptor | 处理错误重试和重定向 |
BridgeInterceptor | 应用层和网络层的桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。 |
CacheInterceptor | 缓存拦截器,如果命中缓存则不会发起网络请求。 |
ConnectInterceptor | 连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。 |
networkInterceptors(网络拦截器) | 用户自定义拦截器,通常用于监控网络层的数据传输。 |
CallServerInterceptor | 请求拦截器,在前置准备工作完成后,真正发起了网络请求。 |
参考文章
https://blog.csdn.net/qq_29152241/article/details/82011539
https://www.jianshu.com/p/8bcfb10243a1