OkHttp源码之深度解析(一)——整体框架分析

OkHttp源码之深度解析(一)——整体框架分析

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

前言

OkHttp源码系列文章:

OkHttp这个网络请求框架对于很多人来说并不陌生,目前很火的Retrofit也是基于OkHttp的,本文将对OkHttp源码的整体框架和大致流程进行解析。

PS:本文基于OkHttp3版本4.9.3

OkHttp的基本使用

下面是OkHttp的简单使用实例:

//创建OkHttpClient
val client = OkHttpClient.Builder()
            .addInterceptor { chain ->
                val request = chain.request()
                val builder = request.newBuilder()
                    .addHeader("Token", "...")
                chain.proceed(builder.build())
            }
            .build()

//创建请求
val request = Request.Builder()
            .url("http://test.com")
            .build()

//发起异步请求
client.newCall(request).enqueue(object : Callback {
    ......
})
复制代码

代码很简单,先创建出一个OkHttpClient对象和Request对象,然后利用他们去构建一个Call用来发起网络请求,最终拿到Response,下面就来看看框架内部是怎么处理请求过程的。

源码分析

1. OkHttpClient类

OkHttpClient类的主要职责就是让用户配置一些请求参数(如interceptor、connectTimeout等等),采用建造者模式通过Builder类去配置OkHttpClient的成员变量,最后调用build方法创建出OkHttpClient实例。

在OkHttpClient类中的主要成员变量有:

  • dispatcher: Dispatcher,用于调度网络请求的分发器
  • interceptors: MutableList<Interceptor>,拦截器集合
  • networkInterceptors: MutableList<Interceptor>,用户自定义的网络拦截器
  • connectionPool: ConnectionPool,连接池
  • protocols: List<Protocol>,支持的Http协议版本,即HTTP/1.1、HTTP/2等
  • cache: Cache,缓存配置,默认是没有
  • cookieJar: CookieJar,cookie配置
  • callTimeout: Int,请求超时,默认是0
  • connectTimeout: Int,连接超时,默认10秒
  • readTimeout: Int,读取超时,默认10秒
  • writeTimeout: Int,写入超时,默认10秒
  • pingInterval: Int,发送ping指令间隔,与WebSocket有关,为了保持长连接
  • retryOnConnectionFailure: Boolean,连接失败是否重连
  • followRedirects: Boolean,是否重定向
  • ollowSslRedirects: Boolean,是否从HTTP重定向到HTTPS
  • hostnameVerifier: HostnameVerifier,域名校验
  • eventListenerFactory: EventListener.Factory,Call的生命周期监听器

OkHttpClient中的参数都不是必要的配置项,如果初始化OkHttpClient的时候没有设置则会使用默认配置,而且这些配置会被所有call请求共享。

2. Request类

Request类的职责与OkHttpClient类相似,也是用来配置请求参数的,通过Builder类去配置以下的参数:

  • url: HttpUrl,请求url
  • method: String,请求方法,如果没有指定的话会默认配置GET
  • headers: Headers,请求头
  • body: RequestBody,请求体
  • tags: Map<Class<*>, Any>,请求标签

3. RealCall类

在OkHttpClient对象调用newCall方法创建Call的时候实际上就会进入到RealCall类中:

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
复制代码

也就是构建Call的工作是由RealCall去做的,RealCall是连接应用和网络层的桥梁,通过调用RealCall的同步请求execute方法或异步请求enqueue方法发起请求最后拿到Response,下面来分析RealCall类中的主要方法。

3.1 同步请求execute方法

override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
复制代码

execute方法主要做了以下的工作:

  • 检测这个Call是否已经被执行过,一个Call只能被执行一次,如果已经执行过则抛出异常
  • 开启请求超时的计时和开启请求生命周期的监听
  • 调用Dispatcher的executed方法将这个RealCall对象加入到正在执行的Call队列中
  • 调用getResponseWithInterceptorChain方法构建拦截器链,遍历拦截器链后返回Response
  • 请求执行完后调用Dispatcher的finish方法将这个Call从队列中移除

3.2 getResponseWithInterceptorChain方法

internal fun getResponseWithInterceptorChain(): Response {
    val interceptors = mutableListOf<Interceptor>()
    ......//省略一堆构建拦截器列表的逻辑

    //构建拦截器链
    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 {
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      ......//异常处理
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }
复制代码

getResponseWithInterceptorChain方法是整个OkHttp框架执行请求的核心所在,采用了责任链模式,本文先对拦截器责任链的构建有个整体了解,后续的文章会详细分析。getResponseWithInterceptorChain方法主要做了以下的事:

  • 根据用户自定义的拦截器和OkHttp内置的拦截器按顺序构建出拦截器列表
  • 构建拦截器责任链RealInterceptorChain
  • 调用RealInterceptorChain的proceed方法遍历拦截器链执行请求,直到所有拦截器都完成拦截最后返回Response
  • 当请求被取消时关闭响应并抛出异常

3.3 异步请求enqueue方法

override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}
复制代码

enqueue方法同样先是检测Call是否已被执行,最后调用Dispatcher的enqueue方法去执行请求,值得注意的是同步请求生成的是RealCall对象,而异步请求生成的是AsyncCall对象。

另外这个callStart方法同步和异步都有调用到,方法内部调用到了EventListener的callStart:

private fun callStart() {
    this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
    eventListener.callStart(this)
}
复制代码

追踪源码发现EventListener的所有回调都是在一个叫LoggingEventListener里面实现的,LoggingEventListener只是负责在Call的每个状态打日志,由此推断出EventListener是用于监听Call的生命周期的,callStart这个回调会在请求开始的时候被触发,当然如果在开发中有特殊要求的话可以自定义EventListenerFactory用于生产需要的EventListener,并在配置OkHttpClient的时候传进去。

4. AsyncCall类

AsyncCall类是定义在RealCall类中的一个内部类,AsyncCall实现了Runnable接口,里面主要是executeOn方法和重写了run方法,先来看看executeOn方法的源码:

fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()
      var success = false
      try {
        executorService.execute(this) //调用线程池执行请求
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ......//异常处理
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this)
        }
      }
    }
复制代码

executeOn方法主要是通过入参传入一个线程池,并调用这个线程池执行请求,请求失败则抛出异常并将异常通过回调返回去,最后调用Dispatcher的finish方法将这个请求移出队列。再来看下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) {
          if (signalledCallback) {
            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)
        }
      }
    }
复制代码

由于AsyncCall实现了Runnable接口,所以run方法是异步的具体实现:

  • 跟同步请求一样通过调用getResponseWithInterceptorChain方法来获取请求结果,由于AsyncCall是RealCall的内部类,因此可以直接访问RealCall的方法getResponseWithInterceptorChain而不需要通过一个RealCall实例去调用
  • 将拿到的Response通过CallBack.onResponse返回
  • 如果请求失败则将错误信息通过CallBack.onFailure返回
  • 如果请求异常则取消请求,同时将异常信息抛出并回调
  • 最后在请求结束时调用Dispatcher的finish方法将这个请求移出队列

除了上面提到的两个主要方法之外,AsyncCall内部还持有了一个RealCall实例:

val call: RealCall
        get() = this@RealCall
复制代码

然后在追踪这个RealCall实例的调用处发现,这个对象只是给Dispatcher用来访问RealCall里的成员的,而AsyncCall本身并没有用到它。

对于这一块本人有个疑惑,RealCall实现了Call接口,AsyncCall实现了Runnable接口,而AsyncCall是定义在RealCall中的inner class,两者之间不存在继承的关系,在AsyncCall中又持有着一个RealCall对象,有种套娃的感觉。在处理异步请求时Dispatcher需要访问RealCall类里面的成员,这时只能通过AsyncCall持有的RealCall实例去访问,既然这样那为什么AsyncCall不直接继承于RealCall呢?继承于RealCall也并不影响AsyncCall实现Runnable接口,这样AsyncCall直接就是一个RealCall了,Dispatcher不就直接能访问里面的成员了吗?至于为什么要这样设计我也不太想明白,欢迎各位朋友在评论区谈谈自己的见解。

5. Dispatcher类

在看RealCall的源码时不难发现,无论是同步请求还是异步请求,都会进入到分发器Dispatcher中做相应的处理,下面就来分析一下Dispatcher的主要职责是什么,先来看下Dispatcher中的成员变量:

class Dispatcher constructor() {

  @get:Synchronized var maxRequests = 64
  @get:Synchronized var maxRequestsPerHost = 5

  @set:Synchronized
  @get:Synchronized
  var idleCallback: Runnable? = null

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService

  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  private val runningSyncCalls = ArrayDeque<RealCall>()
  ......
}
复制代码

Dispatcher中定义了以下的成员变量:

  • maxRequests:并行的最大的请求数为64个
  • maxRequestsPerHost:同一个host并行的最大请求数为5个
  • idleCallback:分发器空闲时的回调
  • executorService:线程池
  • readyAsyncCalls:已准备好的异步请求队列
  • runningAsyncCalls:正在执行的异步请求队列,包含已经被取消但还没完成的AsyncCall
  • runningSyncCalls:正在执行的同步请求队列,包含已经被取消但还没完成的RealCall

除了这些成员变量还定义了一些用来调度Call的方法,也就是说Dispatcher的主要职责就是用来调度Call对象的,内部维护了一个线程池和一些同步/异步请求队列,用于存放和调度Call。

请求流程

到这里OkHttp的整体框架大致分析完了,请求的时候OkHttpClient会通过newCall方法去访问RealCall,如果是发起同步请求则会生成一个RealCall对象通过execute方法传给分发器Dispatcher处理,如果是异步请求则会生成一个AsyncCall对象传给Dispatcher,无论是同步请求还是异步请求最终都是通过getResponseWithInterceptorChain方法去遍历拦截器链获取Response,下面是OkHttp的简单请求流程:

sequenceDiagram
OkHttpClient->>RealCall: newCall(Request)
RealCall->>RealInterceptorChain:getResponseWithInterceptorChain
RealCall->>Dispatcher: execute(RealCall)
RealCall-->>AsyncCall:enqueue
AsyncCall-->>Dispatcher:enqueue(AsyncCall)
Dispatcher-->>AsyncCall:promoteAndExecute
AsyncCall-->>RealInterceptorChain:getResponseWithInterceptorChain
RealInterceptorChain->>RealCall:Response
RealCall->>OkHttpClient:Response

PS:本文仅对OkHttp从大体框架上进行说明,Dispatcher、拦截器等具体模块的详细分析敬请关注后续文章,另外此文章仅代表个人见解,理解有偏差的地方欢迎各位大佬指出。

分类:
Android
标签: