1、OkHttp介绍
OkHttp 是一个开源的 HTTP 客户端库,由 Square 公司开发并维护。它提供了简洁、高效的 API,用于处理网络请求和响应。OkHttp 基于 Java 编写,同时也提供了对 Kotlin 的良好支持。
OkHttp 提供了以下主要特性:
- 简洁易用的 API:OkHttp 提供了简洁而强大的 API,使得进行 HTTP 请求变得非常容易。通过构建 Request 对象和使用 Call 对象来发起同步或异步的网络请求。
- 支持 HTTP/2 和 SPDY:OkHttp 支持现代的 HTTP 协议,包括 HTTP/2 和 SPDY,从而提供更快速和更有效率的网络通信。
- 连接池和缓存:OkHttp 内置了连接池和响应缓存,可以有效地管理和复用网络连接,并提供可配置的缓存机制,减少重复的网络请求。
- 拦截器:OkHttp 提供了拦截器的机制,允许开发者在发送请求和接收响应的过程中进行自定义处理,例如添加公共参数、日志记录等。
- 支持 GZIP 压缩:OkHttp 支持接受和解压 GZIP 压缩的响应数据,减小网络传输的数据量,提升网络性能。
- 适配 Android 平台:OkHttp 在 Android 开发中得到广泛应用,它提供了专门针对 Android 平台的优化,包括性能、安全和稳定性方面的考虑。
2、OkHttp基本使用与请求流程
基本使用
val client = OkHttpClient.Builder()
.callTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
.connectTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
.readTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
.writeTimeout(5000L, java.util.concurrent.TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true)
.followRedirects(true)
.followSslRedirects(true)
.cache(null) // 设置缓存
.authenticator(null) // 设置身份验证器
.certificatePinner(null) // 设置证书锁定器
.connectionPool(null) // 设置连接池
.connectionSpecs(listOf()) // 设置连接规范
.cookieJar(null) // 设置 Cookie 管理器
.dispatcher(null) // 设置分发器
.dns(null) // 设置 DNS 解析器
.eventListenerFactory(null) // 设置事件监听器工厂
.proxy(null) // 设置代理
.protocols(listOf()) // 设置支持的协议
.proxyAuthenticator(null) // 设置代理身份验证器
.proxySelector(null) // 设置代理选择器
.socketFactory(null) // 设置 Socket 工厂
.sslSocketFactory(null) // 设置 SSL Socket 工厂
.hostnameVerifier(null) // 设置主机名验证器
.proxy(proxy) // 设置代理
.build()
val request = Request.Builder()
.url(url)
.header("xxx", "xxx")
.addHeader("xxx", "xxx")
.post(RequestBody.create(null, "XXX")) // 使用 POST 方法并传入请求体,不写默认为 GET 方法
.cacheControl(okhttp3.CacheControl.FORCE_NETWORK) // 设置缓存控制
.tag("custom-tag") // 设置标记
.build()
val call = client.newCall(request)
// 构造 Call 对象之后就可以同步或异步请求,并处理结果了
// 1、同步
client.newCall(call).execute().use { response ->
if (response.isSuccessful){
Log.v("同步请求响应:${response.body?.string()}")
}else{
Log.e("同步请求失败")
}
}
// 2、异步
client.newCall(call).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("异步请求失败: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
Log.v("异步请求响应:${response.body?.string()}")
}
})
请求流程
3、分发器Dispatcher
3.1、异步请求分发流程
首先我们要知道异步请求有两个队列,ready 队列和 running 队列,前者用来记录等待执行的请求,后者用来记录正在执行的请求:
// ready 队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
// running 队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
我们来看看 enqueue()
方法:
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()
}
其中 synchronized()
代码段中,先将请求加入 ready 队列,随后判断当前请求是否为 WebSocket,如果不是就调用 findExistingCallWithHost()
方法,在 running 和 ready 队列中查找与当前请求 Host 相同的请求,如果找到了,就让相同 Host 请求中的 callsPerHost
变量共享同一个对象,这个对象是 AtomicInteger 类型,用于对请求做一些限制,在下文有解释,可以先往下看。
synchronized()
代码段之外,执行的 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
}
synchronized()
代码段中主要完成了检查 ready 队列,并将符合条件可执行的任务添加到 running 队列和 executableCalls
队列中,这个 executableCalls
保存的才是这次要执行的请求,同步代码块之外,就遍历 executableCalls
执行请求。
主要过程可以用下图表示:
其中 ThreadPool 阶段较为重要,是请求效率提升的关键,我们先来回顾一下 Java 线程池的机制:
当一个任务通过 execute(Runnable)
方法添加到线程池时,有两种情况:
- 一、线程数量小于
corePoolSize
,则新建线程(核心)来处理被添加的任务。 - 二、线程数量大于或等于
corePoolSize
,则新任务被添加到等待队列,若添加失败:- 线程数量小于
maximumPoolSize
,新建线程执行新任务。 - 线程数量等于
maximumPoolSize
,使用 RejectedExecutionHandler 拒绝策略。
- 线程数量小于
了解了 Java 线程池机制后,我们来看下方 OkHttp 源码,下方的 ExecutorService 对象就是 OkHttp 中的线程池,其中为了提升请求效率,则不能让新任务被添加到等待队列,而是要新建线程执行新任务,因此将