探索OkHttp系列 (一) 请求的发起与响应

前言

OkHttp是个人使用的比较多的网络请求库,但是一直没有探究它的实现原理,这次就对OkHttp的源码进行分析,探究其实现原理。

分析的okhttp源码版本:4.9.2

基本使用

GET

同步地发起请求,会阻塞线程,不能直接在主线程当中调用

    private fun getData() {
        thread {
            val client: OkHttpClient = OkHttpClient()
            val request: Request = Request.Builder()
                .get()
                .url("https://www.baidu.com")
                .build()
            val response: Response = client.newCall(request).execute()
            val data: String? = response.body?.string()
            Log.d(TAG, "OkHttp 同步请求:$data")
        }
    }

获取OkHttpClient实例,有两种方法,一是像上面代码一样,直接new 一个OkHttpClient对象;二是new 一个OkHttpClientBuilder对象,可以对client配置一些参数,如下

val client: OkHttpClient = OkHttpClient.Builder().build()

异步地发起请求,会自动切换线程

    private fun getData() {
        val client: OkHttpClient = OkHttpClient()
        val request: Request = Request.Builder()
            .get()
            .url("https://www.baidu.com")
            .build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {

            }

            override fun onResponse(call: Call, response: Response) {
                val data: String? = response.body?.string()
                Log.d(TAG, "OkHttp 同步请求:$data")
            }
        })
    }

POST

POST请求,相比于GET请求,多了一个构造RequestBody的步骤,并且请求方法要从get改为post

同步请求,会阻塞线程,不能直接在主线程当中调用

    private fun getData() {
        thread{
            val client: OkHttpClient = OkHttpClient()
            val requestBody: RequestBody = FormBody.Builder()
                .add("username", "EDGNB")
                .add("password", "123456")
                .build()
            val request: Request = Request.Builder()
                .url("https://www.wanandroid.com/user/login")
                .post(requestBody)
                .build()
            val response: Response = client.newCall(request).execute()
            val data: String? = response.body?.string()
            Log.d(TAG, "OkHttp 同步请求:$data")
        }
    }

异步请求

    private fun getData() {
        val client: OkHttpClient = OkHttpClient()
        val requestBody: RequestBody = FormBody.Builder()
            .add("username", "EDGNB")
            .add("password", "123456")
            .build()
        val request: Request = Request.Builder()
            .url("https://www.wanandroid.com/user/login")
            .post(requestBody)
            .build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {

            }

            override fun onResponse(call: Call, response: Response) {
                val data: String? = response.body?.string()
                Log.d(TAG, "OkHttp 同步请求:$data")
            }
        })
    }

对象的创建

OkHttpClientRequest的构造均使用到了建造者Builder模式,调用者可直接通过链式调用对这些对象进行自定义初始化。Request类和OkHttpClient类的本质都是一个描述对象。

OkHttpClient

前面提到,构造一个OkHttpClient有两种方式

// 方式一
val client: OkHttpClient = OkHttpClient()
// 方式二
val client: OkHttpClient = OkHttpClient.Builder().build()

我们查看方式一,它调用了下面的构造方法

  constructor() : this(Builder())

构造了一个Builder实例,并调用了主构造函数,OkHttpClient的成员变量会利用构造的Builder,进行初始化

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

  val dispatcher: Dispatcher = builder.dispatcher

  val connectionPool: ConnectionPool = builder.connectionPool

  val interceptors: List<Interceptor> =
      builder.interceptors.toImmutableList()
	...
}

OkHttpClient.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 eventListenerFactory: EventListener.Factory = 
      									EventListener.NONE.asFactory()
    // 连接失败是否自动重试
    internal var retryOnConnectionFailure = true
    // 服务器认证设置
    internal var authenticator: Authenticator = Authenticator.NONE
    // http是否重定向
    internal var followRedirects = true
    // 是否可以从HTTP重定向到HTTPS
    internal var followSslRedirects = true
    // Cookie策略,是否保存Cookie  
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    // 缓存配置
    internal var cache: Cache? = null
    // Dns配置
    internal var dns: Dns = Dns.SYSTEM
    // 代理
    internal var proxy: Proxy? = null
    // 代理选择器
    internal var proxySelector: ProxySelector? = null
    // 代理服务器认证设置
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    // socket工厂
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    // sslSocket工厂 用于https
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    // 证书信息管理
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    // 传输层版本和连接协议 TLS等
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    // 支持的协议
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    // 主机名校验
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    // 证书链
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    // 证书确认
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    // 调用超时时间( 0的含义? )
    internal var callTimeout = 0
    // 连接超时时间  
    internal var connectTimeout = 10_000
    // 读取超时时间
    internal var readTimeout = 10_000
    // 写入超时时间
    internal var writeTimeout = 10_000
    // 针对HTTP2和web socket的ping间隔
    internal var pingInterval = 0
    internal var minWebSocketMessageToCompress = 
      							RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    // 路由数据库
    internal var routeDatabase: RouteDatabase? = null
  	...
  }

Request

Request用于描述单次请求的参数信息,使用方法如下

val request: Request = Request.Builder()
    .get()
    .url("https://www.baidu.com")
    .build()

Request.Builderbuild方法中,利用自身保存的属性,去构造一个Request对象

    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }

Request.Builder有如下属性

  open class Builder {
    // 请求url  
    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. */
    // 请求tag
    internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
  	...
  }

发起请求

无论是同步请求

val response: Response = client.newCall(request).execute()

还是异步请求

client.newCall(request).enqueue(...)

都需要先调用OkHttpClientnewCall方法,该方法如下

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = 
                                                        false)

该方法返回一个RealCall对象,定义如下

class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {...}

它是Call接口的唯一实现类,内部保存了

  • client:也就是前面的OkHttpClient
  • originalRequest:我们前面创建的Request,用于描述单次请求的参数信息
  • forWebSocket:是否使用Web Socket,默认值为false

RealCall包装了RequestOKHttpClient这两个类的实例,使得后面的方法中可以很方便的使用这两者。

插叙:ArrayDeque

我们后面会使用到ArrayDeque,这里先简单介绍ArrayDeque是什么,以及它的作用

ArrayDequeJDK的一个容器,它有队列的性质的抽象数据类型,继承结构如下

image-20211109220101712

Java中,我们使用栈一般会使用Stack类,而队列的话,Java提供了Queue接口,我们可以使用LinkedList来实现队列的功能(LinkedList实现了Deque接口,Deque继承了Queue接口)。

ArrayDeque继承结构可以看出,它实现了Deque接口,因此它也有队列的功能,在ArrayDeque类中有这样的一段注释

 /*
  * This class is likely to be faster than
  * {@link Stack} when used as a stack, and faster than {@link LinkedList}
  * when used as a queue.
  * /

它表达了两层意思:

  • 当作为「栈」来使用的时候,它的性能比Stack更快
  • 当作为「队列」来使用的时候,它的性能比LinkedList更快

Stack类的注释中,我们也可以看到它推荐我们使用ArrayDeque

/*
 * <p>A more complete and consistent set of LIFO stack operations is
 * provided by the {@link Deque} interface and its implementations, which
 * should be used in preference to this class.  For example:
 * <pre>   {@code
 *   Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
 */

ArrayDeque实现了Deque接口,Dequedouble-ended queue的缩写,意为「双端队列」,什么意思呢?我们查看Deque定义了哪些方法

public interface Deque<E> extends Queue<E> {
    /**
    * 在 首、尾 添加元素
    */
	void addFirst(E e);
    void addLast(E e);
    boolean offerFirst(E e);
    boolean offerLast(E e);
    
    /**
    * 移除并返回 首、尾 的元素
    */
    E removeFirst();
    E removeLast();
    E pollFirst();
    E pollLast();
    
    /**
    * 返回(但不移除) 首、尾 的元素
    */
    E getFirst();
    E getLast();
    E peekFirst();
    E peekLast();
    
    ...
}

我们在Deque的「首、尾」两端都可以做元素的「插入、删除」操作,比如

  • 我们在双端队列的队头插入,队头弹出,那么它就可以作为一个来使用
  • 我们在双端队列的队头插入,队尾弹出,那么它就可以作为一个队列来使用

或者在双端队列的队尾插入,队头弹出,也是可以作为一个队列来使用

ArrayDeque类的注释中,我们还可以得到下面有关ArrayDeque的信息

  • 没有容量限制,根据实际需要自动扩容
  • 线程不安全
  • 不允许放入null的元素

另外,ArrayDeque内部是使用循环数组来实现的。

同步请求

同步请求要调用RealCallexecute方法,定义如下

  override fun execute(): Response {
    // 使用CAS,保证这个RealCall对象只发起一次请求  
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      // 将请求添加到任务队列当中  
      client.dispatcher.executed(this)
      // 通过一系列拦截器的请求处理和响应处理,获取返回结果,并return  
      return getResponseWithInterceptorChain()
    } finally {
      // 不论是否成功,通知Dispatcher请求完成  
      client.dispatcher.finished(this)
    }
  }

我们首先调用了OkHttpClient的调度器dispatcherexecuted方法,来将请求添加到任务队列当中,Dispatcher::executed如下

  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

runningSyncCalls是一个ArrayDeque,定义如下

private val runningSyncCalls = ArrayDeque<RealCall>()

前面我们已经介绍过ArrayDeque了,executed方法调用了runningSyncCallsadd方法,我们注意到executed方法使用了同步锁,使用同步锁的原因是因为ArrayDeque线程不安全的。

为什么在OkHttp中选择ArrayDeque这种容器作为任务队列?

答:因为ArrayDeque效率高,在「插叙:ArrayDeque」中,我们提到ArrayDeque可以作为栈和队列,并且性能优于StackLinkedList

client.dispatcher.executed(this)所做的事情,就是将这个请求添加到一个双端队列中。

回到RealCallbackexecute方法中,又调用了getResponseWithInterceptorChain方法,该方法如下

  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    // 按顺序添加一系列的拦截器
    val interceptors = mutableListOf<Interceptor>()
    // 添加用户自定义拦截器  
    interceptors += client.interceptors
    // 添加重定向拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    // 桥拦截器  
    interceptors += BridgeInterceptor(client.cookieJar)
    // 添加缓存拦截器,从缓存中拿数据
    interceptors += CacheInterceptor(client.cache)
    // 添加网络连接拦截器,负责建立网络连接
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    // 服务器请求拦截器,负责向服务器发送请求数据、从服务器读取响应数据
    interceptors += CallServerInterceptor(forWebSocket)

    // 构建一条责任链,注意前面构建的拦截器列表interceptors作为参数传入
    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) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

getResponseWithInterceptorChain方法会按一定的顺序构建拦截器列表interceptors,接着利用interceptors创建RealInterceptorChain对象,该对象是一条责任链,使用了责任链模式,拦截器会按顺序依次调用,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里。

最后在RealCallbackexecute方法中调用client.dispatcher.finished(this),通知dispatcher请求完成。

这就是一个同步请求发起与响应的过程,关于Dispatcher和拦截器责任链是如何工作的,稍后分析。

小结

发起一个同步请求的语句是

client.newCall(request).execute()
  1. 首先调用OkHttpClientnewCall方法,传入Request,返回一个RealCall对象
  2. 调用RealCall对象的execute方法。在该方法中,先调用OkHttpClientdispatcherexecuted方法,将请求加入dispatcher中的双端队列中;再调用getResponseWithInterceptorChain(),通过拦截器责任链获取响应,最后通知dispatcher请求完成。

异步请求

发起异步请求的方法

client.newCall(request).enqueue(object : Callback {...})

这里也是构造一个RealCall对象,然后调用RealCall对象的enqueue方法,传入Callback

我们查看RealCallenqueue方法

  override fun enqueue(responseCallback: Callback) {
    // 使用CAS,保证这个RealCall对象只发起一次请求    
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    // 创建AsyncCall对象,然后通过dispatcher分发任务  
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

这里创建了一个AsyncCall对象,构造参数是用户传入的Callback,然后调用OkHttpClientdispatcherenqueue方法,传入我们创建的AsyncCall对象。

Dispatcherenqueue方法如下

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      // 将AsyncCall添加到请求队列中
      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) {
        // 寻找同一host的Call  
        val existingCall = findExistingCallWithHost(call.host)
        // 复用Call的callsPerHost
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    // 尝试执行等待队列中的任务  
    promoteAndExecute()
  }

readyAsyncCalls同样是一个双端队列,其类型为ArrayDeque<AsyncCall>(),注意,同步任务是添加到runningSyncCalls当中,异步任务是添加到readyAsyncCalls当中。

Dispatcher::promoteAndExecute如下

  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    // 创建一个待执行任务列表  
    val executableCalls = mutableListOf<AsyncCall>()
    // 标识是否有任务在执行
    val isRunning: Boolean
    synchronized(this) {
      // 前面就是将异步任务AsyncCall添加到readyAsyncCalls当中 
      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.
		// 从等待队列中移除AsyncCall
        i.remove()
        // 同一主机名请求数+1
        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
  }

到现在为止,我们接触到了三个队列,在这里区分一下

  • runningSyncCalls:类型是ArrayDeque<RealCall>,「同步任务」的执行队列
  • readyAsyncCalls:类型是ArrayDeque<AsyncCall>,「异步任务」的等待队列
  • runningAsyncCalls:类型是ArrayDeque<AsyncCall>,「异步任务」的执行队列

它们都是ArrayDeque,双端队列。

AsyncCall表示一个异步任务,它是RealCall的一个内部类,并且实现了Runnable接口,定义如下

  internal inner class AsyncCall(
    // responseCallback就是用户传入的获取处理结果的回调  
    private val responseCallback: Callback
  ) : Runnable {
	
     ...

    /**
     * Attempt to enqueue this async call on [executorService]. This will attempt to 
     * clean up if the executor has been shut down by reporting the call as failed.
     */
    // 对外暴露的接口,通过传入一个线程池来执行该任务  
    fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        // 尝试在线程池中执行该任务  
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        // 线程池拒绝执行该任务,抛出异常  
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        // 回调onFailure方法,通知用户执行失败  
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          // 通知dispatcher请求完成  
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }

    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) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            // 出现异常,回调onFailure方法,通知用户执行失败    
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            // 出现异常,回调onFailure方法,通知用户执行失败      
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          // 通知dispatcher请求完成  
          client.dispatcher.finished(this)
        }
      }
    }
  }

异步线程池

异步请求在线程池中执行,我们向AsyncCallexecuteOn方法传入的参数就是「异步任务执行的线程池」,传入的参数是DispatcherexecutorServiceexecutorService对应的线程池可以在构造Dispatcher的时候指定,若用户没有指定线程池,那么默认是使用下面的线程池

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

构造线程池的几个参数如下

  • 核心线程数corePoolSize:表示线程池中空闲线程也不会被回收的数量,这里设置为0说明线程池中所有空闲线程都会被回收。
  • 最大线程数maximumPoolSize:线程池最大支持创建的线程数,这里设置为Int.MAX_VALUE
  • 线程存活时间keepAliveTime:非核心线程空闲后超过该时间就会被回收,这里设置为60个时间单位(60s)。
  • 时间单位TimeUnit:空闲线程存活的时间单位,这里设置为秒TimeUnit.SECONDS
  • 线程池中的任务队列workQueue:该队列主要用来存储已经被提交但是尚未执行的任务,这里设置为SynchronousQueue()
  • 线程工厂ThreadFactory:线程的创建工厂,这个线程工厂指定了创建的线程的「名字」,以及创建的线程「非守护线程」。

对于上面指定的参数,我们思考下面的几个问题:

为什么要选用SynchronousQueue作为任务队列?

首先我们要了解一下SynchronousQueue是什么,它是一个队列,但它内部不存在任何的容器,它是一种经典的生产者-消费者模型,它有多个生产者和消费者,当一个生产线程进行生产操作(put)时,若没有消费者线程进行消费(take),那么该线程会阻塞,直到有消费者进行消费。也就是说,它仅仅实现了一个传递的操作,这种传递功能由于没有了中间的放入容器,再从容器中取出的过程,因此是一种快速传递元素的方式,这对于我们网络请求这种高频请求来说,是十分合适的。关于 SynchronousQueue 可以看这篇文章:java并发之SynchronousQueue实现原理_-CSDN博客.

线程池的最大线程数不受限制,可以无限制的进行并发网络请求吗?

OkHttp的设计中,线程个数的维护工作不再交给线程池,而是由Dispatcher实现,通过外部设置的maxRequests以及maxRequestsPerHost来调整异步请求的等待队列以及执行队列,从而实现对线程最大数量的控制。Dispatcher具体实现将在后面分析。

小结

发起一个异步请求的语句是

client.newCall(request).enqueue(object : Callback {...})
  1. 首先调用OkHttpClientnewCall方法,传入Request,返回一个RealCall对象
  2. 调用RealCall对象的enqueue方法,并传入结果回调Callback
  3. ReallCall::enqueue中,利用Callback构造了一个AsyncCall对象,并调用了OkHttpClientdispatcherenqueue方法,将AsyncCall作为参数传入
  4. Dispatcher::enqueue中,先将AsyncCall添加到请求队列当中,然后调用自己的promoteAndExecute方法,尝试执行等待队列中的任务
  5. Dispatcher::promoteAndExecute方法会遍历异步任务的等待队列,然后尝试在线程池中去执行这些异步任务

异步任务的执行,也是调用getResponseWithInterceptorChain方法,通过拦截器责任链获取请求的响应,这一点和「同步请求」是一样的。

Dispatcher

前面多次出现了调度器Dispatcher的身影,这里对它做一个总结。

维护的队列

Dispatcher中维护了三个队列:

  • runningSyncCalls:类型是ArrayDeque<RealCall>,「同步任务」的执行队列
  • readyAsyncCalls:类型是ArrayDeque<AsyncCall>,「异步任务」的等待队列
  • runningAsyncCalls:类型是ArrayDeque<AsyncCall>,「异步任务」的执行队列

请求相关

同步请求中,RealCallexecute方法会调用到Dispatcherexecuted方法,将「同步任务」保存到runningSyncCalls

  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

异步请求中,RealCallenqueue方法会调用Dispatcherenqueue方法

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      // 将AsyncCall添加到请求队列中
      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) {
        // 寻找同一host的Call  
        val existingCall = findExistingCallWithHost(call.host)
        // 复用Call的callsPerHost
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    // 尝试执行等待队列中的任务  
    promoteAndExecute()
  }

Dispatcherenqueue方法中,首先将「异步任务」添加到readyAsyncCalls中,然后调用promoteAndExecute()方法,该方法会遍历readyAsyncCalls,寻找能够执行的AsyncCall,能够执行的条件是「当前总请求数」以及「同一主机名的请求数」都没有达到上限,最后调用能够执行的AsyncCallexecuteOn方法,在Dispatcher提供的线程池中执行异步任务。

通知完成

前面我们已经知道,不管是同步请求,还是异步请求,在请求完成后,都会调用Dispatcherfinished方法,通知Dispatcher请求已执行完毕。我们前面没有展开分析Dispatcher被通知后会做些什么,这里就对它进行分析。

「同步请求」完成后会调用Dispatcher下面的方法

  /** Used by [Call.execute] to signal completion. */
  internal fun finished(call: RealCall) {
    finished(runningSyncCalls, call)
  }

「异步请求」完成后会调用Dispatcher下面的方法

  /** Used by [AsyncCall.run] to signal completion. */
  internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

「异步请求」调用的finished比「同步请求」调用的finished多了一个callsPerHost减1的操作,也就是「主机名请求数」减1的操作。接着,它们都会调用到Dispatcher下面的这个重载方法

  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      // 将「同步任务/异步任务」从「执行队列」当中移除  
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    // 尝试执行「等待队列」中的「异步任务」   
    val isRunning = promoteAndExecute()

    // isRunning:在「执行队列」中有「同步/异步任务」待执行
    // idleCallback:Dispatcher空闲时的回调
    // 若「执行队列」中没有待执行的任务,表明当前的Dispatcher是空闲的,这时候,如果idleCallback
    // 不为null,就执行idleCallback的run方法  
    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

小结

Dispatcher 在 网络请求发起过程中的总结:

  1. Dispatcher中维护三个任务队列,实现方式都为双端队列,分别为同步请求执行队列,异步请求等待队列,异步请求执行队列。
  2. 当发起一个同步/异步请求,就会将该请求添加到同步请求执行队列/异步请求等待队列中,然后尝试执行这些请求。
  3. 请求执行完毕就会将这些请求从队列中移除,若后面没有需要执行的任务后,调用idleCallback.run()执行一些空闲任务。
  4. 异步请求执行有两个入口:一是添加了一个异步请求,二是一个请求执行完毕。

请求时序图

同步请求的时序图:

image-20211113011234986

异步请求的时序图:

image-20211113011738025

请求响应

无论是同步请求,还是异步请求,最终都会调用到RealCallgetResponseWithInterceptorChain方法,通过该方法可以拿到请求的响应。关于getResponseWithInterceptorChain方法,我们需要探究两个关键的点

  • 核心机制:拦截器机制
  • 设计模式:在拦截器中如何运用责任链模式

什么是责任链模式?在典型的责任链设计模式里,很多对象由每一个对象对其下级的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

我们回顾下getResponseWithInterceptorChain方法

  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    // 按顺序添加一系列的拦截器
    val interceptors = mutableListOf<Interceptor>()
    // 添加用户自定义拦截器  
    interceptors += client.interceptors
    // 添加重定向拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    // 桥拦截器  
    interceptors += BridgeInterceptor(client.cookieJar)
    // 添加缓存拦截器,从缓存中拿数据
    interceptors += CacheInterceptor(client.cache)
    // 添加网络连接拦截器,负责建立网络连接
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    // 服务器请求拦截器,负责向服务器发送请求数据、从服务器读取响应数据
    interceptors += CallServerInterceptor(forWebSocket)

    // 构建一条责任链,注意前面构建的拦截器列表interceptors作为参数传入
    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) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

该方法会按一定的顺序构建拦截器列表interceptors,接着利用interceptors创建RealInterceptorChain对象,该对象是一条责任链,使用了责任链模式,拦截器会按顺序依次调用,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里。

Interceptor

我们先看下拦截器Interceptor的定义

fun interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response

  companion object {
	// 利用lambda,创建一个拦截器
    inline operator fun invoke(crossinline block: (chain: Chain) -> Response): 
      Interceptor = Interceptor { block(it) }
  }

  interface Chain {
    fun request(): Request
    @Throws(IOException::class)
    fun proceed(request: Request): Response
    fun connection(): Connection?
    fun call(): Call
    fun connectTimeoutMillis(): Int
    fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain
    fun readTimeoutMillis(): Int
    fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain
    fun writeTimeoutMillis(): Int
    fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
  }
}

该接口里面定义了intercept方法,还有一个Chain接口,拦截器链RealInterceptorChain就是实现了Chain接口。

虽然不同的拦截器实现不同,但是它们往往具有相同的结构:

  @Throws(IOException::class)
  override fun intercept(chain: Chain): Response {
    // 获取Request  
    val request = chain.request
    // 处理:Request阶段,拦截器对Request进行处理

    // 调用RealInterceptorChain的proceed方法,在该方法里面,会递归调用下一个拦截器的intercept
    // 方法,拿到下一个拦截器的response  
    val response = (chain as RealInterceptorChain).proceed(request)

    // 处理:Response阶段,完成了该拦截器获取Response的过程,将Response返回到上一个拦截器中
    return response
}

拦截器链的整体执行流程

我们先来看一个生活中的例子,以便更好地理解「拦截器链」是如何工作的。下面是一个流水线生产的例子

在这里插入图片描述

对应到OkHttp的响应过程就是:包装了RequestChain递归的从每个Interceptor手中走过去,最后请求网络得到的Response又会逆序的从每个Interceptor走回来,把Response返回到开发者手中。

我们来看看拦截器链RealInterceptorChain的工作流程

class RealInterceptorChain(
  internal val call: RealCall,
  private val interceptors: List<Interceptor>,
  private val index: Int,
  internal val exchange: Exchange?,
  internal val request: Request,
  internal val connectTimeoutMillis: Int,
  internal val readTimeoutMillis: Int,
  internal val writeTimeoutMillis: Int
) : Interceptor.Chain {

  ...

  internal fun copy(
    index: Int = this.index,
    exchange: Exchange? = this.exchange,
    request: Request = this.request,
    connectTimeoutMillis: Int = this.connectTimeoutMillis,
    readTimeoutMillis: Int = this.readTimeoutMillis,
    writeTimeoutMillis: Int = this.writeTimeoutMillis
  ) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
      readTimeoutMillis, writeTimeoutMillis)

  ...
    
  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++
    ...
    // Call the next interceptor in the chain.
    // copy出一个拦截器链,并且将index设置为(index+1),便于之后「调用责任链的下一个拦截器」  
    val next = copy(index = index + 1, request = request)
    // 获取当前的index对应的拦截器  
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    // 执行当前拦截器的intercept方法
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
    ...
    return response
  }
}

要很好地理解拦截器链的工作流程,需要将RealInterceptorChainproceed方法与前面拦截器的intercept方法结合起来看。我们理一下它们的工作流程,其中RealInterceptorChain简称为Chain

  • RealCallgetResponseWithInterceptorChain方法中,先调用index为0的Chainproceed方法,并传入originalRequest
  • proceed方法里,copy出一个Chain(index+1(1),request),然后调用当前index(0)Interceptorintercept方法,并且传入新copy出来的Chain
  • Interceptorintercept方法,从Chain当中获取request,然后对request添加一些自己的处理,接着调用Chainproceed方法,传入处理后的request,等待返回response
  • proceed方法里,copy出一个Chain(index+1(2),request),然后调用当前index(1)Interceptorintercept方法,并且传入新copy出来的Chain
  • Interceptorintercept方法,从Chain当中获取request,然后对request添加一些自己的处理,接着调用Chainproceed方法,传入处理后的request,等待返回response
  • proceed方法里,copy出一个Chain(index+1(3),request),然后调用当前index(2)Interceptorintercept方法,并且传入新copy出来的Chain
  • 最后一个Interceptorintercept方法处理完后,向上返回处理后的Response
  • 倒数第二个Interceptorintercept方法中,对返回的Response进行一些加工后向上返回
  • 倒数第三个Interceptorintercept方法中,对返回的Response进行一些加工后向上返回
  • 最终在RealCallgetResponseWithInterceptorChain方法中,获取到最终Interceptor处理完后的Response,然后将该Response传给用户,通知用户请求成功完成。

copy拦截器链的时候,下标index设置为index+1就是为了让拦截器责任链能准备地从拦截器容器中取出下一个拦截器进行处理。

整个工作过程就是,当前拦截器对Request处理完毕后,通过调用传入的责任链的 proceed 方法,将请求交给下一个拦截器处理,然后获得下一个拦截器返回的Response,再对Response处理后交给上一个拦截器。

显然在这里使用了一种递归设计,请求从上往下交付,每个拦截器拿到请求都可以对请求做出相应处理,然后交给下一个拦截器,最后一个拦截器处理请求拿到响应后,将响应从下往上交付,每个拦截器拿到响应后再对响应做出响应处理,然后交给上一个拦截器。若中间的过程中若出现了错误,则通过抛出异常来通知上层。

「中间的某个拦截器」也可以选择不将请求Request交给下一级拦截器处理,这时候「该拦截器」直接返回Response给上一级的拦截器即可。

Interceptor顺序

RealCallgetResponseWithInterceptorChain方法中,我们可以看出OkHttp预置了哪些Interceptor、用户可以自定义哪些Interceptor,以及这些Interceptor之间的顺序

  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    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
    )
	...
  }

OkHttp中预置了下面几种Interceptor

  • RetryAndFollowUpInterceptor:负责实现重定向功能
  • BridgeInterceptor:将用户构造的请求转换为向服务器发送的请求,将服务器返回的响应转换为对用户友好的响应
  • CacheInterceptor:读取缓存、更新缓存
  • ConnectInterceptor:建立与服务器的连接
  • CallServerInterceptor:从服务器读取响应

可以看出,整个网络请求的过程由各个拦截器互相配合从而实现,通过这种拦截器的机制,可以很方便地调节网络请求的过程及先后顺序,同时也能够很方便地使用户对其进行扩展。

其中用户可以在两个时机插入 Interceptor

  • 网络请求前后:通过 OkHttpClient.Builder.addInterceptor 方法添加
  • 读取响应前后:通过 OkHttpClient.Builder.addNetworkInterceptor 方法添加

其整体流程如图所示:
在这里插入图片描述

总结

OkHttp采用了拦截器机制责任链模式,它使用多个负责不同功能的拦截器,并将这些拦截器通过责任链连接在一起,采用递归的方式进行调用,每一层的拦截器在请求前和响应后都可以对 请求/响应 做出不同的处理,通过各个拦截器的协调合作,完成了网络请求的响应过程。

参考

  1. OkHttp源码分析系列(一)请求的发起与响应 - 养生达人不熬夜.
  2. java并发之SynchronousQueue实现原理_-CSDN博客.
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值