OkHttp源码解析

OkHttp源码解析

1 OkHttp介绍与用法示例

1.1 OkHttp简介

Http是现代应用程序的网络请求方式,也是应用进行数据交互和流媒体播放的方式,高效的Http数据交互可以让我们的应用加载更快,同时能够节省宽带费用。OkHttp正是这样的一个高效Http客户端,其中OkHttp的项目地址为:github.com/square/okht...

可以看到,OkHttp天然就有以下优点(敲黑板,划重点了,选择OkHttp的好处):

  • 由于支持Http协议,可以让所有的请求指向同一个host,分享同一个socket
  • 链接池化技术降低了请求延时
  • Gzip压缩减少了下载数据量
  • 请求结果缓存避免了重复的数据请求

在网络存在异常的时候,OkHttp会自动处理一些常见的网络请求异常问题。如果我们请求的服务存在多个IP且第一次连接失败的时候,OkHttp会自动替换我们请求的IP。同时OkHttp支持TLS(Transport Layer Security,安全传输层协议)。

1.2 OkHttp的用法示例

首先还是依赖的导入,在项目的配置文件中增加以下依赖:

kotlin 复制代码
implementation("com.squareup.okhttp3:okhttp:5.1.0")

当前最新的版本是5.1.0,需要使用新版本的可以去项目地址看一下最新的版本。然后是使用方法介绍:

java 复制代码
// 创建client实例
OkHttpClient client = new OkHttpClient();
public static final MediaType JSON = MediaType.get("application/json");

// get请求
String run(String url) throws IOException {
  // 创建request请求
  Request request = new Request.Builder()
      .url(url)
      .build();
  // 同步执行
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

// post请求
String post(String url, String json) throws IOException {
  // 创建请求体
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  // 同步的方式执行请求
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

上面介绍的方式是使用同步的方式执行,实际的开发过程中通常是以异步回调的方式进行http请求的,下面给出一个异步回调方式请求服务端接口示例:

java 复制代码
// get请求
String run(String url) throws IOException {
  // 创建request请求
  Request request = new Request.Builder()
      .url(url)
      .build();
  // 异步执行
  client.newCall().enqueue(new Callback(){
      @override
      void onResponse(Call call,Response response){
          // todo 接口返回处理
      }
      
      @override
      void onFail(Call call,IOException e){
          // todo 接口异常处理
      }
  })
}

可以看到异步的请求的时候,使用的是okhttp3.internal.connection.RealCall#enqueue方法,同时需要传入一个回调监听,根据回调返回的接口进行数据的处理。无论是同步的方式还是异步的方式,进行http请求的方式都是很简单,那么这些请求代码里OkHttp都在后面默默做了些什么呢,接下来看看代码的主流程分析。

2 OkHttp源码实现分析

2.1 OkHttp主流程分析

  1. 从1.2中给出的代码示例来看,主要的入口还是在okhttp3.OkHttpClient类中,该类中提供了一个okhttp3.OkHttpClient.Builder类,用于配置OkHttp的各种配置、监听等属性,具体的可配置项还是挺多的,具体的每一项及其用途这里就不一一列举了,其中有几个重要如interceptors、读写超时配置、重试机制等配置。获取到OkHttpClient实例(在实际的项目中通常是将创建OkHttp实例封装起来放在一个单例里面,在需要的地方直接使用相关方法获取)后,无论是同步执行还是异步的方式调用都用到了okhttp3.OkHttpClient#newCall方法,这个方法返回了一个okhttp3.internal.connection.RealCall实例,以同步方式为例,走到了okhttp3.internal.connection.RealCall#execute方法,代码如下:
kotlin 复制代码
override fun execute(): Response {
  // a、检查执行状态
  check(executed.compareAndSet(false, true)) { "Already Executed" }
  timeout.enter()
  // b、通知eventListener接口调用
  callStart()
  try {
    // c、 将当前call实例添加到okhttp3.Dispatcher#runningSyncCalls队列
    client.dispatcher.executed(this)
    // d、 大名鼎鼎的拦截器模式
    return getResponseWithInterceptorChain()
  } finally {
    // e、 执行调度器的下一轮处理
    client.dispatcher.finished(this)
  }
}
  1. 从流程1给出的代码看,主要做了5件事情,其中client.dispatcher.executed(this)将当前的RealCall实例加入到Dispatcher的runningSyncCalls调度队列中,重点看看okhttp3.internal.connection.RealCall#getResponseWithInterceptorChain方法中做了些什么,其实现代码如下:
kotlin 复制代码
  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)
      }
    }
  }
  1. 根据流程2列出来的代码中,可以看到责任链中除了自定义的拦截器外,OkHttp框架本身提供了5个默认的拦截器分别是:
  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor
    这五个拦截器在okhttp3.internal.http.RealInterceptorChain中如何被调用的,我们来一起看下okhttp3.internal.http.RealInterceptorChain#proceed方法中做了些什么:
kotlin 复制代码
override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++
    
    // 流程2的代码19行可以看到exchange为null,这里直接跳过不执行
    if (exchange != null) {
      check(exchange.finder.routePlanner.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // Call the next interceptor in the chain.
    // 通过copy方法传入index+1,得到下一个拦截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    // 下面的interceptor变量是索引为index的拦截器
    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"
      }
    }

    return response
  }
  1. 在流程3中的代码里,重点关注索引为0的拦截器运行的 okhttp3.Interceptor#intercept方法,假如我们在初始化OkHttpClient实例的时候没有传入自定义的拦截器,那么拦截器数组interceptors中只会有框架默认的拦截器,也就是上面流程3中列出来的5个默认的拦截器,下面按照拦截器顺序依次看各个拦截器的实现(拦截器实现了okhttp3.Interceptor#intercept方法)。首先看下RetryAndFollowUpInterceptor拦截器的intercept方法:
kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    // a、初始化各个变量,其中重点关注newRoutePlanner值为true
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    var followUpCount = 0
    var priorResponse: Response? = null
    var newRoutePlanner = true
    var recoveredFailures = listOf<IOException>()
    
    while (true) {
      // b、设置okhttp3.internal.connection.RealCall#exchangeFinder的值
      call.enterNetworkInterceptorExchange(request, newRoutePlanner, chain)

      var response: Response
      var closeActiveExchange = true
      try {
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }

        try {
          // c、重复流程3中贴出的proceed代码,进入下一个拦截器的intercept方法处理逻辑
          response = realChain.proceed(request)
          newRoutePlanner = true
        } catch (e: IOException) {
          // An attempt to communicate with a server failed. The request may have been sent.
          // d、收到异常后,通知eventListener,退出循环
          val isRecoverable = recover(e, call, request)
          call.eventListener.retryDecision(call, e, isRecoverable)
          if (!isRecoverable) throw e.withSuppressed(recoveredFailures)
          recoveredFailures += e
          newRoutePlanner = false
          continue
        }

        // Clear out downstream interceptor's additional request headers, cookies, etc.
        response =
          response
            .newBuilder()
            .request(request)
            .priorResponse(priorResponse?.stripBody())
            .build()
            
        // e、接受用户自定义的拦截器中返回的Response,并填充相应的验证标签信息,处理重定向
        val exchange = call.interceptorScopedExchange
        val followUp = followUpRequest(response, exchange)

        // f、followUp为空表示,follow-up不必要或不可达,直接退出
        if (followUp == null) {
          if (exchange != null && exchange.isDuplex) {
            call.timeoutEarlyExit()
          }
          closeActiveExchange = false
          call.eventListener.followUpDecision(call, response, null)
          return response
        }

        // g、followUpBody为一次性的请求,直接返回当前response
        val followUpBody = followUp.body
        if (followUpBody != null && followUpBody.isOneShot()) {
          closeActiveExchange = false
          call.eventListener.followUpDecision(call, response, null)
          return response
        }

        response.body.closeQuietly()

        // h、超过最大的follow-up次数,直接抛出异常
        if (++followUpCount > MAX_FOLLOW_UPS) {
          call.eventListener.followUpDecision(call, response, null)
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }
        
        // i、更新当前的follow-up请求和返回,继续从b处开始循环
        call.eventListener.followUpDecision(call, response, followUp)
        request = followUp
        priorResponse = response
      } finally {
        // j、退出当前重试机制,设置标志位
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

代码看起来很多,重点的步骤是b、c,在循环内处理了重试和重定向,笔者都在对应位置做了代码注释。其中注释b处的处理在后续的ConnectInterceptor拦截器中还会用到。

5、接下来看看BridgeInterceptor中做了些什么:

kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request()
    val requestBuilder = userRequest.newBuilder()
    
    // a、此处省略了一系列的cookie和网络请求头的设置
    
    // b、进入下一个拦截器的处理环节
    val networkRequest = requestBuilder.build()
    val networkResponse = chain.proceed(networkRequest)

    cookieJar.receiveHeaders(networkRequest.url, networkResponse.headers)

    val responseBuilder =
      networkResponse
        .newBuilder()
        .request(networkRequest)
        
    // c、处理Gzip压缩的特殊情况
    if (transparentGzip &&
      "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
      networkResponse.promisesBody()
    ) {
      val responseBody = networkResponse.body
      if (responseBody != null) {
        val gzipSource = GzipSource(responseBody.source())
        val strippedHeaders =
          networkResponse.headers
            .newBuilder()
            .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build()
        responseBuilder.headers(strippedHeaders)
        val contentType = networkResponse.header("Content-Type")
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }

    return responseBuilder.build()
  }

可以看出BridgeInterceptor主要是处理一些和cookie相关的请求头设置,Gzip协议的特殊处理。整体相对简单。

  1. 接下来看看CacheInterceptor里面做了一些什么处理,代码如下:
kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    val cacheCandidate = cache?.get(chain.request().requestForCache())

    val now = System.currentTimeMillis()
    
    // a、通过compute方法得到一个关于缓存的数据结构CacheStrategy
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
    
    // b、通过DiskLruCache中获取的cacheCandidate与compute得到的缓存都存在,
    // 关闭获取自DiskLruCache中拿到的cacheCandidate
    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body.closeQuietly()
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // c、通过compute方法返回的CacheStrategy,缓存失效,但网络不可用,返回状态码504网关超时
    if (networkRequest == null && cacheResponse == null) {
      return Response
        .Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(HTTP_GATEWAY_TIMEOUT)
        .message("Unsatisfiable Request (only-if-cached)")
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
        .also {
          listener.satisfactionFailure(call, it)
        }
    }

    // If we don't need the network, we're done.
    // d、通过compute方法返回的CacheStrategy中,只有在CacheStrategy.Factory#computeCandidate
    // 方法中,允许访问缓存且缓存未过期(涉及几个缓存时间的计算),则直接返回CacheStrategy的缓存
    if (networkRequest == null) {
      return cacheResponse!!
        .newBuilder()
        .cacheResponse(cacheResponse.stripBody())
        .build()
        .also {
          listener.cacheHit(call, it)
        }
    }

    // 通过监听回调通知缓存命中情况
    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }

    // e、进入下一个拦截器的处理,实际上就是真正走网络请求了
    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body.closeQuietly()
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        // f、缓存不为空且网络数据的code为HTTP_NOT_MODIFIED,更新缓存的相关标签
        // headers、时间戳等数据,并更新LruDiskCache中的数据,同时通知回调缓存命中
        val response =
          cacheResponse
            .newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(cacheResponse.stripBody())
            .networkResponse(networkResponse.stripBody())
            .build()

        networkResponse.body.close()

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache!!.trackConditionalCacheHit()
        cache.update(cacheResponse, response)
        return response.also {
          listener.cacheHit(call, it)
        }
      } else {
        cacheResponse.body.closeQuietly()
      }
    }

    // g、使用网络返回数据并更新LruDiskCache缓存
    val response =
      networkResponse!!
        .newBuilder()
        .cacheResponse(cacheResponse?.stripBody())
        .networkResponse(networkResponse.stripBody())
        .build()

    if (cache != null) {
      val cacheNetworkRequest = networkRequest.requestForCache()

      if (response.promisesBody() && CacheStrategy.isCacheable(response, cacheNetworkRequest)) {
        // Offer this request to the cache.
        val cacheRequest = cache.put(response.newBuilder().request(cacheNetworkRequest).build())
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }

      // h、更新由于特殊的网络请求引起的LruDiskCache中的缓存
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }

    return response
  }

这部分其实是实际面试中喜欢问到的(划重点了),主要涉及到http的一些缓存知识,在主流程里面未深入去讲http里面的eTag、lastModified标签。先留个坑,在后面仔细讲一下这一块知识点。

  1. 接下来看看ConnectInterceptor拦截器的实现:
kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    // a、在流程4的注释b处,初始化了exchangeFinder,在这个方法内部会用到,这里很重要
    val exchange = realChain.call.initExchange(realChain)
    // b、更新realChain中的exchange值
    val connectedChain = realChain.copy(exchange = exchange)
    // c、进入下一个拦截器的处理逻辑内
    return connectedChain.proceed(realChain.request)
  }

注释a处会调用okhttp3.internal.connection.RealCall#enterNetworkInterceptorExchange中初始化的exchangeFinder,正常没在初始化OkHttpClient初始化的时候配置过的话,默认会返回FastFallbackExchangeFinder,如果配置了okhttp3.OkHttpClient.Builder#fastFallback变量为false的话,就会返回SequentialExchangeFinder类型。这两个类都实现了find接口,调用者通过find方法返回的RealConnection进行数据交互。这两个类的实现先留个坑,先继续我们的主流程。

  1. CallServerInterceptor拦截器的实现如下:
kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    var sendRequestException: IOException? = null
    try {
      // a、向服务器发送请求
      exchange.writeRequestHeaders(request)

      if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
        // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
        // Continue" response before transmitting the request body. If we don't get that, return
        // what we did get (such as a 4xx response) without ever transmitting the request body.
        // b、源码的注释意思是,请求里面有"Expect:100-continue",需要等待一个
        // 完整的"HTTP/1.1 100 Continue"请求后才会真正发送请求体。如果我们没正常收
        // 到"HTTP/1.1 100 Continue"的服务端返回,将不会发送请求体,且会受到一个4XX的返回码
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
          exchange.flushRequest()
          responseBuilder = exchange.readResponseHeaders(expectContinue = true)
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
        if (responseBuilder == null) {
          if (requestBody.isDuplex()) {
            // Prepare a duplex body so that the application can send a request body later.
            // b、双工模式下准备请求体并写入(http2才支持的协议)
            exchange.flushRequest()
            val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
            requestBody.writeTo(bufferedRequestBody)
          } else {
            // Write the request body if the "Expect: 100-continue" expectation was met.
            // c、非双工模式下,准备请求体并写入
            val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
            requestBody.writeTo(bufferedRequestBody)
            bufferedRequestBody.close()
          }
        } else {
          // d、未满足状态码100,返回的responseBuilder不为空,通知请求结束,防止当前连接重用
          exchange.noRequestBody()
          if (!exchange.connection.isMultiplexed) {
            // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
            // from being reused. Otherwise we're still obligated to transmit the request body to
            // leave the connection in a consistent state.
            exchange.noNewExchangesOnConnection()
          }
        }
      } else {
        exchange.noRequestBody()
      }

      // e、请求发送结束
      if (requestBody == null || !requestBody.isDuplex()) {
        exchange.finishRequest()
      }
    } catch (e: IOException) {
      if (e is ConnectionShutdownException) {
        throw e // No request was sent so there's no response to read.
      }
      if (!exchange.hasFailure) {
        throw e // Don't attempt to read the response; we failed to send the request.
      }
      sendRequestException = e
    }

    try {
      if (responseBuilder == null) {
        // f、读取返回的请求头
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
      }
      var response =
        responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .bui读取
      var code = response.code
        
      // g、如果返回码为100、102~199,都循环读取
      while (shouldIgnoreAndWaitForRealResponse(code, exchange)) {
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
        }
        response =
          responseBuilder
            .request(request)
            .handshake(exchange.connection.handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build()
        code = response.code
      }

      exchange.responseHeadersEnd(response)
       
       // h、打开请求的请求体,结束整个请求过程
      response =
        if (forWebSocket && code == 101) {
          // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
          response.stripBody()
        } else {
          response
            .newBuilder()
            .body(exchange.openResponseBody(response))
            .build()
        }
      if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)
      ) {
        exchange.noNewExchangesOnConnection()
      }
      if ((code == 204 || code == 205) && response.body.contentLength() > 0L) {
        throw ProtocolException(
          "HTTP $code had non-zero Content-Length: ${response.body.contentLength()}",
        )
      }
      return response
    } catch (e: IOException) {
      if (sendRequestException != null) {
        sendRequestException.addSuppressed(e)
        throw sendRequestException
      }
      throw e
    }
  }

至此整个请求的主流程就结束了,其实还是埋下了不少细节上的坑。可以看出来整个主流程里面其实没太涉及到Socket底层相关的源码,这是因为OkHttp中将更加底层的Socket代码细节隐藏了,我们在源码的主流程中没有涉及到这一块,其实还有缓存相关的处理细节、OkHttp请求任务调度细节等。接下来就看看留下来的坑,开始填坑工作。

2.2 OkHttp源码加载细节

在前面的主流程分析中,我们介绍了整个请求的创建过程,从OkHttpClient配置到生成一个新的RealCall执行,最终进入OkHttp大名鼎鼎的拦截器模式处理,由5个OkHttp的默认拦截器依次处理整个请求流程,在2.1我们分析了整个主流程和5个拦截器的实现。本节一起来看看隐藏在主流程中的实现细节。

2.2.1 OkHttp任务调度实现

在1.2中我们介绍了任务执行的两种方式,同步的方式okhttp3.internal.connection.RealCall#execute,该方法实现如下:

kotlin 复制代码
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)
    }
  }

跟进okhttp3.Dispatcher#executed方法内部实现你会发现很简单,实现如下:

kotlin 复制代码
@Synchronized
  internal fun executed(call: RealCall) = runningSyncCalls.add(call)

加了锁的同步方法,往Dispatcher内部的runningSyncCalls添加一个RealCall实例。继续看看异步方法okhttp3.internal.connection.RealCall#enqueue方法,同样也是调用了Dispatcher的enqueue方法,我们直接看一下Dispatcher的enqueue方法:

kotlin 复制代码
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()
  }

可以看到enqueue方法将AsynCall实例加入到readyAsyncCalls队列内,需要注意的是equeue方法内代码块加了锁。如果不是一个WebSocket请求,会根据请求的host在异步队列readyAsyncCalls和runningAsyncCalls中寻找已存在的请求,并重用找到的call。实际RealCall的reuseCallsPerHostFrom方法是更新了一下每个host主机上存在的call请求数量。在讲重点的okhttp3.Dispatcher#promoteAndExecute方法之前,我需要介绍一下几个Dispatcher的变量:

  1. maxRequests 最大并发请求量(默认值为64)
  2. maxRequestPerHost 单个host主机的最大并发请求量(默认值为5)
  3. readyAsyncCalls 等待队列中的异步请求列表
  4. runningAsyncCalls 正在执行的异步请求列表
  5. runningSyncCalls 正在执行的同步请求

了解了以上的几个变量之后,再来看promoteAndExecute方法的实现就很简单了:

kotlin 复制代码
  private fun promoteAndExecute(): Boolean {
    assertLockNotHeld()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    // a、遍历readyAsyncCalls队列,找出满足条件的可执行AsyncCall
    // 实例(不超过maxRequests和maxRequestsPerHost)
    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
    }

    // Avoid resubmitting if we can't logically progress
    // particularly because RealCall handles a RejectedExecutionException
    // by executing on the same thread.
    if (executorService.isShutdown) {
      // b、处理executorService处于shutdown状态的数据处理
      for (i in 0 until executableCalls.size) {
        val asyncCall = executableCalls[i]
        asyncCall.callsPerHost.decrementAndGet()

        synchronized(this) {
          runningAsyncCalls.remove(asyncCall)
        }

        asyncCall.failRejected()
      }
      idleCallback?.run()
    } else {
      // c、遍历执行注释a处找到的可执行AsyncCall实例
      for (i in 0 until executableCalls.size) {
        val asyncCall = executableCalls[i]
        asyncCall.executeOn(executorService)
      }
    }

    return isRunning
  }

可以看到最终是执行了AsyncCall的excuteOn方法,excutorService中会执行excute方法,该方法的入参是AsyncCall实例,实际会执行AsyncCall的run方法,在run方法中你会发现有一个老熟人okhttp3.internal.connection.RealCall#getResponseWithInterceptorChain方法,至此就进入了OkHttp的拦截器模式主流程了,其中run方法中会调用okhttp3.Dispatcher#finished方法,该方法会调用promoteAndExecute(又是老熟人了)下一轮的任务调度。

2.3 OkHttp的Socket连接细节

其实整个主流程和拦截器的代码分析中,其实没有涉及到底层的Socket是如何建立的,只能说OkHttp的使用太简便了,简单到我们只需要关心高层的接口使用,底层的Socket建立和池化过程我们在使用OkHttp的时候根本感知不到。接下来我们一起看看被隐藏的Socket细节。

在开始讲Socket之前,回到讲RetryAndFollowUpInterceptor拦截器的intercept方法时,注释了一个很重要的方法RealCall#enterNetworkInterceptorExchange,我们先看下这个方法的实现:

kotlin 复制代码
  fun enterNetworkInterceptorExchange(
    request: Request,
    newRoutePlanner: Boolean,
    chain: RealInterceptorChain,
  ) {
    // a、去掉了部分无关紧要的检查代码
    ...
    
    if (newRoutePlanner) {
      val routePlanner =
        RealRoutePlanner(
          taskRunner = client.taskRunner,
          connectionPool = connectionPool,
          readTimeoutMillis = client.readTimeoutMillis,
          writeTimeoutMillis = client.writeTimeoutMillis,
          socketConnectTimeoutMillis = chain.connectTimeoutMillis,
          socketReadTimeoutMillis = chain.readTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          retryOnConnectionFailure = client.retryOnConnectionFailure,
          fastFallback = client.fastFallback,
          address = client.address(request.url),
          connectionUser = CallConnectionUser(this, connectionPool.connectionListener, chain),
          routeDatabase = client.routeDatabase,
        )
      // b、方法的关键,初始化了RealCall的exchangeFinder变量值
      this.exchangeFinder =
        when {
          client.fastFallback -> FastFallbackExchangeFinder(routePlanner, client.taskRunner)
          else -> SequentialExchangeFinder(routePlanner)
        }
    }
  }

从上面的代码看真正关键的地方在注释b处,初始化了RealCall的exchangeFinder变量,这个变量有两种取值,默认是FastFallbackExchangeFinder,但是如果设置了OkHttpClient的fastFallback变量为false,则RealCall的exchangeFinder变量会被设置为SequentialExchangeFinder。FastFallbackExchangeFinder和SequentialExchangeFinder都实现了find接口,RealCall的exchangeFinder变量真正被使用到的地方在ConnectInterceptor拦截器中,该方法调用了RealCall的initExchange方法,话不多说直接看代码:

kotlin 复制代码
internal fun initExchange(chain: RealInterceptorChain): Exchange {
    withLock {
      check(expectMoreExchanges) { "released" }
      check(!responseBodyOpen)
      check(!requestBodyOpen)
    }

    val exchangeFinder = this.exchangeFinder!!
    // a、关键的地方
    val connection = exchangeFinder.find()
    val codec = connection.newCodec(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    withLock {
      this.requestBodyOpen = true
      this.responseBodyOpen = true
    }

    if (canceled) throw IOException("Canceled")
    return result
  }

可以看到前面RealCall#enterNetworkInterceptorExchange中设置的exchangeFinder变量调用了find方法,这里以FastFallbackExchangeFinder的fin方法为例:

kotlin 复制代码
override fun find(): RealConnection {
    var firstException: IOException? = null
    try {
      // a、需要连接的Plan不为空或者存在下一个后续Plan,循环处理
      while (tcpConnectsInFlight.isNotEmpty() || routePlanner.hasNext()) {
        if (routePlanner.isCanceled()) throw IOException("Canceled")

        // Launch a new connection if we're ready to.
        val now = taskRunner.backend.nanoTime()
        var awaitTimeoutNanos = nextTcpConnectAtNanos - now
        var connectResult: ConnectResult? = null
        if (tcpConnectsInFlight.isEmpty() || awaitTimeoutNanos <= 0) {
          // b、发起tcp连接
          connectResult = launchTcpConnect()
          nextTcpConnectAtNanos = now + connectDelayNanos
          awaitTimeoutNanos = connectDelayNanos
        }

        // Wait for an in-flight connect to complete or fail.
        // c、阻塞等待tcp链接
        if (connectResult == null) {
          connectResult = awaitTcpConnect(awaitTimeoutNanos, TimeUnit.NANOSECONDS) ?: continue
        }

        // d、tcp连接返回结果
        if (connectResult.isSuccess) {
          // We have a connected TCP connection. Cancel and defer the racing connects that all lost.
          cancelInFlightConnects()

          // Finish connecting. We won't have to if the winner is from the connection pool.
          if (!connectResult.plan.isReady) {
            // e、建立tls连接(最终的http的代理的证书校验、握手在这里)
            connectResult = connectResult.plan.connectTlsEtc()
          }

          if (connectResult.isSuccess) {
            // f、链接池处理(简单来说就是把结果存起来,下次好找到)
            return connectResult.plan.handleSuccess()
          }
        }

        val throwable = connectResult.throwable
        if (throwable != null) {
          if (throwable !is IOException) throw throwable
          if (firstException == null) {
            firstException = throwable
          } else {
            firstException.addSuppressed(throwable)
          }
        }

        val nextPlan = connectResult.nextPlan
        if (nextPlan != null) {
          // Try this plan's successor before deferred plans because it won the race!
          routePlanner.deferredPlans.addFirst(nextPlan)
        }
      }
    } finally {
      cancelInFlightConnects()
    }

    throw firstException!!
  }

代码看起来很多,我在关键的位置都做了注释,其中launchTcpConnect方法调用链路为okhttp3.internal.connection.ConnectPlan#connectTcp>>okhttp3.internal.connection.ConnectPlan#connectSocket>>javax.net.SocketFactory#createSocket,至此socket链接生成。然后看注释e处,调用的链路为okhttp3.internal.connection.ConnectPlan#connectTlsEtc>>javax.net.ssl.SSLSocketFactory#createSocket>>okhttp3.internal.connection.ConnectPlan#connectTls,在connetctTls中完成SslSocket的握手和证书校验,至此完成了Socket建立,通过注释f处的handleSuccess>>okhttp3.internal.connection.RealConnectionPool#put链路,完成连接的池化操作,具体的池化操作可以看RealConnectionPool中的操作。FastFallbackExchangeFinder的find分析结束了,SequentialExchangeFinder实例的socket建立过程与FastFallbackExchangeFinder类似,这里就不再重复分析了。

3.小结

OkHttp这个网络请求框架的代码实现还是非常优雅的,从Call任务的调度实现到数据的缓存、Socket连接池化过程以及串通整个请求流程的拦截器模式等,有非常多值得我们学习的地方。源码的学习本身还是很枯燥的,相信你读完这篇文章也能有一些自己的思考和感悟。加油Android开发人,每天进步一点!!!

相关推荐
Krorainas30 分钟前
HTML 页面禁止缩放功能
前端·javascript·html
whhhhhhhhhw1 小时前
Vue3.6 无虚拟DOM模式
前端·javascript·vue.js
鱼樱前端1 小时前
rust基础(一)
前端·rust
xw51 小时前
Trae开发uni-app+Vue3+TS项目飘红踩坑
前端·trae
Dolphin_海豚1 小时前
vue-vapor 的 IR 是个啥
前端·vue.js·vapor
撰卢1 小时前
Filter快速入门 Java web
java·前端·hive·spring boot
ai小鬼头1 小时前
创业心态崩了?熊哥教你用缺德哲学活得更爽
前端·后端·算法
拾光拾趣录2 小时前
算法 | 下一个更大的排列
前端·算法
小屁孩大帅-杨一凡2 小时前
如何使用Python将HTML格式的文本转换为Markdown格式?
开发语言·前端·python·html
于慨2 小时前
uniapp云打包安卓
前端·uni-app