开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第38天,点击查看活动详情
昨天对OkHttp(4.10.0)分别进行了分析:
本篇内容是上面四篇的合集,我个人觉得还是这样的合集更完整一些,学习起来一气呵成,所以还是上传一下完整内容。
源码的OkHttp版本为4.10.0
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
1.简单使用
- okHttp的简单使用代码如下:
//创建OkHttpClient对象
val client = OkHttpClient().newBuilder().build()
//创建Request对象
val request = Request.Builder()
.url("https://wanandroid.com/wxarticle/list/408/1/json") //添加请求的url地址
.build() //返回一个Request对象
//发起请求
fun request() {
val response = client
.newCall(request) //创建一个Call对象
.enqueue(object : Callback { //调用enqueue方法执行异步请求
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
TODO("Not yet implemented")
}
})
}
- 工作流程有四步:
- 创建OkHttpClient对象
- 创建Request对象
- 创建Call对象
- 开始发起请求,
enqueue
为异步请求,execute
为同步请求
2.OkHttpClient对象是如何创建的
val client = OkHttpClient().newBuilder().build()
OkHttpClient
对象的创建是一个典型的建造者模式,先看一下newBuilder
方法做了什么,源码如下:
//创建了一个Builder对象
open fun newBuilder(): Builder = Builder(this)
class Builder constructor() {
//调度器
internal var dispatcher: Dispatcher = Dispatcher()
//拦截器集合
internal val interceptors: MutableList<Interceptor> = mutableListOf()
//网络拦截器集合
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
...
}
newBuilder
中创建了一个Builder
对象,Builder
对象的构造函数中定义了很多的变量,这里只保留了3个重要的。
下面看一下build
方法做了什么
//这个this就是上面创建的Builder对象
fun build(): OkHttpClient = OkHttpClient(this)
okHttpClient
源码如下
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
@get:JvmName("interceptors") val interceptors: List<Interceptor> =
builder.interceptors.toImmutableList()
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
builder.networkInterceptors.toImmutableList()
...
}
通过OkHttpClient
对象的源码可以得知,Builder
创建的调度器、拦截器最终都会交给OkHttpClient
,这是建造者模式的特定。
3.Request对象是如何创建的
val request = Request.Builder()
.url("https://wanandroid.com/wxarticle/list/408/1/json") //添加请求的url地址
.build() //返回一个Request对象
open class Builder {
//请求地址
internal var url: HttpUrl? = null
//请求方法
internal var method: String
//请求头
internal var headers: Headers.Builder
//请求体
internal var body: RequestBody? = null
...
}
4.创建Call对象
val call = client.newCall(request)
override fun newCall(request: Request): Call {
//newRealCall中传递了三个参数,第一个参数是OkHttpClient本身,第二个参数request,
//第三个不用关注
return RealCall.newRealCall(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 {
...
}
从源码可知RealCall
是Call
的子类,那么Call
又是什么呢,往下看
//调用是准备好要执行的请求。也可以取消调用。
//由于该对象表示单个请求/响应对(流),因此不能执行两次。
interface Call : Cloneable {
//返回发起此调用的原始请求。
fun request(): Request
@Throws(IOException::class)
//同步请求,立即调用请求,并阻塞,直到响应可以处理或出现错误。
fun execute(): Response
//异步请求,接受回调参数
fun enqueue(responseCallback: Callback)
//取消请求
fun cancel()
//如果此调用已被执行或进入队列,则返回true。多次执行调用是错误的。
fun isExecuted(): Boolean
//是否是取消状态
fun isCanceled(): Boolean
//超时时间,
fun timeout(): Timeout
//创建一个与此调用相同的新调用,即使该调用已经进入队列或执行,该调用也可以被加入队列或执行。
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
5.发起请求
- 以异步请求为例进行分析
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("onFailure:$e")
}
override fun onResponse(call: Call, response: Response) {
println("onResponse:${response.body.toString()}")
}
})
RealCall
是Call
的子类所以enqueue
的具体实现是在RealCall
中
override fun enqueue(responseCallback: Callback) {
//检查是否进行了二次请求
check(executed.compareAndSet(false, true)) { "Already Executed" }
//请求后立即调用,相当于监听请求的开始事件
callStart()
//将请求交给调度器来决定什么时候开始请求
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
private fun callStart() {
this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(this)
}
- 疑问:
client.dispatcher.enqueue
是如何决定什么时候开始请求的- 已知
client
就是OkHttpClient
dispatcher
是调度器,先来看一下它的源码
- 已知
class Dispatcher constructor() {
//并发执行的最大请求数。上面的请求队列在内存中,等待正在运行的调用完成。
//如果在调用这个函数时有超过maxRequests的请求在运行,那么这些请求将保持在运行状态。
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
//每台主机可并发执行的最大请求数。这限制了URL主机名的请求。
//注意,对单个IP地址的并发请求仍然可能超过此限制:
//多个主机名可能共享一个IP地址或通过同一个HTTP代理路由。
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
//线程安全的单例模式,线程池的获取用于线程调度。
@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!!
}
//定义准备发送的队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
//定义异步发送队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
//定义同步发送队列
private val runningSyncCalls = ArrayDeque<RealCall>()
...
}
- 再来看一下
dispatcher.enqueue
的源码
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//将call对象添加到准备发送的队列,这个call对象来自AsyncCall,稍后再讲
readyAsyncCalls.add(call)
//修改AsyncCall,使其共享对同一主机的现有运行调用的AtomicInteger。
if (!call.get().forWebSocket) {
//共享一个已经存在的正在运行调用的AtomicInteger
val existingCall = findExistingCallWithHost(call.host())
//统计发送数量
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//准备发送请求
promoteAndExecute()
}
//将符合条件的call从readyAsyncCalls(准备发送的队列)添加到runningAsyncCalls(异步发送队列)中
//并在服务器上执行它们
//不能在同步时调用,因为执行调用可以调用用户代码
//如果调度程序当前正在运行,则为true。
private fun promoteAndExecute(): Boolean {
assert(!Thread.holdsLock(this))
//收集所有需要执行的请求
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//遍历准备发送的队列
while (i.hasNext()) {
val asyncCall = i.next()
//判断已经发送的请求是大于等于最大请求个数64,是则跳出循环
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
//判断并发请求个数是否大于等于最大并发个数5,如果是则跳出循环
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
//从准备队列中删除
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
}
- 最终请求是通过
AsyncCall
的executeOn
发送出去的,AsyncCall
是什么
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
...
}
- 接收了一个回调,继承了Runnable
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) // 停止运行
}
}
}
AsyncCall
将自己加入到线程池,然后线程池开启线程执行自己的run方法,那么AsyncCall
加入了一个怎样的线程池呢?
@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!!
}
- 这里定义了一个缓存线程池,具有缓存功能且如果一个请求执行完毕后在60s内再次发起就会复用刚才那个线程,提高了性能。
- 交给线程池后,线程池会开启线程执行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) {
// 不要两次发出回调信号!
Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", 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)
}
}
}
在请求得到结果后最后会调用finish
表示完成,这里的finish
又做了什么呢?
/**
* 由[AsyncCall.run]用于表示完成。
*/
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
/**
* 由[Call.execute]用于表示完成。
*/
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
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()
//如果没有请求需要执行,回调闲置callback
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
-
流程图如下
6.拦截器链
- 上面的代码走到了
getResponseWithInterceptorChain()
,这个方法得到了response
的响应,先看源码
@Throws(IOException::class)
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)
//定义真正的拦截器链
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 transmitter.noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
getResponseWithInterceptorChain
方法是整个OkHttp实现责任链模式的核心,在这个方法中除了众多拦截器是重点之外还有chain.proceed
这个重点,这里利用了责任链模式,先看一下这里面做了什么事情:
override fun proceed(request: Request): Response {
check(index < interceptors.size)
//统计当前拦截器调用proceed方法的次数
calls++
//这里是对拦截器调用proceed方法的限制,
//在ConnectInterceptor及其之后的拦截器最多只能调用一次proceed!!
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
//创建并调用链中的下一个拦截器
val next = copy(index = index + 1, request = request)
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"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
上面的proceed
函数中的逻辑就做了一件事,创建下一级责任链并调用下一个拦截器,在getResponseWithInterceptorChain
函数中除CallServerInterceptor
拦截器之外它前面的每一个拦截器都会调用一次proceed
并且只会调用一次,call++
的目的是为了保证责任链能依次调用。
下面再来聊一聊拦截器,如果没有自定义的拦截器那么就只有5个拦截器,这5个拦截器的功能如下图
1.RetryAndFollowUpInterceptor:
- 重定向或者重试拦截器,同时还创建了一个连接管理池
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
companion object {
//定义重试或者重定向的次数
//Chrome遵循21个重定向;Firefox, curl和wget跟随20;Safari是16;HTTP/1.0推荐5。
private const val MAX_FOLLOW_UPS = 20
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//chain转换为具体的拦截器链,RealInterceptorChain实现了Interceptor.Chain
val realChain = chain as RealInterceptorChain
var request = chain.request
//这个call就是RealCall对象
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
//不是重试并且可以执行新的路由
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
//newExchangeFinder定义为true因此通过enterNetworkInterceptorExchange函数中
//创建了一个新的ExchangeFinder对象,用来管理连接池
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)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
// 试图与服务器通信失败。请求可能已经发送。
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
//后面的处理都结束之后会回传response
//如果存在,请附上先前的response。这样的response是没有主体的。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
//找出要响应接收response的HTTP请求。
//这将添加身份验证头,遵循重定向或处理客户端请求超时。
//如果后续操作是不必要的或不适用的,则返回null。
val followUp = followUpRequest(response, exchange)
//获取到的request为空则直接返回response
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
//request有值且只允许被调用一次则返回response
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若不为空则重新赋值后继续发起请求
request = followUp
priorResponse = response
} finally {
//如果closeActiveExchange为true则当前交换不被使用且关闭
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
fun enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) {
check(interceptorScopedExchange == null)
synchronized(this) {
//判断之前的请求是否是打开状态,如果是则不能发出新的请求
check(!responseBodyOpen) {
"cannot make a new request because the previous response is still open: " +
"please call response.close()"
}
check(!requestBodyOpen)
}
if (newExchangeFinder) {
//创建了ExChangeFinder
this.exchangeFinder = ExchangeFinder(
connectionPool,
createAddress(request.url),
this,
eventListener
)
}
}
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
HTTP_PROXY_AUTH -> {
//407异常处理
}
HTTP_UNAUTHORIZED -> {
//401异常处理
}
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
//300 异常处理
//构建重定向请求
return buildRedirectRequest(userResponse, method)
}
HTTP_CLIENT_TIMEOUT -> {
//408异常处理
}
HTTP_UNAVAILABLE -> {
//503异常处理
}
HTTP_MISDIRECTED_REQUEST -> {
//OkHttp可以合并HTTP/2连接,即使域名不同。看到
//RealConnection.isEligible()。如果我们尝试这样做,并且服务器返回HTTP 421,
//那么可以尝试另一个连接。
}
}
}
/**
* 构建重定向请求
*/
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// 客户端是否允许重定向?
if (!client.followRedirects) return null
val location = userResponse.header("Location") ?: return null
// 不要遵循重定向到不支持的协议。
val url = userResponse.request.url.resolve(location) ?: return null
// 如果配置了,不要遵循SSL和非SSL之间的重定向
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
// 大多数重定向不包括请求体。
val requestBuilder = userResponse.request.newBuilder()
if (HttpMethod.permitsRequestBody(method)) {
val responseCode = userResponse.code
val maintainBody = HttpMethod.redirectsWithBody(method) ||
responseCode == StatusLine.HTTP_PERM_REDIRECT ||
responseCode == StatusLine.HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != StatusLine.HTTP_PERM_REDIRECT && responseCode != StatusLine.HTTP_TEMP_REDIRECT) {
requestBuilder.method("GET", null)
} else {
val requestBody = if (maintainBody) userResponse.request.body else null
requestBuilder.method(method, requestBody)
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding")
requestBuilder.removeHeader("Content-Length")
requestBuilder.removeHeader("Content-Type")
}
}
//当跨主机重定向时,删除所有身份验证头。
//这对应用层来说是潜在的麻烦,因为他们没有办法保留它们。
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
2.BridgeInterceptor:
- 桥接拦截器,负责把用户请求转换成网络请求,把网络响应转换成对用户友好的响应。
- 源码方面没什么难度就是添加了一些请求头,需要注意的是Accept-Encoding请求头,如果用户自己添加了那就需要自己解压,如果用户没有添加则请求过程会自己添加gizp,同时会自动解压。
3.CacheInterceptor:
- CacheInterceptor就是负责更新和读取缓存的,内部是通过okio进行读取和写入的
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
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) {
// 候选缓存不适用,关闭
cacheCandidate.body?.closeQuietly()
}
// 如果网络被禁止使用则缓存为空并且抛出异常504
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)
}
}
var networkResponse: Response? = null
try {
//让下一个拦截器处理
networkResponse = chain.proceed(networkRequest)
} finally {
//防止内存泄露
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
//有缓存
if (cacheResponse != null) {
//服务器返回状态码为304则返回缓存结果
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()
//在合并头之后更新缓存
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)) {
//将请求结果进行缓存
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
}
}
4.ConnectInterceptor:
- 连接拦截器就是用来建立连接的
//建立连接并继续到下一个拦截程序,网络可以用于返回的响应,也可以使用GET验证缓存的响应。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
//返回一个携带新请求的exchange对象
//exchange主要作用是发送请求和接收响应的。
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
//让下一层拦截器处理,同时将exchange一起传递过去
return connectedChain.proceed(realChain.request)
}
}
-
- 连接又是如何建立连接的
//查找新连接或池连接以承载即将到来的请求和响应
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(this) {
check(expectMoreExchanges) { "released" }
check(!responseBodyOpen)
check(!requestBodyOpen)
}
val exchangeFinder = this.exchangeFinder!!
//exchangeFinder是在RetryAndFollowUpInterceptor中创建的
//找到可用的连接后创建一个新的解码器并返回
val codec = exchangeFinder.find(client, chain)
//创建Exchange对象,主要负责发送请求和接收响应的
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
}
- exchangeFinder.find(client, chain)
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
//这里就是查找可用的链接,如果不可用则一直重复查找,知道找到为止
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
//创建并返回一个新的编码器
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure(e.lastConnectException)
throw e
} catch (e: IOException) {
trackFailure(e)
throw RouteException(e)
}
}
- exchangeFinder.findHealthyConnection
@Throws(IOException::class)
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
//查找连接,如果现有连接存在,则优先选择现有连接,然后是池,最后构建一个新连接。
val candidate = findConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled
)
//确认找到的连接是否可以正常使用
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
//如果连接不可用就从连接池中取出
candidate.noNewExchanges()
//这里就是确保连接可用,如果前面找到的连接不可用那么继续查找
if (nextRouteToTry != null) continue
val routesLeft = routeSelection?.hasNext() ?: true
if (routesLeft) continue
val routesSelectionLeft = routeSelector?.hasNext() ?: true
if (routesSelectionLeft) continue
throw IOException("exhausted all routes")
}
}
- exchangeFinder.findConnection
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
//尝试重用调用中的连接。
val callConnection = call.connection
if (callConnection != null) {
var toClose: Socket? = null
synchronized(callConnection) {
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
}
//如果连接还没有被释放则重用
if (call.connection != null) {
check(toClose == null)
return callConnection
}
//连接被释放
toClose?.closeQuietly()
//从连接的分配列表中删除
eventListener.connectionReleased(call, callConnection)
}
//需要一个新的连接。给它新的数据。
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0
//从连接池获取
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
//连接池中什么都没有的情况下走这里
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
//使用来自前一个合并连接的路由
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
// 在现有路由中选择
routes = null
route = routeSelection!!.next()
} else {
//计算出一个新的连接,这是一个阻塞操作
var localRouteSelector = routeSelector
if (localRouteSelector == null) {
localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
this.routeSelector = localRouteSelector
}
val localRouteSelection = localRouteSelector.next()
routeSelection = localRouteSelection
routes = localRouteSelection.routes
if (call.isCanceled()) throw IOException("Canceled")
//这里有了一组新的IP地址,所以再次尝试从连接池中获取
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
route = localRouteSelection.next()
}
//创建一个新的连接
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
//这里连接了服务器
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
call.client.routeDatabase.connected(newConnection.route())
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
return result
}
synchronized(newConnection) {
//添加到连接池中
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
eventListener.connectionAcquired(call, newConnection)
return newConnection
}
- 从
exchangeFinder.find
开始到exchangeFinder.findConnection
只做了一件事,就是先尝试重用连接,如果不能重用就从连接池中取出一个新的连接,如果无法取出一个连接就创建一个新的连接并添加到连接池中。 - 服务器是怎么连接的,看这行代码
newConnection.connect
fun connect(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
call: Call,
eventListener: EventListener
) {
...
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
if (rawSocket == null) {
//无法连接
break
}
} else {
//在socket上构建完整HTTP或HTTPS连接
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
break
} catch (e: IOException) {
...
}
...
}
connect
中最终调用的是connectSocket
方法
@Throws(IOException::class)
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
val proxy = route.proxy
val address = route.address
//创建socket对象
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
this.rawSocket = rawSocket
eventListener.connectStart(call, route.socketAddress, proxy)
rawSocket.soTimeout = readTimeout
try {
//进行socket连接
Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
} catch (e: ConnectException) {
throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
initCause(e)
}
}
try {
//okio的接口,用于输入,类似InputStream
source = rawSocket.source().buffer()
//okio的接口,用于输出,类似OutputStream
sink = rawSocket.sink().buffer()
} catch (npe: NullPointerException) {
if (npe.message == NPE_THROW_WITH_NULL) {
throw IOException(npe)
}
}
}
- 这个方法就是创建了一个
socket
对象然后使用socket
建立连接,利用okio
的输入输出接口获取输入/输出流
5.CallServerInterceptor:
- 请求拦截器,在前置准备工作完成后,真正发起了网络请求。
- 先写入请求头,如果是GET请求则请求完毕,如果是POST请求则等待返回状态码为100时在发送请求体
- 然后读取响应头,经过判断后(非空响应体)再读取响应体
- 拿到最终结果后一层层的向上传递,会经过之前的每一个拦截器最终回到自己定义的回调中
6.拦截器的总结:
- 首先经过
RetryAndFollowUpIntercept
拦截器,它主要负责重试和重定向,并且重试或者重定向的次数不能大于20次,同时它还创建了一个ExchangeFinder
对象用于管理连接池为后续的连接做准备; - 第二个拦截器是
BridgeInterceptor
拦截器,这个拦截器主要是补充了请求头,把用户请求转换成网络请求,网络响应转换成用户可以接收的响应,同时还需要注意的一点是如果用户手动添加了Accept-Encoding
那就需要处理解压操作; - 第三个拦截器是
CacheInterceptor
拦截器,主要作用就是缓存response
并且它的内部是通过okio
来处理缓存的; - 第四个拦截器是
ConnectInterceptor
拦截器,这个拦截器是负责建立连接的,从代码中可知最终是通过RealConnection
对象建立socket
连接的并且获得了输入输出流为下一步读写做准备,RealConnection
对象的获取时优先复用的,如果无法复用则从连接池中获取,如果无法获取则创建一个新的连接并将其放入连接池中; - 第五个拦截器是
CallServerInterceptor
拦截器,它真正发起了网络请求,负责数据的读取和写入。
7.OkHttp的缓存策略
OkHttp的缓存是基于HTTP网络协议的,所以这里需要先来来了解一下HTTP的缓存策略。HTTP的缓存策略是根据请求和响应头来标识缓存是否可用,缓存是否可用则是基于有效性和有效期的。
在HTTP1.0时代缓存标识是根据Expires头来决定的,它用来表示绝对时间,例如:Expires:Thu,31 Dec 2020 23:59:59 GMT,当客户端再次发起请求时会将当前时间与上次请求的时间Expires进行对比,对比结果表示缓存是否有效但是这种方式存在一定问题,比如说客户端修改了它本地的时间,这样对比结果就会出现问题。这个问题在HTTP1.1进行了改善,在HTTP1.1中引入了Cache-Control标识用来表示缓存状态,并且它的优先级高于Expires。Cache-Control常见的取值为下面的一个或者多个:
- private:默认值,用于标识一些私有的业务数据,只有客户端可以缓存;
- public:用于标识通用的业务数据,客户端和服务端都可以缓存;
- max-age-xx:缓存有效期,单位为秒;
- no-cache:需要使用对比缓存验证缓存有效性;
- no-store:所有内容都不缓存,强制缓存、对比缓存都不会触发。
HTTP的缓存分为强制缓存和对比缓存两种:
- 强制缓存:当客户端需要数据时先从缓存中查找是否有数据,如果有则返回没有则向服务器请求,拿到响应结果后将结果存进缓存中,而强制缓存最大的问题就是数据更新不及时,当服务器数据有了更新时,如果缓存有效期还没有结束并且客户端主动请求时没有添加no-store头那么客户端是无法获取到最新数据的。
- 对比缓存:由服务器决定是否使用缓存,客户端第一次请求时服务端会返回标识(Last-Modified/If-Modified-Since与ETag/If-None-Match)与数据,客户端将这两个值都存入缓存中,当客户端向服务器请求数据时会把缓存的这两个数据都提交到服务端,服务器根据标识决定返回200还是304,返回200则表示需要重新请求获取数据,返回304表示可以直接使用缓存中的数据
- Last-Modified:客户端第一次请求时服务端返回的上次资源修改的时间,单位为秒;
- If-Modified-Since:客户端第再次请求时将服务器返回的Last-Modified的值放入If-Modified-Since头传递给服务器,服务器收到后判断缓存是否有效;
- ETag:这是一种优先级高于Last-Modified的标识,返回的是一个资源文件的标识码,客户端第一次请求时将其返回,生成的方式由服务器决定,决定因素包括文件修改的时间,问津的大小,文件的编号等等;
- If-None-Match:客户端再次请求时会将ETag的资源文件标识码放入Header提交。
对比缓存提供了两种标识,那么有什么区别呢:
- Last-Modified的单位是秒,如果某些文件在一秒内被修改则并不能准确的标识修改时间;
- 资源的修改依据不应该只用时间来表示,因为有些数据只是时间有了变化内容并没有变化。
- OkHttp的缓存策略就是按照HTTP的方式实现的,
okio
最终实现了输入输出流,OKHttp的缓存是根据服务器端的Header自动完成的,开启缓存需要在OkHttpClient创建时设置一个Cache对象,并指定缓存目录和缓存大小,缓存系统内部使用LRU作为缓存的淘汰算法。下面的代码就是对HTTP的对比缓存和强制缓存的一种实现:- 拿到响应头后根据头信息决定是否进行缓存;
- 获取数据时判断缓存是否有效,对比缓存后决定是否发出请求还是获取本地缓存;
//给定一个请求和缓存的响应,它将确定是使用网络、缓存还是两者都使用。
class CacheStrategy internal constructor(
//发送的网络请求
val networkRequest: Request?,
//缓存响应,基于DiskLruCache实现的文件缓存,key为请求的url的MD5值,value为文件中查询到的缓存
//如果调用不使用缓存,则为null
val cacheResponse: Response?
) {
init {
//这里初始化,根据传递的cacheResponse判断是否缓存
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
val headers = cacheResponse.headers
for (i in 0 until headers.size) {
val fieldName = headers.name(i)
val value = headers.value(i)
when {
fieldName.equals("Date", ignoreCase = true) -> {
servedDate = value.toHttpDateOrNull()
servedDateString = value
}
fieldName.equals("Expires", ignoreCase = true) -> {
expires = value.toHttpDateOrNull()
}
fieldName.equals("Last-Modified", ignoreCase = true) -> {
lastModified = value.toHttpDateOrNull()
lastModifiedString = value
}
fieldName.equals("ETag", ignoreCase = true) -> {
etag = value
}
fieldName.equals("Age", ignoreCase = true) -> {
ageSeconds = value.toNonNegativeInt(-1)
}
}
}
}
}
fun compute(): CacheStrategy {
val candidate = computeCandidate()
//被禁止使用网络并且缓存不足,
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
//返回networkRequest=null和cacheResponse=null的CacheStrategy
//在缓存拦截器中最终会抛出504的异常
return CacheStrategy(null, null)
}
return candidate
}
/**
* 假设请求可以使用网络,则返回要使用的策略
*/
private fun computeCandidate(): CacheStrategy {
// 无缓存的响应
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// 如果缓存的响应缺少必要的握手,则丢弃它。
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// 如果不应该存储此响应,则绝不应该将其用作响应源。
// 只要持久性存储是良好的并且规则是不变的那么这个检查就是多余的
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
val requestCaching = request.cacheControl
//没有缓存 || 如果请求包含条件,使服务器不必发送客户机在本地的响应,则返回true
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
//返回cacheResponse的缓存控制指令。即使这个响应不包含Cache-Control头,它也不会为空。
val responseCaching = cacheResponse.cacheControl
//返回response的有效期,以毫秒为单位。
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) {
//从requestCaching中获取最新的时间并转为毫秒
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
//从requestCaching中获取过期的时间并转为毫秒
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
//
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())
}
// 找到要添加到请求中的条件。如果满足条件,则不传输响应体。
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)
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
/**
* 从送达日期开始返回响应刷新的毫秒数
*/
private fun computeFreshnessLifetime(): Long {
val responseCaching = cacheResponse!!.cacheControl
if (responseCaching.maxAgeSeconds != -1) {
return SECONDS.toMillis(responseCaching.maxAgeSeconds.toLong())
}
val expires = this.expires
if (expires != null) {
val servedMillis = servedDate?.time ?: receivedResponseMillis
val delta = expires.time - servedMillis
return if (delta > 0L) delta else 0L
}
if (lastModified != null && cacheResponse.request.url.query == null) {
//正如HTTP RFC所推荐并在Firefox中实现的那样,
//文件的最大过期时间应该被默认为文件被送达时过期时间的10%
//默认过期日期不用于包含查询的uri。
val servedMillis = servedDate?.time ?: sentRequestMillis
val delta = servedMillis - lastModified!!.time
return if (delta > 0L) delta / 10 else 0L
}
return 0L
}
/**
* 返回response的有效期,以毫秒为单位。
*/
private fun cacheResponseAge(): Long {
val servedDate = this.servedDate
val apparentReceivedAge = if (servedDate != null) {
maxOf(0, receivedResponseMillis - servedDate.time)
} else {
0
}
val receivedAge = if (ageSeconds != -1) {
maxOf(apparentReceivedAge, SECONDS.toMillis(ageSeconds.toLong()))
} else {
apparentReceivedAge
}
val responseDuration = receivedResponseMillis - sentRequestMillis
val residentDuration = nowMillis - receivedResponseMillis
return receivedAge + responseDuration + residentDuration
}
...
}
- OkHttp缓存的请求只有GET,其他的请求方式也不是不可以但是收益很低,这本质上是由各个method的使用场景决定的。
internal fun put(response: Response): CacheRequest? {
val requestMethod = response.request.method
if (HttpMethod.invalidatesCache(response.request.method)) {
try {
remove(response.request)
} catch (_: IOException) {
// 无法写入缓存。
}
return null
}
if (requestMethod != "GET") {
// 不要缓存非get响应。技术上我们允许缓存HEAD请求和一些
//POST请求,但是这样做的复杂性很高,收益很低。
return null
}
...
}
- 完整流程图如下
8.OkHttp的连接池
/**
* 管理HTTP和HTTP/2连接的重用,以减少网络延迟。共享相同地址的HTTP请求可以共享一个连接。
*/
class ConnectionPool internal constructor(
internal val delegate: RealConnectionPool
) {
constructor(
maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit
) : this(RealConnectionPool(
taskRunner = TaskRunner.INSTANCE,
maxIdleConnections = maxIdleConnections,
keepAliveDuration = keepAliveDuration,
timeUnit = timeUnit
))
constructor() : this(5, 5, TimeUnit.MINUTES)
/** 返回池中空闲连接的数量。 */
fun idleConnectionCount(): Int = delegate.idleConnectionCount()
/** 返回池中的连接总数。 */
fun connectionCount(): Int = delegate.connectionCount()
/** 关闭并删除池中的所有空闲连接。 */
fun evictAll() {
delegate.evictAll()
}
}
- OkHttp支持5可并发KeepLive,默认链路生命为5分钟,连接池的最终实现是
RealConectionPool
,添加连接到连接池的代码如下
//RealConnectionPool#put
fun put(connection: RealConnection) {
connection.assertThreadHoldsLock()
//添加到连接池
connections.add(connection)
//通过清除队列的schedule,清除空闲时间最长的连接超过保持连接限制或空闲连接限制的连接
cleanupQueue.schedule(cleanupTask)
}
9.OkHttp的空闲连接如何清除
private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
override fun runOnce() = cleanup(System.nanoTime())
}
/**
* 在此池上执行维护,如果空闲时间最长的连接超过保持连接限制或空闲连接限制,则删除该连接。
* 返回休眠的持续时间(以纳秒为单位),直到下一次预定调用此方法。如果不需要进一步清理,则返回-1。
*/
fun cleanup(now: Long): Long {
//正在使用的连接数量
var inUseConnectionCount = 0
//空闲的连接数量
var idleConnectionCount = 0
//最长空闲连接
var longestIdleConnection: RealConnection? = null
//最长空闲时间
var longestIdleDurationNs = Long.MIN_VALUE
// 找到要清理的连接,或者找到下一次清理的截止时间。
for (connection in connections) {
synchronized(connection) {
// 如果连接正在使用,继续搜索
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++
} else {
idleConnectionCount++
// 如果连接已经准备好被清理,我们就完成了。
//空闲时间 = 当前时间 - 闲置时间
val idleDurationNs = now - connection.idleAtNs
//如果空闲时间 > 最长空闲时间
if (idleDurationNs > longestIdleDurationNs) {
//空闲时间赋值给最长空闲时间
longestIdleDurationNs = idleDurationNs
//当前connection赋值给最长空闲连接
longestIdleConnection = connection
} else {
Unit
}
}
}
}
when {
//最长空闲时间 >= 存活时间 || 空闲的连接数量 > 最大空闲连接数量
longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections -> {
// 选择了一个前面查找到的连接来清理。清理前再次确认它是否可以被清理,然后关闭
val connection = longestIdleConnection!!
synchronized(connection) {
if (connection.calls.isNotEmpty()) return 0L // 不再空闲
if (connection.idleAtNs + longestIdleDurationNs != now) return 0L // 不再空闲
connection.noNewExchanges = true
//清除连接
connections.remove(longestIdleConnection)
}
connection.socket().closeQuietly()
if (connections.isEmpty()) cleanupQueue.cancelAll()
// 再次清理。
return 0L
}
// 空闲连接数量 > 0
idleConnectionCount > 0 -> {
// 连接即将被清除。
// 返回 存活时间 - 最长空闲时间
return keepAliveDurationNs - longestIdleDurationNs
}
// 正在使用的连接数量 > 0
inUseConnectionCount > 0 -> {
// 所有连接都在使用中。在我们再次运行之前,它至少是保持存活的时间。
// 返回连接的存活时间
return keepAliveDurationNs
}
else -> {
//没有连接,空闲或正在使用。
return -1
}
}
}
空闲连接的清除具体分为2个步骤:
- 找到当前连接是空闲的并且这个连接的空闲时间大于设定的最长空闲时间的连接,只有这种连接才可以被清除;
- 条件判断:最长空闲时间 >= 存活时间 || 空闲的连接数量 > 最大空闲连接数量 ,符合这个条件后才可以通过
remove
清除连接,并且在清除前还要进行确认是空闲的连接;
两个属性值的获取:
- 判断是否有空闲连接,如果有则返回空闲连接的空闲时间;
- 判断是否有正在使用的连接,如固有则返回连接的存活时间;
10.Application Interceptors与Network Interceptors
- Application Interceptors是应用拦截器,Network Interceptors是网络拦截器;
- 应用拦截器是用于在请求发送前和网络响应后的拦截器,只能触发一次。而网络拦截器在发生错误重试或者重定向时可以执行多次,相当于进行了二次请求;
- 如果CacheInterceptor命中了缓存就不在进行网络请求了,因此会存在短路网络拦截器的情况;
- 应用拦截器通常用于统计客户端的网络请求发起情况;网络拦截器中可以获取到最终发送请求的request也包括重定向的数据,也可以获取真正发生网络请求的回来的response,从而修改对应的请求和响应数据。