开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第37天,点击查看活动详情
文章中源码的OkHttp版本为4.10.0
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
OkHttp源码分析(一)中主要分析了使用、如何创建,再到发起请求;
OkHttp源码分析(二)中主要分析了OkHttp的拦截器链;
OkHttp源码分析(三)中主要分析了OkHttp的缓存策略;
那么在这篇文章中主要分析OkHttp的连接池如何清除空闲连接等内容。
1.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)
}
2.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清除连接,并且在清除前还要进行确认是空闲的连接;
- 两个属性值的获取:
- 判断是否有空闲连接,如果有则返回空闲连接的空闲时间;
- 判断是否有正在使用的连接,如固有则返回连接的存活时间;
3.Application Interceptors与Network Interceptors
- Application Interceptors是应用拦截器,Network Interceptors是网络拦截器;
- 应用拦截器是用于在请求发送前和网络响应后的拦截器,只能触发一次。而网络拦截器在发生错误重试或者重定向时可以执行多次,相当于进行了二次请求;
- 如果CacheInterceptor命中了缓存就不在进行网络请求了,因此会存在短路网络拦截器的情况;
- 应用拦截器通常用于统计客户端的网络请求发起情况;网络拦截器中可以获取到最终发送请求的request也包括重定向的数据,也可以获取真正发生网络请求的回来的response,从而修改对应的请求和响应数据。