写在前面
OkHttp 内部实现上有了一些新的东西,在此做下整理。
内容
简述
这里使用的 OkHttp 的版本为:
implementation("com.squareup.okhttp3:okhttp:4.9.0")
借用从百度图片上找的一张描述 OkHttp 整体结构的图
还有一段官方的示例代码:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
可以看到请求的时候需要构建一个 Request,然后传给 OKHttpClient 里的 Call,由它去发起请求,然后拿到 Response。
Call & RealCall
OkHttpClient执行 newCall()方法的时候,其实是获取了一个 Call接口的实现类 RealCall
// OkHttpClient.kt
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
// Call.kt
interface Call : Cloneable {
fun request(): Request
@Throws(IOException::class)
fun execute(): Response
fun enqueue(responseCallback: Callback)
fun cancel()
fun isExecuted(): Boolean
fun isCanceled(): Boolean
fun timeout(): Timeout
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
// RealCall.kt
class RealCall(
val client: OkHttpClient,
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
...
}
发起请求
使用 ReallCall发起请求的时候只能执行一次,在开始的时候会对状态做检查,如果想要再请求,可以通过ReallCall的clone()方法,来发起请求。
同步
// RealCall.kt
override fun execute(): Response {
// 使用 CAS 检查是否重复调用发起请求
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
接着会调用 OkHttpClient里的Dispatcher的executed()方法,并把当前的RealCall传入进去,然后就开始走拦截器里真正的请求。
异步
异步请求同样也会进行是否重复请求的检查,然后调用分发器进行异步请求,这里的参数为 AsyncCall,是RealCall的内部类,因此可以直接获取到当前的RealCall。
// RealCall.kt
override fun enqueue(responseCallback: Callback) {
// 使用 CAS 检查是否重复调用发起请求
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
...
val call: RealCall
get() = this@RealCall
...
}
Dispatcher
在分发器里,它维护着三个队列:
// Dispatcher.kt
// 准备开启异步的队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
// 正在异步执行的队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
// 正在运行的同步队列
private val runningSyncCalls = ArrayDeque<RealCall>()
这三个队列使用的是ArrayDeque,里面是一个循环数组,有头尾指针,当头尾指针重叠的时候,意味着满了,可以进行扩容的操作。使用ArrayDeque可以节省空间。
它不是一个线程安全的类,所以下面可以看到,相关的操作方法需要进行加锁(synchronized)的操作。
同步
把 RealCall加入到正在运行的同步队列里
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
异步
把 AsyncCall加入到readyAsyncCalls里,然后从readyAsyncCalls遍历取出下一个,如果runningAsyncCalls的长度大于等于最大请求数(默认64),则退出循环,去开始执行已有的异步请求;如果这个异步请求的对同一个域名的请求数量大于等于同一域名最大请求数(默认 5),则跳过下面的步骤,继续下一次循环,直到少于同一域名最大请求数。
当符合上面的两个条件,此时就把 AsyncCall从readyAsyncCalls里移除,添加到runningAsyncCalls和executableCalls里。
最后遍历executableCalls,把每个 AsyncCall放入到线程池里,开始执行。
@get:Synchronized var maxRequests = 64
@get:Synchronized var maxRequestsPerHost = 5
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
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()
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
}
线程池
在这个线程池里,我们可以看到核心线程数为 0,最大线程数为 Int.MAX_VALUE,并且用了SynchronousQueue。
@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!!
}
这样的话意味着这个线程池不会一直有核心线程在运行,所有线程到时间了可以被回收。其次由于使用了SynchronousQueue,这是个没有容量的队列,任务加到线程池里,无法进入到阻塞队列里,会直接通过新建线程来处理它,可以得到最大的并发和最快的处理。
线程池执行每一个 AsyncCall
线程池开始运行,就会执行 AsyncCall的 run()方法,因为它实现了Runnable接口,所以在线程池里就会调用这个方法。流程上跟同步执行的execute()方法一样,都是会进入拦截器里去做真正的请求。
// AsyncCall.kt
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) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, 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)
}
}
}
请求的最后
不管这个同步或异步的请求是否成功,都会走到finally块,这里调用了分发器的 finish()方法。在这个方法里,就是把 Call 从它们相应的队列里移除,然后再继续查看是否还有准备的异步请求要处理。
/** Used by [AsyncCall.run] to signal completion. */
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
/** Used by [Call.execute] to signal completion. */
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
}
// 再走一遍 promoteAndExecute)
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
本文深入分析了 OkHttp 4.x 的内部实现机制,包括 Call 和 RealCall 的工作原理、同步和异步请求处理流程、Dispatcher 的队列管理方式以及线程池的配置与使用。
2372

被折叠的 条评论
为什么被折叠?



