本篇只是个人的学习笔记,建议移步面试官:听说你熟悉OkHttp原理?看大佬分析
源码分析
okhttp源码是4.10.0版本
我们先看看一次最简单的网络请求
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
thread {
val response = client.newCall(request).execute()
Log.e(TAG, "onCreate: ${response.body}")
}
本文主要对response如何生成来解析,看看RealCall的execute方法
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)
}
}
直接看重点,也就是 client.dispatcher.executed(this)以及之后的,先看看dispatcher的executed方法,里面只有一句代码
runningSyncCalls.add(call)
这里的runningSyncCalls是什么呢,是一个双端数组队列,表示的是正在运行的同步调用,包括已经取消但是还在运行的(Running synchronous calls. Includes canceled calls that haven't finished yet.),同样的队列还有runningAsyncCalls(正在异步调用),readyAsyncCalls(准备异步调用)。那么这里的意思就很明显了,就是加入队列里。
现在再看看getResponseWithInterceptorChain这个方法,这个算是okhttp的核心了。
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 {
//传入request启动责任链
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)
}
}
}
暂时跳过前半部分的拦截器,这个我们在后面具体分析每一个拦截器的作用,可以看到生成response的是chain.proceed(originalRequest),那么先进去看一下具体怎么做到的
override fun proceed(request: Request): Response {
//必须小于拦截器的数量
check(index < interceptors.size)
//当前拦截器调用proceed的次数
calls++
//exchage是对请求流的封装,在执行ConnectInterceptor前为空
if (exchange != null) {
//保证host和port没有被修改
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
//ConnectInterceptor之后每个拦截器只能调用proceed一次
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]
//调用intercept方法传入新建的责任链
@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
}
这里的check就是判断里面的条件,如果为false就会抛出错误。calls则是每个拦截器调用proceed方法的次数,需要注意的是exchange是ConnectInterceptor才不为空的,所以在ConnectInterceptor之前的拦截器都有可能走多次proceed方法,而在ConnectInterceptor之后的只有networkInterceptors和CallServerInterceptor,CallServerInterceptor是不会允许proceed方法的。所以这里的报错消息是network interceptor must call proceed() exactly once。
再看回copy方法,就是创建下一个拦截器的责任链,所以index要+1,然后再拿到下一个拦截器,调用他的intercept方法,这里就是每一个拦截器里面要具体实现的方法了,这保证了除了最后一个拦截器,前面的拦截器都会至少执行一次proceed方法(实际上第一个应用拦截器也可以不执行proceed方法)。
可以看到核心就是责任链模式,去调用每一个拦截器,最后会返回response,不用关心每个拦截器具体做了什么,只用把request发到责任链,然后得到需要的response就可以了。
那么拿到了response后,就只有最后一步了
client.dispatcher.finished(this)
进去里面看一下,主要就是promoteAndExecute方法,以及会调用idleCallback,这里的idleCallback就是当dispatcher空闲下来之后会执行的回调
//调用的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!")
//每次dispatcher变空闲的时候会进行的回调
idleCallback = this.idleCallback
}
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
再看看promoteAndExecute方法
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//遍历readyAsyncCalls
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
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
}
这里就是循环把readyAsyncCalls里面的任务取出来,然后放进线程池以及运行中的异步队列,这个方法在enqueue和finish方法中都会调用,所以有新的请求入队和当前请求完成后,都要重新把任务提交到线程池。
- 题外话:为什么双端队列这里用数组而不是链表,因为每次有新的请求或者完成请求都会把readyAsyncCalls里面的转换成runningAsyncCalls,而如果用链表的话会分散在内存各个地方,cpu缓存无法带来便利,并且在垃圾回收上也没有数组好。
各级拦截器作用
- interceptors
用户自定义的拦截器,主要就是添加自定义的header,参数。且这个拦截器只会调用一次,所以可以用来统计网络请求发起情况,但是他可以因为本地异常重试调用多次proceed方法,或者因为中断不调用proceed方法。 - RetryAndFollowUpInterceptor
重定向拦截器,这个拦截器就是用来处理错误重试和重定向的,内部会开启一个循环,如果路由连接失败或者服务器连接失败就会重试,所以也会多次调用proceed方法。 - BridgeInterceptor
应用层和网络层的桥梁,主要就是为你的请求头添加各种固定信息,然后如果请求头用了头部压缩算法的话(gzip),就会在这一层进行解压。 - CacheInterceptor
缓存拦截器,里面有一个缓存策略,返回networkRequest和cacheResponse,如果两者都为空,就意味着禁止使用网络并且缓存不够充足,就会失败。如果命中缓存了,就直接返回缓存里面的,没有缓存才会继续往下走,执行proceed方法。需要注意okhttp只有get方法的缓存。 - ConnectInterceptor
连接拦截器,这里主要就是初始化了之前提到的exchange,这里主要有一个连接池来管理我们的连接,不然每次走网络申请都要建立一次连接,太耗费资源了。像http后面就提出长连接和多路复用这些来优化,这里有五种方法来获取连接。
1.先会查询call中是否已经有连接
2.从连接池中找
3.计算路径,再次从连接池中寻找
4.自己建立一个新的连接,如果第五种方法没有成功就会把这个新连接放到连接池中
5.从连接池中找是否有多路复用的连接,如果找到那么第四种自己建立的连接就会关闭,返回多路复用的连接
- networkInterceptors
自定义的网络拦截器,因为在RetryAndFollowUpInterceptor后面,所以如果网络发生错误或者重定向该拦截器有可能多次被调用,但是只能调用一次proceed方法,通常用于监控网络层的数据传输。 - CallServerInterceptor
请求拦截器,最后一个拦截器,就是对服务器发起了网络请求,没有运行proceed方法。
addInterceptor与addNetworkInterceptor的区别
从调用顺序来看,addInterceptor在责任链的第一个,addNetworkInterceptor在倒数第二个。addInterceptor在应用层中,一般只能被调用一次,允许多次调用proceed或者不调用proceed,多用于统计网络请求发起情况。而addNetworkInterceptor则可以因为重定向的原因调用多次,但是只能走一次proceed方法。多用于统计网络传输的数据。
参考资料:
面试官:听说你熟悉OkHttp原理?