OkHttp 的由来
对于 HTTP,早期的安卓提供了两种 API:一种是 Java 原生的 URLConnection,另一种是 Apache 的 HttpClient。
这两种方案,Square 公司觉得都不好用,就对 API 进行了封装,形成了最初版本的 OkHttp 框架。之后 Square 先是移除了 HttpClient 内部实现,接着连 URLConnection 内部实现也移除了。两者都不用,选择自己实现底层支持,比如 TCP 连接的建立等工作。
接着,安卓官方也将其 URLConnection 的底层实现改为了 OkHttp 的底层实现,比如:如何通过 DNS 获取 IP 地址、通过 Socket 发送和接收 HTTP 数据等过程,都是使用的 OkHttp 的代码。
我们先来看看如何简单使用 OkHttp。
简单用法
来到 OkHttp 的主页,通过 OkHttp 来发送一个 GET 请求,并打印响应的状态码。
先添加依赖:
kotlin
// Module-level: build.gradle.kts
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.11.0") // 如果没有看到 OkHttp 的源码,可以换个版本
}
然后创建一个 Empty Views Activity 项目,其 MainActivity
中的代码如下所示:
kotlin
import java.io.IOException
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = "https://www.baidu.com"
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
// 安卓中一般是使用 enqueue() 方法
// 因为 execute() 方法是同步的,enqueue() 方法是异步的
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
println("Status Code: ${response.code}")
}
})
}
}
运行结果:
less
I/System.out Status Code: 200
源码
了解了其基本用法后,我们来跟踪源码,看看它究竟在背后干了什么。
先点开最直接的 enqueue()
方法:
kotlin
// Call.kt
interface Call : Cloneable {
fun enqueue(responseCallback: Callback)
}
但发现它是 Call
接口中的一个抽象方法,所以我们需要回头看看 newCall()
方法创建的对象是什么。
kotlin
// OkHttpClient.kt
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
发现它创建了一个 RealCall
对象。
kotlin
// RealCall.kt
class RealCall(
val client: OkHttpClient,
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {...}
创建该对象需要传入三个参数:
-
client
: 就是我们先前创建的OkHttpClient
对象,所有通用配置都通过它来完成,比如网络的超时时长。 -
originalRequest
: 是我们前面创建的Request
请求对象,存放用来发起 HTTP 请求的基本信息,比如请求方法、请求路径、请求头等。另外参数名的意思是"原始请求",这是因为 OkHttp 会把该请求对象进行多次封装改变,得到的更完整的请求对象,才拿去用于网络请求。
-
forWebSocket
: 这个参数一般用不到,默认为false
就行。因为 WebSocket 协议的作用是让服务器能够主动发送消息给客户端,但这种需求我们一般用不到,它常用于需要频繁刷新数据的场景,比如股票交易、虚拟货币交易。
知道了创建的是 RealCall
对象后,我们来到 RealCall
中查找其 enqueue()
方法的实现:
kotlin
// RealCall.kt
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
这段代码的主要逻辑在于后两行,我们先看 callStart()
方法。
kotlin
// RealCall.kt
private fun callStart() {
this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(this)
}
第一行在跟踪程序错误,用于错误分析;第二行是调用了一个回调方法,eventListener
是一个监听器,用于监听与 HTTP 交互的过程,比如 TCP 连接的建立。
总的来说,callStart
方法就是一个辅助方法,可以不用管,对程序运行无实际影响。我们再回去看看 client.dispatcher.enqueue(AsyncCall(responseCallback))
中的 dispatcher
、enqueue()
和 AsyncCall()
。
先来看 dispatcher
,发现它是一个 Dispatcher
对象。
kotlin
// OkHttpClient.kt
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
而 Dispatcher 是 OkHttp 用于线程调度的,当需要多个请求同时运行,就需要有多个线程,Dispatcher 就是管理多个线程的。OkHttp 使用 Dispatcher,是想要通过统一的线程池和请求队列来复用线程、管理并发,避免每次请求都需要创建新线程。
接着看 enqueue()
方法:
kotlin
// Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// 统计某个主机的请求数
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
方法内部会将参数的 call
添加到双向队列中,存放已准备好要执行的请求。那么进入队列的请求会在什么时候被执行?其实就在 promoteAndExecute()
方法中。
我们进入 promoteAndExecute()
方法:
kotlin
// Dispatcher.kt
// 将合适的 Call 取出,拿去执行
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
// 遍历 readyAsyncCalls
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.
// 选择执行后,不会导致超出 maxRequests、maxRequestsPerHost 容量的 asyncCall
i.remove()
asyncCall.callsPerHost.incrementAndGet()
// 放进 executableCalls 可执行的 Call 列表中
executableCalls.add(asyncCall)
// 放进 runningAsyncCalls 正在执行的 Call 队列中,用于记录
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// 执行上述的 executableCalls 列表
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService) // 关键执行代码
}
return isRunning
}
所以,为了知道 enqueue()
方法的具体逻辑,我们来看看 executeOn()
方法的内部:
kotlin
// RealCall.kt
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) // This call is no longer running!
}
}
}
execute()
方法是 Executor
接口中的抽象方法,参数是 Runnable
类型,它会将参数放到后台去执行。
java
// Executor.java
public interface Executor {
void execute(Runnable command);
}
那我们来看看它实际执行的内容,来到 AsyncCall
的 run
方法。(注意:AsyncCall
是 RealCall
的内部类,它实现了 Runnable
接口)。
kotlin
// RealCall.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)
}
}
}
}
可以看到其中调用了 getResponseWithInterceptorChain()
方法获得了一个服务器返回的响应,那么我们很清楚,这就是这个方法的关键。之后它会将这个响应传给 onResponse
回调方法,而 responseCallback
就是我们在调用 enqueue
方法时传入的 Callback
实例,所以我们可以在 onResponse
方法中打印响应返回的状态码。
出错的话,会调用 responseCallback
的 onFailure
回调。
流程总结
我们来对 enqueue
的异步流程做个总结。其本质是经典的 "生产者-消费者"模型:
- 首先,生产者是
RealCall.enqueue
,它将请求任务包装成AsyncCall
,然后放入Dispatcher
的待处理队列readyAsyncCalls
中。 - 调度中心是
Dispatcher.promoteAndExecute
,它根据当前的并发限制,从待处理队列中取出合适的任务,然后放进运行中队列runningAsyncCalls
进行状态跟踪,并最终通过ExecutorService
线程池来处理。 - 消费者是
AsyncCall.run
,执行网络请求,并通过Callback
将结果返回。
理解了这个模型,整个异步调度的框架就很清晰了。
至此,OkHttp 执行一个异步请求的调度流程 框架,我们就已经看完了。接下来我们会进入请求的关键,也就是 getResponseWithInterceptorChain
方法的内部实现。不过在讲它之前,会先看看 OkHttpClient
中各个可配置的参数有哪些,让我们先对 OkHttp 有个大致了解。