这是 OkHttp 深度解析系列文章
OkHttp深度解析(一) : 从一次完整请求看 OkHttp整体架构
OkHttp深度解析(二) : OkHttpClient 你没见过的那些属性
OkHttp深度解析(三) : 拦截器?连接复用&合并?Http2?
前言
OkHttp 是一个非常优秀的网络请求框架,几乎每个 Android 开发人员应该都使用过它。
本文深入介绍了 Okhttp 的整体框架结构,OkhttpClient重点属性深度解释,并且深度分析 了一次完整的的 Http 请求在Okhttp中是如何实现的 ,我个人习惯系统性了解一项技术而不是单纯的罗列源码通篇讲解,因为只有建立出系统,才能理解的更透彻,这样记忆会非常深刻。因此我侧重使用画图的方式来分析 Okhttp(只有关键源码),图是重点,文字是辅助(画图好纍😅)。
这篇文章应该是你从未见过的OkHttp 全流程网络请求的深入解读,它同时也可能解惑你在其它博客中找不到答案的疑问。
OkHttp 发展背景
-
诞生背景
在OkHttp出现之前,Android开发者主要使用Apache的
HttpClient和Google自家的HttpURLConnection进行网络请求。但这两者多少都会存在一些问题。美国的移动支付公司Square在开发过程中,深感这些原生网络库难以满足其需求,便在2013年开源了OkHttp。 -
发展之路: 从包装到替代
OkHttp并非一开始就完全另起炉灶,其发展经历了几个阶段:
- 初期包装 :最初,OkHttp是对原生
HttpClient和HttpURLConnection的封装,旨在提供更友好、统一的API。 - 剥离与独立 :随着Apache宣布弃用
HttpClient,OkHttp也移除了对其的支持。后来,Square团队认为HttpURLConnection也不够好用,索性去掉了对它的依赖,转而直接从 Socket 层完全重写整个 Http 网络请求**,实现了真正的独立。 - 反向影响 :OkHttp因其出色的设计和性能,受到了广大开发者的欢迎。Google也注意到了这一点,并在Android 4.4 (KitKat) 系统中,将
HttpURLConnection的底层实现替换成了OkHttp**。这意味着,即使你在代码中使用的是标准的HttpURLConnection,其底层实际使用的 Okhttp的代码。 - 向Kotlin迈进:从OkHttp 4版本开始,Square开始使用Kotlin语言进行重写
- 初期包装 :最初,OkHttp是对原生
OkHttp 整体框架
首先我们先看一下使用 Okhttp 如何完成一个网络请求:
kotlin
//okhttp 网络请求示例(异步)
val client = OkHttpClient()
val request = Request.Builder()
.url("")
.build()
client.newCall(request)
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
//处理错误
}
override fun onResponse(call: Call, response: Response) {
//处理 response
}
})
代码非常简单,这得益于 Okhttp 优秀的框架设计,以下是以一次网络请求流程的视角来系统的了解 Okhttp 的整体结构:

文字说明:(请在看完文字说明后回来再看一次上面的图,理解会更深刻,而且不容易忘记)
-
通过 OkHttpClient.Builder 来构造出一个 OkHttpClient 实例(这个实例在绝大部分场景都应该是单例的),后面会详细介绍 OkHttpClient 中各个属性配置的作用
-
通过 Request.Builder 来构造出一个 Request 实例,这表示一个 originalRequest(原始请求),开发者需要配置一个请求的基础参数
-
通过 newCall 方法(携带 request 参数)创建一个 Call 对象,实际为 RealCall 实例,它包含了以下参数:
kotlinclass RealCall( val client: OkHttpClient,//开发者创建出来的 OkHttp Client 实例 /** 一个未经重定向或者没有添加认证头的原始请求, 也就是未经OkHttp内部处理的请求,并不是实际发给服务器的请求 */ val originalRequest: Request, val forWebSocket: Boolean //发起的是否是一个WebSocket请求,因为WebSocket也是基于Http的协议 ) : Call { ...... }也就是说我们创建的这个原始请求对象,被转化为 了一个 RealCall,后面的每个 RealCall 就代表了一个请求
-
开始使用 execute(同步)/enqueue(异步)的方式发起一个 Http 请求
-
enqueue 方式: enqueue 需要传入一个 callback 对象来接收返回结果,整体逻辑如下:
kotlinoverride fun enqueue(responseCallback: Callback) { //检查请求的执行状态 check(executed.compareAndSet(false, true)) { "Already Executed" } //事件记录,标记开始执行请求 callStart() //初始化一个异步的 Call,并入队 client.dispatcher.enqueue(AsyncCall(responseCallback)) }💡 PS: 关于check 方法,这是 Kotlin 标准库中的方法,它内部使用的是 Kotlin Contract 这个高级特性
此处简单介绍下这个方法,因为在 Okhttp 中大量使用到了它
kotlin@kotlin.internal.InlineOnly public inline fun check(value: Boolean, lazyMessage: () -> Any): Unit { contract { returns() implies value } if (!value) { val message = lazyMessage() throw IllegalStateException(message.toString()) } }contract 表示契约,implies 表示 暗示,这个方法的作用是 开发者 告诉 编译器 如果 check 方法能够正常返回(也就是不会抛出异常),则暗示检查成功,否则抛出异常信息。
enqueue 方法中 check 的作用就是请求执行状态的检查
-
**
executed**是一个AtomicBoolean,用于标记这个Call是否已被执行过。compareAndSet(false, true)尝试将其值从false设置为true。 -
check函数 :如果传入的表达式结果为false(即设置失败,说明executed已经是true,意味着这个Call已经被执行过),check函数就会抛出一个IllegalStateException,异常信息为 "Already Executed"。 -
这保证了 一个
Call实例只能被执行一次,这是 OkHttp 防止重复请求的重要设计。
🔑 关于 Contract 的更深入理解,请移步此处
keep on :
kotlininternal fun enqueue(call: AsyncCall) { synchronized(this) { //将这个请求添加到准备执行的队列中 readyAsyncCalls.add(call) /**下面这两行代码的意思是:找到与当前的这个请求主机名相同的Call, 然后使它们共享「同一个主机请求个数」这个变量。 为何共享?因为 Okhttp 要求同一个主机的请求个数不能大于5个(默认) */ if (!call.call.forWebSocket) { val existingCall = findExistingCallWithHost(call.host) if (existingCall != null) call.reuseCallsPerHostFrom(existingCall) } } //开始推举并执行请求 promoteAndExecute() }kotlin//promote 表示推动,意思就是对readyAsyncCalls中 Call 进行进一步的挑选,然后执行 private fun promoteAndExecute(): Boolean { this.assertThreadDoesntHoldLock() //遍历 readyAsyncCalls,挑选出不会导致超负载的 Call 来添加至 executableCalls //同时也会添加到 runningAsyncCalls中(runningAsyncCalls中也包含已取消单但未执行完的Call) //runningAsyncCalls 主要用于记录正在运行的 Call 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 } //开始遍历 executableCalls 然后执行每个 AsyncCall for (i in 0 until executableCalls.size) { val asyncCall = executableCalls[i] asyncCall.executeOn(executorService) } return isRunning }由于 AsyncCall 继承了 Runnable ,因此它表示一个需要放进线程池中执行的具体任务,这是异步请求的核心(此处仅贴出核心代码)
kotlininternal inner class AsyncCall( private val responseCallback: Callback ) : Runnable { @Volatile var callsPerHost = AtomicInteger(0) private set fun reuseCallsPerHostFrom(other: AsyncCall) { this.callsPerHost = other.callsPerHost } ... fun executeOn(executorService: ExecutorService) { ... executorService.execute(this) ... } override fun run() { ... //这是异步任务的核心,通过拦截器链的链式调用方式执行一个请求并返回结果 val response = getResponseWithInterceptorChain() ... //把返回的 response 传给 callback responseCallback.onResponse(this@RealCall, response) ... } } -