引言
说到Android开发中用到的网络库,OkHttp
可谓是不可不提的一个,它是由Square 公司为 JVM、Android 和 GraalVM 精心设计的 HTTP 客户端,Google官方也在源码当中的 HttpURLConnection
的底层实现改成 了OkHttp
尽管我们在进行网络请求时通常情况是使用的对于它二次封装版本Retrofit
,但归根结底依靠的还是OkHttp
,包括像其他一些库在关于网络请求方面(例如Glide)都对它做了支持,因而了解它是对于我们来说也是至关重要的
OkHttp使用起来方便,优点也很多:
- HTTP/2支持,允许对同一个主机的所有请求共享一个套接字
- 使用连接池降低请求延迟 (HTTP/2不可用情况下)
- 支持gzip压缩,以便通过减少网络数据的大小来提高网络效率
- 自动重试重定向,在请求失败时自动重试请求,从而提高请求可靠性
- 自动处理请求结果的缓存,避免网络重复请求,以便最大化网络效率
概述
本文基于OkHttp 4.12.0网络请求的执行流程进行分析,OkHttp
主要分为四个角色,分别是OkHttpClient、Request
、Call
、Response
角色 | 含义 |
OkHttpClient | 网络请求客户端 |
Request | 网络请求对象 |
Call | 一次具体的网络请求发起者 |
Response | 网络请求返回的数据结果 |
使用流程可简单分为以下三步:
- 通过建造者模式来分别创建出
OkHttpClient
和Request
对象 - 调用
OkHttpClient
的newCall
方法构建出Call
对象 - 使用
Call
对象的同步请求方法execute
或者是异步请求方法enqueue
发起请求获取到Response
val client = OkHttpClient.Builder().build()
val request = Request.Builder().url("https://wanandroid.com").build()
val call =client.newCall(request)
val response =call.execute()
从上面可以看到,OkHttp的使用还是比较简单的,本文重点分析请求流程,在分析前这里先介绍另外一个角色就是调度器Dispatcher
。
HttpClient
的内部持有一个Dispatcher
对象,它主要是用来对异步请求的策略管理,默认设置最多存在的请求数量为64,同一主机最大请求数为5,所以一旦不满足其中一个,那么该请求就不会立马得到处理。Dispatcher
内部有三个ArrayDeque
(用数组实现的双端队列)分别是:
- 正在执行的同步请求队列
runningSyncCalls
- 准备执行的异步请求队列
readyAsyncCalls
- 正在执行的异步请求队列
runningAsyncCalls
同步请求execute
通过如下代码发起同步请求
client.newCall(request).execute()
接下来看这个execute
方法的具体实现
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
// 这里主要是个监听器,表示开始进行网络请求了
callStart()
try {
//将自身加入runningSyncCalls中
client.dispatcher.executed(this)
// 获得相应结果
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
直接看try语句块的代码,一共就两行:
第一句的调用在dispatcher的实现中是往runningSyncCalls
加入元素(也就是RealCall
)
第二句调用getResponseWithInterceptorChain
返回 Resopnse
同步请求只用了两代码行就完成了,是不是很简单呢?重点是getResponseWithInterceptorChain
,这个内部具体实现先不管,先来分析异步请求
异步请求enqueue
使用enqueue方法发起异步请求,传入callback接收请求结果
client.newCall(request).enqueue(CallBack)
在enqueue
实现中会将我们在外部传入的Callback
包装成一个AsyncCall
对象,作为参数执行Dispatcher
的enqueue
方法。AsyncCall
是我们通过OkHttpClient.newCall
构建出的RealCall
对象的内部类,所以也就天然有RealCall
对象包括其内部字段的直接引用,它还实现了Runnable
接口,便于后续被放入线程池中执行。
来到Dispatcher
的相关方法
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
//查找当前是否有指向同一 Host 的异步请求
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//检查当前的网络请求数量是否超过了最大请求数量
if (runningAsyncCalls.size >= this.maxRequests) break
//检查当前call的主机的请求是否超过最大限制
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue
i.remove()
//标记当前call主机数+1
asyncCall.callsPerHost.incrementAndGet()
//加入到可请求队列中
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
if (executorService.isShutdown) {
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.callsPerHost.decrementAndGet()
synchronized(this) {
runningAsyncCalls.remove(asyncCall)
}
asyncCall.failRejected()
}
idleCallback?.run()
} else {
//将所有符合条件的可执行请求加入线程池中等待执行
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
}
return isRunning
}
在Dispatcher
中首先将它加入readyAsyncCalls
双端队列中。然后就是策略过滤出该队列中全部的AsyncCall
,放入线程池去执行,即触发AsyncCall
的 run()
方法。
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
的run
方法中,也是通过getResponseWithInterceptorChain
方法来获取Response
,最后通过之前包装的Callback
将执行结果返回到外部去。
获取请求结果
不管是同步还是异步请求最后获取请求结果都是通过走getResponseWithInterceptorChain
这个方法,而它顾名思义就是以拦截器链来获取请求结果的。它的这个设计思想就有点类似于我们Andrid当中的触摸事件TouchEvent
的传递,又或者是说是设计模式当中的责任制模式。
具体实现如下:
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
)
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)
}
}
}
总的来说主要的步骤是将OkHtttpClient中定义好的Interceptor放入空集合中,包含我们用户自定义的普通拦截器和网络拦截器,然后再加入内置的几个已实现好的Interceptor构建出一个完整的拦截器集合,然后让拦截器链(Interceptor.Chain
)去处理它
RealInterceptorChain
是拦截器链的具体实现,在构建出完整的拦截器集合后紧接着就是创建RealInterceptorChain
对象,最后就是调用它的proceed
方法得到Response
注意自定义拦截器和OkHttp的内置拦截器在整个拦截器链中的位置
OkHttp五大拦截器
RetryAndFollowUpInterceptor
重试重定向拦截器用于请求失败的重试工作以及重定向的后续请求工作,同时还会对连接做一些初始化工作。
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newRoutePlanner = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newRoutePlanner, chain)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
//1.获取响应
response = realChain.proceed(request)
newRoutePlanner = true
} catch (e: IOException) {
// 尝试检查重试
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newRoutePlanner = false
continue }
response = response.newBuilder()
.request(request)
.priorResponse(priorResponse?.stripBody())
.build()
val exchange = call.interceptorScopedExchange
//跟踪重定向。构建后续的Request
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
//如果请求时一次性的则放弃重试
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body.closeQuietly()
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
请求失败自动重试并且根据需要进行重定向。这里面有个死循环一直在执行请求,通过返回Resopnse或者抛出异常的形式结束循环。重试就是请求失败捕获到IO异常时,在这个请求连接还未关闭时(IO异常不属于ConnectionShutdownException
时),通过recover
方法去判断是否能够进行重试。重定向就是通过followUpRequest
尝试构建重定向请求来重新请求,如果这个重定向请求方法返回的Request
为null
或者RequesBody
是一次性的话(isOneShot
)就直接返回这次的Response
。否则就是重定向次数加一,进行重定向请求,重定向有个最大次数20次,超过的话就直接丢出异常(ProtocolException
)
BridgeInterceptor
桥接拦截器,主要用于补全请求头+gzip
解析。这块的介绍就不贴代码了,这里简要地说下逻辑:在请求之前,它向我们的请求头中添加必要的参数,在拿到请求的响应后,根据响应头中的参数去判断是否需要 gzip
解析,如果需要则通过 GzipSource
去解析。
CacheInterceptor
用于网络缓存,我们可以通过OkHttpClient.cache()
方法来配置缓存。在讲它之前先来了解下HTTP的缓存规则,可以按照其行为将其分为两大类:
- 缓存:浏强览器并不会将请求发送给服务器。强缓存是利用
http
的返回头中的Expires
或者Cache-Control
两个字段来控制的,用来表示资源的缓存时间 - 协商缓存:浏览器会将请求发送至服务器。服务器根据
http
头信息中的Last-Modify/If-Modify-Since
或Etag/If-None-Match
来判断是否命中协商缓存。如果命中,则http
返回码为304
,客户端从本地缓存中加载资源
接着来看看 CacheInterceptor
拦截器做了哪些工作吧
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
//本地的缓存
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
//需要发起的请求
val networkRequest = strategy.networkRequest
//本地的缓存
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(cacheResponse.stripBody())
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
var networkResponse: Response? = null
try {
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body.closeQuietly()
}
}
//协商缓存
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(cacheResponse.stripBody())
.networkResponse(networkResponse.stripBody())
.build()
networkResponse.body.close()
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(cacheResponse?.stripBody())
.networkResponse(networkResponse.stripBody())
.build()
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
listener.cacheMiss(call)
}
}
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
}
}
}
return response
}
缓存拦截器整的工作流程总的来说就是:首先根据本次请求的Request先从磁盘获取缓存,然后根据缓存策略重新计算出需要通过网络发起的请求(networkRequest
)和可用的缓存(cacheResponse
)。后面的话就是通过这两个来判断处理了,假如二者都为null,那么就直接构建出一个504错误码失败的Resopnse返回;如果networkRequest
为null,cacheResponse
不为null,那么就使用缓存返回;如果前面两步都没走到的话,那么就会拿networkRequest
开始网络请求,服务器返回请求后,如果刚开始通过缓存策略计算出的cacheResponse
不为null,而且服务器返回的状态码是304说明资源没有变换,那就还是使用缓存返回。不是的话就使用本次的响应,然后更新磁盘cache
。
ConnectInterceptor
连接拦截器,获取连接 Exchange,传递到下一给拦截器处理
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
//获取连接 Exchange:数据交换(封装了连接)
val exchange = realChain.call.initExchange(realChain)
//根据Exchange对象复制创建一个新的链
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
连接拦截器的代码很简单,主要逻辑在initExchange方法中
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(this) {
check(expectMoreExchanges) { "released" }
check(!responseBodyOpen)
check(!requestBodyOpen)
}
//这里的exchangeFinder的注入是在重试拦截器中完成的
val exchangeFinder = this.exchangeFinder!!
//查找连接Realconnection
val connection = exchangeFinder.find()
//返回ExchangeCodec
val codec = connection.newCodec(client, chain)
// Exchange:数据交换器 包含了exchangecodec与Realconnection
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
synchronized(this) {
this.requestBodyOpen = true
this.responseBodyOpen = true
}
if (canceled) throw IOException("Canceled")
return result
}
稍微介绍下上面的Exchange和ExchangeCode
Exchange
传递单个 HTTP
请求和响应对,在 ExchangeCode
的基础上担负了一些管理及事件分发的作用
ExchangeCode
负责对 request
编码及解码 Response
,即写入请求及读取响应,我们的请求及响应数据都是通过它来读写
CallServerInterceptor
最后一个拦截器,与服务器进行通信。面对比较大的请求体时,会先去询问服务器是否接收此请求体,如果服务器接收并返回响应码200,则继续发送请求体,否则直接返回。
结语
通过本文的分析可以看出,整个的网络请求实现就在这五个内置的拦截器中,它们的协作就好比是生产流水线那样,将网络请求经过自己的工序处理后交给下一环节,较为不同的是,生产出的产品Response又会由里向外地传递。
对于
ConnectInterceptor
和CallServerInterceptor
这两者是实际完成网络请求的地方,涉及到一些底层的东西就简单概过了