读源码挑战第一篇 - OKHttp3 源码分析

247 阅读7分钟

前言

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
  }
}

RealCallCall接口的具体实现类,是应用端与网络层的连接桥,展示应用端原始的请求与连接数据,以及网络层返回的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拦截器拿到请求结果,依次传递回去。拦截器工作流程见下图:

拦截器.png

OKHttp 请求流程图

通过上面的分析我们可以得到发起一个网络请求的详细流程图如下: okhttp-流程图.drawio.png

总结

到这里相信大家对OKHttp的请求流程都清楚了,下面我们再总结一下该框架中常见的几种设计模式:

  • 建造者模式OkHttpClientRequestResponse中都用到了该模式,因为这几个类中都有很多参数,需要供用户选择需要的参数来构建其想要的实例,所以在开源库中,Build模式是很常见的。
  • 工厂方法模式: OkHttpClient.newCall(request Request) 来创建 Call 对象
  • 责任链模式:这个应该是该框架设计上的精华了,将7种拦截器构成拦截器责任链,然后按顺序从上往下执行,得到Response后,从下往上传回去。

数据结构上,三种请求队列:readyAsyncCalls runningAsyncCalls runningSyncCalls采用ArrayDeque来保证请求先存进去的首先发起,并且,在enqueue中,需要进行遍历readyAsyncCalls,这相对于链表,数组的查找效率要更高些。

心得感悟

第一篇源码分析整理完了,对OKHttp的理解和使用更加清晰了一些。对于阅读源码,最重要的还是按照流程,一步步来,同时记录下关键地方,读到最后肯定会看明白的。

参考

  1. github.com/square/okht…
  2. juejin.cn/post/684490…
  3. segmentfault.com/a/119000004…