OkHttp网络请求流程小结
前言
本文基于OkHttp 4.7.2,下面的源码都是kotlin,我看了看OkHttp的github,发现最新4.8.0版本kotlin和java五五开,所以还不会kotlin的Android程序猿还不赶快学起来~
简单使用
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
...
}
override fun onResponse(call: Call, response: Response) {
...
}
})
关键就是这一行:
client.newCall(request).enqueue()
其实就是两个方法:
OkHttpClient的newCall() 和 RealCall的execute()/enqueue()方法(execute()/enqueue()分别为同步和异步请求)
源码分析
以下源码主要分析异步请求,因为它的步骤比同步请求多了创建并加入线程池这一步,但两者之后的步骤都是一样的,所以不再分析同步请求的源码。
创建Call对象并加入线程池处理
OkHttpClient.newCall()
可以看到,newCall方法直接创建了一个RealCall对象
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
RealCall.enqueue()
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" } //如果executed为true则抛出异常
executed = true
}
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
在创建OkHttpClient对象时,会通过它的内部类Builder来创建,其中创建了Dispatcher对象。因此这里可以通过client.dispatcher直接获取它持有的dispatcher对象。
Dispatcher任务调度类
Dispatcher用于控制并发请求,下面是其中一些主要的属性(省略了set和get方法)
ArrayDeque即双端队列,它可以从两端对元素进行增删,其中存储元素的数据结构为数组。
var maxRequests = 64 //最大并发请求数
var maxRequestsPerHost = 5 //每个主机的最大请求数
val executorService: ExecutorService //消费者线程池
private val readyAsyncCalls = ArrayDeque<AsyncCall>() //将要运行的异步请求队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>() //正在运行的异步请求队列
private val runningSyncCalls = ArrayDeque<RealCall>() //正在运行的同步请求队列
构造方法
class Dispatcher constructor() //默认构造,会在网络请求前创建默认线程池
constructor(executorService: ExecutorService) : this() { //有参构造方法需要传入一个线程池对象
this.executorServiceOrNull = executorService
}
RealCall.enqueue(responseCallback: Callback)
又调用回dispatcher的enqueue方法
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Dispatcher.enqueue(call: AsyncCall)
AsyncCall是RealCall的内部类,它是一个Runnable对象
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call) //直接将任务加入待运行异步任务队列末尾
//下面是为了共享同一主机正在运行的调用的AtomicInteger
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute() //这个方法会将待运行队列中符合要求的任务跃迁至正在运行队列,并将它们加入线程池运行
}
Dispatcher.promoteAndExecute()
这个函数将“readyAsyncCalls队列中符合要求的任务取出并加入到runningAsyncCalls队列”这个操作称为“跃迁”(promote)
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//while循环遍历待执行异步队列
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break //当前并发的请求数量大于最大并发量64时,就跳出循环,停止跃迁,将之前跃迁的任务放入线程池执行
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue //主机最大并数量大于主机最大并发量5,会跳过当前异步调用,继续遍历待执行异步队列
i.remove() //通过迭代器移除刚才迭代器返回的待执行队列中的任务
asyncCall.callsPerHost.incrementAndGet() //更新每个请求的主机并发数量
executableCalls.add(asyncCall) //将跃迁的任务加入executableCalls列表
runningAsyncCalls.add(asyncCall) //将跃迁的任务加入runningAsyncCalls队列
}
isRunning = runningCallsCount() > 0
}
//遍历上面跃迁的所有任务,并加入线程池执行
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
AsyncCall.executeOn(executorService: ExecutorService)
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
executorService.execute(this) //在线程池中执行AsyncCall的run方法,这个线程池类似于CachedExecutorService,只有“无限”多个非核心线程
success = true
} catch (e: RejectedExecutionException) {
...
} finally {
if (!success) {
client.dispatcher.finished(this) //最终总会调用Dispatcher的finish方法
}
}
}
dispatcher.finished(this)
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//将执行完的任务移除runningAsyncCalls队列
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
val isRunning = promoteAndExecute() //执行完继续调用promoteAndExecute方法取出并执行后面的待执行请求
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
AsyncCall.run()
上面AsyncCall的executeOn方法中将AsyncCall对象加入到线程池并执行了下面的run方法:
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
val response = getResponseWithInterceptorChain() //请求网络,获取数据
signalledCallback = true
responseCallback.onResponse(this@RealCall, response) //回调接口的onResponse方法,并将response作为参数传入
} catch (e: IOException) {
...
} catch (t: Throwable) {
...
} finally {
client.dispatcher.finished(this)
}
}
}
通过拦截器发送请求
RealCall.getResponseWithInterceptorChain()
internal fun getResponseWithInterceptorChain(): Response {
//构建一个完整的拦截器栈
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)
//创建RealInterceptorChain拦截器链,其中封装了一些请求的基本信息
val chain = RealInterceptorChain(
call = this,
interceptors = 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) //调用proceed方法获取数据
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
RealInterceptorChain.proceed(request: Request)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
...
//实例化拦截器链中下一个拦截器,其实就是又创建了一个RealInterceptorChain对象,但更改了两个参数
val next = copy(index = index + 1, request = request)
//获取列表中当前的拦截器
val interceptor = interceptors[index] //interceptors就是上面getResponseWithInterceptorChain函数中创建RealInterceptorChain时传入的列表
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null") //看这里,当前拦截器的intercept方法中传入的是下一个拦截器next
...
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
上面 val response = interceptor.intercept(next) 这行代码intercept方法中又会调用传入的下一个拦截器next,这就实现了一个调用链:
它们各有各的作用,职责分明。它们的作用分别如下:
RetryAndFollowUpInterceptor
:取消、失败重试、重定向BridgeInterceptor
:把用户请求转换为 HTTP 请求;把 HTTP 响应转换为用户友好的响应CacheInterceptor
:读写缓存、根据策略决定是否使用ConnectInterceptor
:实现和服务器建立连接CallServerInterceptor
:实现读写数据
最终的网络请求是在CallServerInterceptor的intercept方法中的,其中通过Exchange完成网络请求,它的实现类之一是Http1ExchangeCodec,请求和获取数据是通过I/O流向Socket对象写入或读取实现的。
缓存策略
HTTP的缓存
OkHttp的缓存策略
上面请求流程中,中间会经过CacheInterceptor进行拦截。
OkHttp的缓存策略可以从它的CacheInterceptor的intercept方法看出:
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
val cacheCandidate = cache?.get(chain.request()) //获取待使用缓存,是一个DiskLruCache缓存,
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()
}
//不进行网络请求,并且没有缓存或缓存已过期,则返回504 Unsatisfiable Request错误
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)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
//不进行网络请求而缓存可用,则使用缓存
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.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()
}
}
//有缓存,并且响应码为304: Not Modified,则将缓存和请求来的响应进行合并
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(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
//缓存策略允许缓存并且响应体不为空,就将请求来的数据存入缓存
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
CacheInterceptor执行流程小结
- 如果网络不可用并且无可用的有效缓存,则返回504错误
- 如果禁止了网络请求,则直接使用缓存
- 如果没有缓存且网络请求可用,则进行网络请求
- 如果此时有缓存,并且网络请求返回的响应码为 304: Not Modified,说明缓存还是有效的,则合并网络响应和缓存结果,并更新缓存
- 如果没有缓存,则将请求回来的结果写入新的缓存中;
CacheStrategy.Factory(…).compute()
我们从 cache 拿到缓存响应后,还需要做这几件事:
- 看当前调用方允不允许使用缓存(判断 request.cacheControl() 的值)
- 允许使用缓存的话,验证这个缓存还有没有效
这个compute方法就是来完成这些事情的。
获取到缓存策略后,在CacheInterceptor的intercept方法中就会根据networkRequest和CacheResponse的情况来做出不同的处理。
/**
上面CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute() 通过这个compute方法来获取缓存策略
*/
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
//禁止使用网络并且缓存无效
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
//没有缓存的响应
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// Drop the cached response if it's missing a required handshake.
//HTTPS请求没有进行握手
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// If this response shouldn't have been stored, it should never be used as a response source.
// This check should be redundant as long as the persistence store is well-behaved and the
// rules are constant.
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
//请求头Cache-Control为no-cache或者请求头包含If-Modified-Since或者If-None-Match,则本地缓存过期,需要服务器验证本地缓存是不是还能继续使
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
val responseCaching = cacheResponse.cacheControl
val ageMillis = cacheResponseAge()
var freshMillis = computeFreshnessLifetime()
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
//缓存过期了,但仍然可用,给相应头中添加了Warning,使用缓存
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
return CacheStrategy(null, builder.build())
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
val conditionName: String
val conditionValue: String?
//缓存已经过期,满足相应的条件则给请求头添加相应的参数,来验证资源是否改变
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
else -> return CacheStrategy(request, null) // No condition! Make a regular request.
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
失败重连
OkHttp的失败重连是通过拦截器链中的RetryAndFollowUpInterceptor来实现的,它的intercept方法如下:
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 newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
response = realChain.proceed(request) //调用下一个拦截器
newExchangeFinder = true
} catch (e: RouteException) {
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) { //调用了recover方法
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue //重新进行请求
} catch (e: IOException) {
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) { //调用了recover方法
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue //重新进行请求
}
...
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
可以看到,在请求出现异常时会调用recover方法尝试重连:
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
//app层面禁止重连
if (!client.retryOnConnectionFailure) return false
//不能再发送请求体了
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
//致命异常
if (!isRecoverable(e, requestSendStarted)) return false
//没有更多尝试的途径了
if (!call.retryAfterFailure()) return false
// For failure recovery, use the same route selector with a new connection.
//对于失败重连,使用相同的route selector和一个新的连接
return true
}
也就是说,只要不满足上面四个if语句的条件,就会返回true,并在intercept方法中调用下一个拦截器重新请求。
请求流程图
参考资料
《Android进阶之光》