知识梳理系列之五——OkHttp的原理

OkHttp原理解析

OkHttp的使用流程源码解析

通常的使用方法:

	val okHttpClient : OkHttpClient = OkHttpClient.Builder()
                .connectTimeout(...) // 可设置连接、读写、调用等超时
                .dispatcher(...) // 可设置自定义的调度器
                .connectionPool(...) // 可设置自定义连接池
                .cache(...) // 可设置缓存
                .addInterceptors(...) // 可向责任链添加自定义拦截器等等
                .build() 
    val request : Request = Request.Builder()
    			.url(...)// 设置url
    			.headers(...)// 设置请求头
    			.get() // 设置请求方法
    			.build()    
    // 发起一个异步请求,也可使用execute在工作线程中发起同步请求
    okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
            }

            override fun onFailure(call: Call, e: IOException) {
            }
        })
    
  1. OkHttpClient的实例化过程和构造器模式

    在OkHttpClient的使用过程中,首先采用构造器对其进行实例化,使用构造器的无参构造函数,将默认使用分发器、连接池、协议版本支持HTTP1.1/HTTP2、连接读写超时时间均设为10000毫秒、不使用代理、不使用Cookies、无缓存(方便根据服务器的策略使用缓存)等配置。

  2. Request和Call的实例化过程

    类似地,Request也是使用构造器模式进行实例化,可以对url、请求头、请求方法进行配置;

    然后使用OkHttpClient的实例方法newCall,传入Request实例,创建RealCall对象;

  3. 发起同步execute和异步enqueue请求

    同步和异步方法都是RealCall的实例方法,在创建RealCall后调用,可发起请求:
    他们的差别是:同步没有工作线程,需手动在线程中执行,异步在工作线程中执行,并且默认被两个队列维护。

    3.1 同步请求

// RealCall.kt
  override fun execute(): Response {
    // 同步代码块,确保一个线程在执行请求时被阻塞;
    // 开始计算超时
    // 事件监听器启动监听(监听dns、代理、https的安全连接、网络连接等的启动终止等)
    ...
    try {
      // 1. 分发器将请求添加到同步调用队列
      client.dispatcher.executed(this)
      // 2. 使用责任链完成任务并返回响应
      return getResponseWithInterceptorChain()
    } finally {
      // 3. 结束分发,移除请求
      client.dispatcher.finished(this)
    }
  }
  • 其中executed方法就是将RealCall实例,添加到一个双头队列中:
    private val runningSyncCalls = ArrayDeque<RealCall>()

  • finished 则会执行响应的移除操作;

    3.2 异步请求

// RealCall.enqueue --> Dispatcher.enqueue
// Dispatcher.kt
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      // 1. 向ready的异步调用队列中添加call;
      readyAsyncCalls.add(call)

      // 2. 复用相同HOST的调用计数,为了方便统计同HOST的连接数(@Volatile var callsPerHost)
      // 这是一个多线程共享数据,与连接池有关,连接池要求同一个HOST的连接数不得超过5,
      // 最多不得超过64,否则会放入队列阻塞
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    // 3. 异步请求的重点方法
    promoteAndExecute()
  }

异步请求,触发RealCall的enqueue方法,执行了检查当前线程是否正在执行和启动事件监听后,触发了Dispatcher的enqueue方法。

最后进入了核心方法Dispatcher#promoteAndExecute()

// Dispatcher.kt
  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      // 1. 同步遍历ready的异步请求队列
      // 如果running的异步请求队列超过了最大长度64,则当前的请求不会被执行;
      // 如果running的异步请求队列相同HOST的请求数超过了5,则继续循环只执行同HOST未超过5的请求;
      // 满足要求的请求可以被执行,将会被添加到executableCalls列表中
      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
    }

// RealCall$AsyncCall
	// 2. 遍历可执行的请求列表,并使用线程池执行请求。
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

  fun executeOn(executorService: ExecutorService) {
      ...
      // 3. 触发了线程池执行Runnable.run()
      executorService.execute(this)
      ...
  }

  override fun run() {
  	...
  	// 4. RealCall$AsyncCall实现了Runnable,最终执行了责任链方法。
    val response = getResponseWithInterceptorChain()
    ...
    responseCallback.onResponse(this@RealCall, response)
    ...
    responseCallback.onFailure(this@RealCall, canceledException)
    ...
    client.dispatcher.finished(this)
  }
  

总结:
分析得到,同步方法执行请求,由一个runningSyncCall的队列维护,直接在调用线程中执行责任链发起请求;
异步方法执行请求,则是由readyAsyncCall、runningAsyncCall两个队列维护,一个存放准备好需要请求的任务,一个存放正在执行的请求任务;并且不允许连接数大于64、同HOST连接数大于5,否则请求会阻塞在ready队列中;可执行的请求任务将最终通过执行责任链完成,并按需触发onResponse、onFailure方法。


责任链模式

1. 什么是责任链模式呢?

一个列表在执行任务的时候,上一个任务都持有下一个任务的引用。在上一个任务执行方法中完成自己的任务后,用下一个任务的引用触发执行下一个任务的方法。以此不断执行下去,最终完成所有任务的执行。

在一个业务(例如网络请求)由多个任务(一个任务集合:超时重连、请求桥接、缓存使用和数据更新、连接建立、数据传输等)组成,并不知晓具体会执行其中的哪个任务(集合的元素),或者哪些任务(集合的子集)时,使用责任链模式,让使用业务的发送者无需关心具体执行的过程,由责任链(接收者)负责具体确定使用那个或哪些任务,最终得到发送者关心的结果。实现了发送者与接收者之间的解耦

2. OkHttp怎样使用的责任链
如下简化代码:
首先是有一个Interceptor接口,其intercept(Chain)方法由proceed(Request)方法触发,是实现业务逻辑和使用proceed调用责任链的下一个任务,并最终返回Responce给第一个proceed(Request)方法;
另外再有一个Chain接口,RealInterceptorChain继承了此接口,实现了接口的proceed(Request)方法,取出列表中的Interceptor、构造index+1的Chain,然后执行intercept(Chain)方法。
类似一种递归调用的方式,遍历一个拦截器列表。

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

  interface Chain {
    @Throws(IOException::class)
    fun proceed(request: Request): Response	
    ...
  }
}
// RealInterceptorChain.kt
class RealInterceptorChain(
  ...
) : Interceptor.Chain {

  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
    ...

    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
	...
    return response
  }
}

3. 调用流程:
责任链调用流程

重试和重定向

重试和重定向拦截器:

  1. 主要是在请求返回RouteException/IOException异常时,根据recover判断是否需要尝试重新与服务器建立连接;
  2. 而请求成功返回了Response,但是状态码为407/408/300/301/302/503等时,flowUpRequest方法将决定是否需要重定向,如果需要重定向返回非空的Request并重置了request继续循环请求;重定向的次数不能超过20;
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
  	// 1. 转成RealInterceptorChain对象;
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    ...
    while (true) {
      // 2. 创建ExchangeFinder对象,该对象用来从连接池中获取可复用的连接; 
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)
      ...
      try {
      	...
        try {
          // 3. 进入责任链的下一个拦截器执行
          response = realChain.proceed(request)
          newExchangeFinder = true
        } catch (e: RouteException) {
          // 4. 当与服务器建立连接失败时,如果是可恢复的(根据recover方法判断),
          // 则回复并尝试重新建立连接————重试
          ...
          continue
        }
        ...

        val exchange = call.interceptorScopedExchange
        // 5. 根据返回的response的状态码(如300/301/302/303/307/308/401/407/408/421/503等)
        // 等条件确定是否需要重定向,不需要重定向会返回空
        val followUp = followUpRequest(response, exchange)

        if (followUp == null) {
          if (exchange != null && exchange.isDuplex) {
            call.timeoutEarlyExit()
          }
          closeActiveExchange = false
          return response
        }

        val followUpBody = followUp.body
        if (followUpBody != null && followUpBody.isOneShot()) {
          closeActiveExchange = false
          return response
        }

        response.body?.closeQuietly()

		// 重定向的次数不能超过最大限制否则抛出异常
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }

        request = followUp
        priorResponse = response
      } finally {
      	// 6. ExchangeFinder置空和退出连接
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }
  ...
}

桥接

桥接拦截器的作用是:为请求封装请求头和对返回的响应体进行GZIP解压缩。
可以看到interceptor方法中,根据需求配置Request Header的Content-Type、Content-Length、Host、Transfer-Encoding、Connection、Cookies、User-Agent等字段;
并且在返回的response,进行判断是否使用GZIP压缩数据,然后决定是否解压缩;

class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request()
    val requestBuilder = userRequest.newBuilder()

    val body = userRequest.body
    if (body != null) {
      val contentType = body.contentType()
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString())
      }

      val contentLength = body.contentLength()
      if (contentLength != -1L) {
        requestBuilder.header("Content-Length", contentLength.toString())
        requestBuilder.removeHeader("Transfer-Encoding")
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked")
        requestBuilder.removeHeader("Content-Length")
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", userRequest.url.toHostHeader())
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive")
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.header("Accept-Encoding", "gzip")
    }

    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", userAgent)
    }

    val networkResponse = chain.proceed(requestBuilder.build())

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

    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)

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

Cache处理

Cache拦截器是根据本地缓存的策略和数据决定是否使用缓存或者、是否请求验证资源是否过期、是否请求获取数据以及跟新缓存策略和数据的处理器。

class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    // 1. 根据Request获取缓存中的Response,cache实质是使用一个DisLruCache来实现的缓存,
    // 这个缓存使用Request的url作为Key,根据Key获取Entry进而得到Response
    val cacheCandidate = cache?.get(chain.request())

    // 2. 根据缓存、请求获取缓存策略。
    val now = System.currentTimeMillis()
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    ...

    // 3.当网路不可用、缓存无效时,返回503响应
    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)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // 4. 当缓存可用,直接使用缓存返回响应
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    ...
      // 6. 继续执行网络请求(缓存过期、或缓存策略要求先验证响应新鲜程度、或要求重新请求等情况下)
      networkResponse = chain.proceed(networkRequest)

    // 7. 执行验证响应新鲜程度,得到的响应码是304(HTTP_NOT_MODIFIED)时,直接使用缓存,并更新本地缓存;
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()

        ...
        cache.update(cacheResponse, response)
        return response.also {
          listener.cacheHit(call, it)
        }
      } else {
        cacheResponse.body?.closeQuietly()
      }
    }

	// 8. 执行网络请求(响应新鲜程度不满足要求,即服务端响应已经修改)后,
	// 根据缓存策略将新的响应数据和策略存储到缓存中
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

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

      ...
    }

    return response
  }
   ...
 }

继续追踪CacheStrategy分析缓存策略的处理:
CacheStragtegy 缓存策略对象,有两个重要成员变量。

  • networkRequest表示要发起的网络请求,当此对象被赋值为null,缓存拦截器将不会使用Request发起请求;
  • cacheResponse表示本地缓存的上次请求的响应数据,当此对象被赋值为null,表示缓存是不可用的。

详见注释:

// CacheStrategy.kt
    fun compute(): CacheStrategy {
      // 1. 进入核心方法
      val candidate = computeCandidate()

      // 2. 当Request不为空,并且Cache-Control:Only-If-Cache的策略时,返回两个null的CacheStrategy
      if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
        return CacheStrategy(null, null)
      }

      return candidate
    }

    private fun computeCandidate(): CacheStrategy {
      // 1.1 没有缓存时,通常是首次请求或Cache-Control:no-store;
      if (cacheResponse == null) {
      	// 返回networkRequest不为空,cacheResponse为空,将发起请求获取数据;
        return CacheStrategy(request, null)
      }

      // 1.2 请求使用的HTTPS但是没有进行SSL/TLS握手;
      if (request.isHttps && cacheResponse.handshake == null) {
      	// 返回networkRequest不为空,cacheResponse为空,将发起请求获取数据;
        return CacheStrategy(request, null)
      }

      // 缓存策略发生变化,才会执行是否可缓存的检查
      if (!isCacheable(cacheResponse, request)) {
        return CacheStrategy(request, null)
      }

	  // 1.3 Cache-Control:no-cache的情况,需要先请求服务器校验资源新鲜度,然后再决定是否使用缓存
      val requestCaching = request.cacheControl
      if (requestCaching.noCache || hasConditions(request)) {
        return CacheStrategy(request, null)
      }

	  // 1.4 下面就是根据Cache-Control(没有设置no-cahche)
	  // 的max-age、min-fresh、max-stale和cacheResponse的Age来
	  // 判断缓存是否过期,如果没有过期或者过期但处于仍然可用的状态下会继续使用缓存。
	  // (1) age + min_fresh <= max_age : 未过期;
	  // (2) (age + min_fresh > max_age) && (age + min_fresh <= max) : 
	  //      过期仍可使用 会挂110警告码;
	  //      超过24小时,max-age=-1,未设置expires的情况会挂113警告码。
	  // (3) 过期,在networkRequest的头部添加
	  //  ETag或If-None-Match或If-Modified-Since等字段,用于请求验证cacheResponse的新鲜度
      val responseCaching = cacheResponse.cacheControl
      val ageMillis = cacheResponseAge()
      var freshMillis = computeFreshnessLifetime()

      if (requestCaching.maxAgeSeconds != -1) {
        freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
      }

      var minFreshMillis: Long = 0
      if (requestCaching.minFreshSeconds != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
      }

      var maxStaleMillis: Long = 0
      if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
      }

      if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        val builder = cacheResponse.newBuilder()
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
        }
        val oneDayMillis = 24 * 60 * 60 * 1000L
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
        }
        return CacheStrategy(null, builder.build())
      }

      val conditionName: String
      val conditionValue: String?
      when {
        etag != null -> {
          conditionName = "If-None-Match"
          conditionValue = etag
        }

        lastModified != null -> {
          conditionName = "If-Modified-Since"
          conditionValue = lastModifiedString
        }

        servedDate != null -> {
          conditionName = "If-Modified-Since"
          conditionValue = servedDateString
        }

        else -> return CacheStrategy(request, null) 
      }

      val conditionalRequestHeaders = request.headers.newBuilder()
      conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)

      val conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build()
      return CacheStrategy(conditionalRequest, cacheResponse)
    }

Connection

初始化了Exchange对象,这个对象是实际处理网络IO的对象,使用了连接池中可复用的连接和Codec,提供了一系列方法:如writeRequest、flush、readResponse等网络IO读写。

CallServer

责任链中最后一个拦截器,用来使用Exchange,执行网络IO


分发器Dispatcher、线程池、连接池

  1. 分发器Dispatcher
    1.1 分发器是在OkHttpClient构造时,初始化的;
    1.2 分发器中实例化了一个60s 保活、名为okhttp3.Client Dispatcher的CacheThreadPool(0个核心线程,Integer.MAX_VALUE个非核心线程);
    1.3 分发器是负责将同步/异步请求分发到责任链中执行的重要模块;
    1.4 分发器维护了三个双头队列,用于添加删除同步异步Call;
    1.5 提供了取消所有请求的方法;

  2. 线程池
    在分发其中实例化的线程池用于异步请求;

  3. 连接池ConnectionPool
    3.1 维护了5个空闲连接、保活5分钟;
    3.2 由RealConnectionPool实现其功能:维护使用的连接、空闲连接、连接个数、清除连接、连接保活时间等;
    3.3 在ConnectionInterceptor拦截器执行是使用:

ConnectionInterceptor.intercept   -->  RealCall.initExchange  --> ExchangeFinder.find  
--> ExchangeFinder.findHealthyConnection

最后通过将构造的Exchange传入下一个责任链CallServerInterceptor,在其中使用Exchange.HttpCodec,进行网络IO

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值