前言
2023年希望自己有所输出,将自己看过的,感觉比较重要的知识整理记录下来,所以就先从比较常见的三方框架开始吧。
尽管接下来阅读的源码框架在网上资料会有很多,但是我还是想按照自己的思路整理总结一下,重点分析其流程和框架,从而避免“看过即忘过”的现象。
做技术,多读多练,就是最佳路径,加油加油加油!
OKHttp3版本:4.9.2
OKHttp 怎么使用
- 简单使用:
private fun testOkHttp() {
val client = OkHttpClient()
val request: Request = Request.Builder()
.url("https://baidu.com/")
.build()
//同步请求
val response: Response = client.newCall(request).execute()
//异步请求
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
}
- 创建OkHttpClient另一种方式:
val client = OkHttpClient.Builder()
.addInterceptor(UrlParamsInterceptor()) //设置请求参数拦截器,可以用于处理公参
.addInterceptor(ResponseInterceptor()) //设置接口返回拦截器,对返回数据统一做公共处理
.connectTimeout(2000L, TimeUnit.MILLISECONDS)//连接超时设置
.build()
通常项目中会自定义拦截器处理网络请求的公共逻辑,如上代码。
OKHttp使用起来是不是很简单,先创建一个OKHttpClient对象,然后调用newCall来发起请求,最后调用execute或者enqueue执行同步请求或者异步请求。
到这,大家一定会赞叹OKHttp框架封装的真棒,业务方使用起来很方便也很简洁,就算不太熟悉网络知识的同学,想实现一个网络请求,参考提示,也能很快就完成了。那它到底做了什么呢,运用了什么设计方式,来完成了一个复杂又强大的网络框架呢?接下来我们一点点分析。
OKHttp 主流程分析
一. 首先创建OKHttpClient对象,两种方式:
- 直接new OkHttpClient(),
- 使用OkHttpClient中的Builder类运用构造者模式来完成一个client对象创建。 两种方式殊途同归,都会进入到Builder的构造函数中,去初始化一些默认参数,如下:
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()// 分发器
internal var connectionPool: ConnectionPool = ConnectionPool()// 连接池
internal val interceptors: MutableList<Interceptor> = mutableListOf()// 拦截器集合
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()// 网络拦截器,业务方自定义的
......
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null// 安全套接层socket 工厂,用于HTTPS
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier// 验证确认响应证书 适用 HTTPS 请求连接的主机名。
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT // 证书锁定,使用CertificatePinner来约束哪些认证机构被信任。
internal var certificateChainCleaner: CertificateChainCleaner? = null
......
}
二. 创建Request请求
// 创建Request对象
val request: Request = Request.Builder()
.url("https://baidu.com/")
.build()
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl, // 请求地址
@get:JvmName("method") val method: String, // 请求方法
@get:JvmName("headers") val headers: Headers, // 请求头
@get:JvmName("body") val body: RequestBody?, // 请求体
internal val tags: Map<Class<*>, Any>
) {
......
open class Builder {
internal var url: HttpUrl? = null
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
/** A mutable map of tags, or an immutable empty map if we don't have any. */
internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
constructor() {
this.method = "GET" // 默认get请求
this.headers = Headers.Builder()
}
......
}
}
可见,Request对象很简单,使用构造者模式来配置请求地址,请求方法,请求头,请求体。
三. 发起请求
如下代码可知请求有两种,同步请求和异步,一般比较常用的是异步请求,避免阻塞主线流程。
//同步请求
val response: Response = client.newCall(request).execute()
//异步请求
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
不管是同步还是异步都需要通过client的newCall方法,创建了RealCall对象,然后调用了其内部方法来发起内部请求。下面来分析一下RealCall这个类,RealCall实现了Call接口:
interface Call : Cloneable {
/**
* 原始请求:没有被处理过的
*/
fun request(): Request
/**
* 同步请求,立即执行,失败抛出IO异常,
* 如果请求被执行时,被再次调用会抛出IllegalStateException
*/
@Throws(IOException::class)
fun execute(): Response
/**
* 异步请求,将请求安排在将来的某个时间点执行。
* 将请求结果通过Callback返回
*/
fun enqueue(responseCallback: Callback)
/** 是否被执行 */
fun isExecuted(): Boolean
/** 是否被取消 */
fun isCanceled(): Boolean
/** 一个完整Call请求流程的超时时间配置,默认选自[OkHttpClient.Builder.callTimeout] */
fun timeout(): Timeout
/** 克隆这个call,创建一个新的相同的Call */
public override fun clone(): Call
/** 利用工厂模式来让 OkHttpClient 来创建 Call对象 */
fun interface Factory {
fun newCall(request: Request): Call
}
}
RealCall
是Call接口
的具体实现类,是应用端与网络层的连接桥,展示应用端原始的请求与连接数据,以及网络层返回的response
及其它数据流。
1. 同步请求
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
// 调用分发器执行此请求,实际上就是把此次请求加入到runningSyncCalls中:正在执行的同步队列
client.dispatcher.executed(this)
// 执行拦截器链,获取到response,并返回
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
2.异步请求
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
// 调用分发器异步执行方法,将创建的异步请求传过去
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Dispatcher中:
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()
// 正在执行的异步请求队列,最大为64
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 同域名的最大请求数为5
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
// 都满足条件后,从已经准备好的异步请求队列中清除掉它
i.remove()
asyncCall.callsPerHost.incrementAndGet()
// 加入到可执行的请求集合中
executableCalls.add(asyncCall)
// 加入到正在执行的异步请求队列中
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// 遍历可执行队列,调用线程池来执行AsyncCall
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
Dispatcher的enqueue
方法就是将AsyncCall
加入到readyAsyncCalls
队列中,然后调用promoteAndExecute
方法来执行请求,promoteAndExecute
方法做的其实就是遍历readyAsyncCalls
队列,然后将符合条件的请求用线程池执行,也就是会执行AsyncCall.run()
方法。
AsyncCall.run()中:
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) {
......
} finally {
client.dispatcher.finished(this)
}
}
}
}
所以,同步请求和异步请求最终都会通过getResponseWithInterceptorChain()
来获取到请求结果。此方法中就涉及到各种拦截器的执行了。
3.获取请求结果
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// 创建拦截器集合
val interceptors = mutableListOf<Interceptor>()
// 业务方创建的应用拦截器,最早添加的,用于添加一些公参等
interceptors += client.interceptors
// 重试和重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 桥接拦截器,是客户端与服务器之间的沟通桥梁,负责将用户构建的请求转换为服务器需要的请求,以及将网络请求返回回来的响应转换为用户可用的响应
interceptors += BridgeInterceptor(client.cookieJar)
// 缓存拦截器,这里主要是缓存的相关处理,会根据用户在OkHttpClient里定义的缓存配置,然后结合请求新建一个缓存策略,由它来判断是使用网络还是缓存来构建response。
interceptors += CacheInterceptor(client.cache)
// 建立连接的拦截器,会建立`TCP连接`或者`TLS连接`
interceptors += ConnectInterceptor
if (!forWebSocket) {
// 业务方自己设置的,网络拦截器
interceptors += client.networkInterceptors
}
// 最后的拦截器,进行网络数据的请求和响应,将请求头与请求体发送给服务器,以及解析服务器返回的response
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 {
// 执行拦截器责任链来获取 response
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)
}
}
}
简单概括一下:这里采用了责任链设计模式,通过拦截器构建了以RealInterceptorChain
责任链,然后执行proceed
方法来得到response
。其中责任链中包含的各种拦截器的都在注释中标明了,下面我们来看一下,这个责任链到底是怎样运转的?
class RealInterceptorChain(......) : Interceptor.Chain {
······
private var calls: Int = 0
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
// 统计当前拦截器调用proceed方法的次数
calls++
// exchage是对请求流的封装,在执行ConnectInterceptor时,才通过initExchange方法初始化,之前都是null
if (exchange != null) {
// 后面的拦截器不允许修改host和port,因为在ConnectInterceptor中已经建立了连接和请求流
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
// 这里是对拦截器调用proceed方法的限制,自定义的network拦截器最多只能调用一次proceed
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
//index+1, 复制创建新的责任链,也就意味着调用责任链中的下一个处理者,也就是下一个拦截器
val next = copy(index = index + 1, request = request)
//取出当前拦截器
val interceptor = interceptors[index]
//执行当前拦截器的拦截方法
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
// 异常处理
if (exchange != null) {
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
}
}
即:拦截器都实现了Interceptor
接口,通过调用其intercept(chain)
方法来执行各个拦截器里面的逻辑,在该方法中又通过调用chain的proceed
方法进行下一个拦截器的处理,知道最后的 CallServerInterceptor
拦截器拿到请求结果,依次传递回去。拦截器工作流程见下图:
OKHttp 请求流程图
通过上面的分析我们可以得到发起一个网络请求的详细流程图如下:
总结
到这里相信大家对OKHttp的请求流程都清楚了,下面我们再总结一下该框架中常见的几种设计模式:
- 建造者模式:
OkHttpClient
、Request
、Response
中都用到了该模式,因为这几个类中都有很多参数,需要供用户选择需要的参数来构建其想要的实例,所以在开源库中,Build模式
是很常见的。 - 工厂方法模式:
OkHttpClient.newCall(request Request) 来创建 Call 对象
。 - 责任链模式:这个应该是该框架设计上的精华了,将7种拦截器构成拦截器责任链,然后按顺序从上往下执行,得到
Response
后,从下往上传回去。
数据结构上,三种请求队列:readyAsyncCalls
runningAsyncCalls
runningSyncCalls
采用ArrayDeque
来保证请求先存进去的首先发起,并且,在enqueue中,需要进行遍历readyAsyncCalls,这相对于链表,数组的查找效率要更高些。
心得感悟
第一篇源码分析整理完了,对OKHttp的理解和使用更加清晰了一些。对于阅读源码,最重要的还是按照流程,一步步来,同时记录下关键地方,读到最后肯定会看明白的。