Okhttp结构解析

okhttp是一个老牌的网络请求框架,这里是它的官网地址

具体的使用方法也可以参考官网地址

官网说默认的http客户端他有下面几个特点 

  • HTTP / 2支持允许对同一主机的所有请求共享一个套接字。
  • 连接池可减少请求延迟(如果HTTP / 2不可用)。
  • 透明的GZIP缩小了下载大小。
  • 响应缓存可以完全避免网络重复请求。 

今天就来看看什么个它。

首先还是来看看它的整体架构(v4.4.0 版本)。 

重点看几个类

OkhttpClient是一个对外使用的类,内部核心实现是RealCall.

RealCall是okhttp进行网络访问的核心现类,它实现了网络请求的逻辑。

Dispatcher 是一个任务调度的类,它内部维护三个请求队列和一个线程池,是任务的执行的管理者。

AsyncCall是一个runnable类,内部装了request请求和respone派发逻辑。

Interceptor是拦截器类,它的子类中分装了request处理和resonse处理的逻辑,okhttp中有很多拦截器。

接下来看异步消息的执行流程

这里可以看任务的派发是交给Dispatcher对象的。Dispatcher中定义了三个队列,准备执行异步队列(readyAsyncCalls),正在执行的异步队列(RunningAsycCalls),正在执行的同步请求队列(runningSyncCalls),任务队列维护的过程稍后再看。这里先来看一下线程程池对象executorService。


 @get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }
  
  @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!!
    }

这里使用比较特殊的无界线程池。线程池中使用的是阻塞队列SynchronousQueue,该队列中不会存数据,每次存入数据的时候必须等另外一个线程取完数据,所以线程池中不会存任务。

线程池的容量是无穷大(Int.MAX_VALUE)并且没有核心任务,每次新任务到来就新开线程,那如果任务很多并且耗时较长的时候,会不会出现oom呢?

不会,上面提到两个队列,readAsyncCalls和runningAsyncCalls ,其中runningAsyncCalls中任务的数量就是线程池中正在运行的线程个数,它容量被参数maxRequests(默认值是64)限制,新到的异步任务会直接加入到readAsyncCalls,然后从readAsyncCalls中取出来,如果runningAsyncCalls中的任务数量小于64,就从readAsyncCalls中转移到runningAsyncCalls中,然后把该任务投递到executorService中执行。如果runningAsyncCalls的任务数大于等于64,就暂时不转移当前任务的所属队列,不投递到线程池中。当该任务执行完毕的时候,就会把该请求从runningAsyncCalls中删除,然后从readAsyncCalls中开启新一轮的转移、投递行为。runningAsyncCalls中存储的任务最多为64,所以线程池中最多有64个并发任务,内存占用较小,不会出现oom。

什么?为啥不用固定线程池容量呢? 

如果固定线程是容量,这个线程数大小很难确定,同时如果线程容量定的小了,还需要指定任务拒绝策略。采用上述方式,每个任务都能得到执行,不需要定制拒绝策略。

任务添加的简单流程

值得注意的是Dispatcher.finished(Call)中会执行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()

        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
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

拦截器的运行方式

拦截器机制要从RealCall的 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)

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

可以看到这里使用MutableList数据结构 构建了一个Interceptor栈结构,注意RealInterceptorChain的够在方法中的index参数,它是递归核心参数,现在传递的是0,可以把它看作连接器在MutableList中的序号。

接下来是重要的递归调用。RealInterceptorChain的proceed(originalRequest)

  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)
    ......
    // Call the next interceptor in the chain.
    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
  }

  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)

上面重点看

val next = copy(index = index + 1, request = request)方法。

假设copy()方法重新构造了一个新的RealInterceptorChain简单记作RealInterceptorChainA, 更新了index = 0+1 = 1,和request,此时的request是最原始的requst,就是getResponseWithInterceptorChain()方法中的那个originRequest。

然后取得当前的序号index= 0的拦截器,执行它的拦截方法Intercept()。也就是MutableList中第一个添加的拦截器(client.interceptors)。

记第一个拦截器为client.interceptors,在client.interceptors的Intercept()方法中会对request相关的处理,修改完的request记作request1,然后调用RealInterceptorChainA的proceed(request),注意这里proceed方法中的request就是request1。处理过的request1就传递给了下一级调用。此时的RealInterceptorChainA的index=1。

开启下一轮循环。流程和上面类似,RealInterceptorChainA的proceed()中生成新的拦截器记作RealInterceptorChainB,它的index=1+1,request是上面传过来的request1;根据RealInterceptorChainA中index取得拦截器,index= 1的拦截器(RetryAndFollowUpInterceptor)。执行他的intercept()方法...

伴随着index的增加,MutableList中的每一个拦截器都能调用。一直到最后一个拦截器CallServerInterceptor返回response,结束d递归过程,函数调用栈中函数开始出栈,对response做拦截处理。所有的拦截器都对response做完处理就能够派发结果了。

拦截器调用栈如下图所示。

链接池ConnectionPool,实际类是RealConnection。

如果多个http请求是相同的请求地址,那么它的链接可以复用。链接池会保持五个空闲连接等待复用,每个连接的保持事件是5分钟。复用过程在ConnectInterceptor 

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

其中Exchange对象请求和响应的传输层,initExchange(chain)方法完成了连接池的复用的入口。该对象生成的连接会优先从连接池中查找,如果没有就新建连接。

ExchangeCodec接口管理了Http编码和解码(可以用Connection生成)。ExchangeFinder.findConnection()优先从链接池中找同意主机的连接,弱国找到就直接复用,如果没有找到就新建一个,然后存入链接池并返回该连接。

欢迎指出错误,本文长期维护更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值