okhttp详解系列一:开篇

okhttp 整体架构

RealCallOkHttpClientRealInterceptorChainindex: Intinterceptors: List<Interceptor>Response proceed(Request r)InterceptorExchangeFinderRealConnectionPoolExchangeRetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptor1n RealCallRealCallRealInterceptorChainexecutegetResponseWithInterceptorChain创建Interceptor ListnewRealInterceptorChainproceednext = copy(index+1)复制当前责任链,指针指向下一个拦截器intercept(next)执行拦截器

okhttp的拦截器分为应用拦截器网络拦截器,拦截器按照顺序执行,看下RealCall.getResponseWithInterceptorChain中拦截器的顺序定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
//1.添加用户注册的应用拦截器
interceptors += client.interceptors
//2.添加重试重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
//3.添加桥接拦截器,用于用户数据和网络数据之间的相互转换
interceptors += BridgeInterceptor(client.cookieJar)
//4.添加缓存拦截器
interceptors += CacheInterceptor(client.cache)
//5.连接拦截器,负责跟服务器建立连接
interceptors += ConnectInterceptor
if (!forWebSocket) {
//6.添加用户自定义网络拦截器
interceptors += client.networkInterceptors
}
//7.服务请求连接器
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
)
//后面代码省略

拦截器的处理时序

这个是简化版的连接器处理时序,连接器调用到下一个都是要经过RealInterceptorChain.proceed,这里面逻辑比较简单,就是找到下一个连接器并执行,这个细节时序图中没有体现。

应用拦截器应用拦截器重试重定向拦截器重试重定向拦截器桥接拦截器桥接拦截器缓存拦截器缓存拦截器连接拦截器连接拦截器网络拦截器网络拦截器服务请求拦截器服务请求拦截器intercept初始化ExchangeFinder重试和重定向场景运行多次处理请求头,包括加载cookie加载本地缓存缓存池获取可用连接或新建连接,创建Exchange和HTTP解码器发送请求解析响应保存缓存保存cookiegzip解压处理异常和返回码决定是否重试、重定向

同步和异步请求源码解析

okhttp异步请求调用

异步请求需要调用enqueue,如下:

1
2
3
4
5
6
7
8
9
10
11
12
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()

client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}

override fun onResponse(call: Call, response: Response) {}
})
}

异步请求源码解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 准备被执行的请求队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
// 正在执行的请求队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()

internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 添加到准备执行队列中
readyAsyncCalls.add(call)

// 每个AsyncCall都维护了一个计数器,这段代码的作用是找到相同host的请求,相同host的请求使用同一个计数器
// 相同host的请求默认最大是5个,超过5个后,新的请求会等待前面的请求执行完成
if (!call.call.forWebSocket) {
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()

// 默认最大是64个并行请求
if (runningAsyncCalls.size >= this.maxRequests) break
// 同一个host默认最多5个并行请求
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue
// 从准备执行队列中删除该任务
i.remove()
// 任务计数器加一,用于限制相同host的最大并行请求个数
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
}

// 请求结束时会执行该函数
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) {
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}

// 再次执行promoteAndExecute,遍历队列任务并执行新的任务,因为超过最大并行任务限制后,
// 有些任务正在排队,所以任务结束后需要再次检查执行队列运行新的任务
val isRunning = promoteAndExecute()

if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}

同步请求源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class RealCall {
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }

timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
// 直接在当前线程执行网络请求
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
}

class Dispacher {
private val runningSyncCalls = ArrayDeque<RealCall>()
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
}

从Dispatcher可以看出,同步请求单独放在runningSyncCalls队列中(和异步请求独立),因此同步请求的数量是没有限制的。

使用指南

OkHttpClient在整个app中最好采用单例模式,所有的网路请求都使用一个OkHttpClient实例。这是因为每个OkHttpClient都会创建自己的连接池线程池。如果每次请求都创建一个OkHttpClient就会浪费系统资源。

当所有请求都使用了同一个OkHttpClient后,如果某次网络请求需要特殊配置可以使用OkHttpClient::newBuilder(),这种方式创建的client会共享连接池、线程池以及全局配置。

参考OkHttpClients Should Be Shared

编译okhttp 4.x

  • 下载okhttp-4.10.x源码Java jdk17IntelliJ IDEA
  • 使用IntelliJ IDEA打开okhttp项目根目录。
  • Gradle JVM需要设置为jdk17,位置在IntelliJ IDEA【File】->【Settings】->【Build,Execution,Deployment】->【Build Tools->Gradle】。
  • 在IDEA中打开AsynchronousGet.kt文件,然后点击运行main函数即可。

参考资料

OkHttp
Android 网络优化-DNS优化 SNI
Interceptors: 重要
“深入交流“系列:Okhttp(二)拦截器的实现
OkHttp3 Cache
HTTP 304状态码的详细讲解
Cache-Control no-cache与max-age=0的区别
浅谈http中的Cache-Control
谈谈OKHttp的几道面试题
okhttp如何实现dns拦截?
Android端HTTPDNS+OkHttp接入指南