OkHttp 源碼解析(Kotlin版)

前言

OkHttp 是一款非常優(yōu)秀的網絡請求框架叫潦,隨著Kotlin語言的不斷完善星爪,OkHttp 自 4.0 版本開始使用Kotlin編寫牌柄,對于鞏固Kotlin語法知識及實踐蒋歌,研讀OkHttp 4.0及以上版本的源碼是一個不錯的選擇摧阅。

首先列出一些前置知識點:

  1. Kotlin基礎知識
  2. Kotlin學習平臺
  3. 網絡請求響應碼含義
1xx:信息汰蓉,請求收到,繼續(xù)處理
2xx:成功棒卷,行為被成功地接受顾孽、理解和采納
3xx:重定向祝钢,為了完成請求,必須進一步執(zhí)行的動作
4xx:客戶端錯誤若厚,請求包含語法錯誤或者請求無法實現(xiàn)
5xx:服務器錯誤拦英,服務器不能實現(xiàn)一種明顯無效的請求
  1. Http緩存機制

OkHttp的基本使用

  • 首先添加依賴庫(去官網找最新的或想要的版本)
  • Http 請求有多種類型,常用的分為 Get 和 Post测秸,而 POST 又分為 Form 和 Multiple 等疤估,下面我們以Get請求為例:
       // 1.創(chuàng)建OkHttpClient 對象,
       // var client = OkHttpClient();//方式一
        //方式二:
        val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
        //2.創(chuàng)建請求對象并添加請求參數信息
        val request = Request.Builder().url("").build()
        //3.構建進行請求操作的call對象
        val call = client.newCall(request)

        //同步請求 Call (RealCall)—>execute() 返回response
        // val response = client.newCall(request).execute()
        //異步請求 Call (RealCall)—>enqueue()
        call.enqueue(
                object : Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        println(e.stackTrace.toString())
                    }

                    @Throws(IOException::class)
                    override fun onResponse(call: Call, response: Response) {
                        println(response.body.toString())
                    }
                })

  • 首先使用OkHttpClint的構造OkHttpClient()或者Build模式構建一個OkHttpClint的對象實例霎冯;
  • 使用構建者模式構建一個Request對象铃拇,通過OkHttpClient和Request對象,構建出Call對象沈撞;
  • 執(zhí)行call的enqueue()或者execute()慷荔。

注意:在實際開發(fā)中建議將OkHttpClint對象的創(chuàng)建封裝成單列, 因為每個 OkHttpClient 對象都管理自己獨有的線程池和連接池关串,復用連接池和線程池能夠減少延遲拧廊、節(jié)省內存。

OkHttp 源碼分析

一. OkHttpClient

 constructor() : this(Builder())
 
 //這里是默認的參數設置
 class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()//調度器晋修,通過雙端隊列保存Calls(同步&異步Call)
    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()//一個Call的狀態(tài)監(jiān)聽器
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES//默認沒有Cookie
    internal var cache: Cache? = null
    internal var dns: Dns = Dns.SYSTEM//域名解析系統(tǒng) domain name -> ip address
    internal var proxy: Proxy? = null
    internal var proxySelector: ProxySelector? = null//使用默認的代理選擇器
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()//默認的Socket 工廠生產Socket 
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS//OKHttp連接(Connection)配置
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000
    internal var pingInterval = 0//和WebSocket有關,為了保持長連接吧碾,我們必須間隔一段時間發(fā)送一個ping指令進行保活
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    internal var routeDatabase: RouteDatabase? = null

connectionSpecs: OKHttp連接(Connection)配置

companion object {
    internal val DEFAULT_PROTOCOLS = immutableListOf(HTTP_2, HTTP_1_1)

    internal val DEFAULT_CONNECTION_SPECS = immutableListOf(
        ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT)
  }
   
   /**
     * A modern TLS configuration that works on most client platforms and can connect to most servers.
     * This is OkHttp's default configuration.
     */
     //針對TLS的墓卦, 是OkHttp 的默認配置
   @JvmField
    val MODERN_TLS = Builder(true)
        .cipherSuites(*APPROVED_CIPHER_SUITES)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .supportsTlsExtensions(true)
        .build()


 /** URL的未經加密倦春,未經身份驗證的連接 */
    @JvmField
    val CLEARTEXT = Builder(false).build()

二. 同步請求流程分析

以下代碼為同步請求流程中的核心代碼,按照調用次序呈現(xiàn)落剪。

  //  1.
  val response = client.newCall(request).execute()
  //  2.
   /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
  
  //RealCall的execute()
  //  3. 真正執(zhí)行請求的是在call的實現(xiàn)類 RealCall的execute()中
   override fun execute(): Response {
   //標記請求執(zhí)行狀態(tài):一個請求只能執(zhí)行一次
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    timeout.enter()
    callStart()
    try {
     // 4. 通知dispatcher已經進入執(zhí)行狀態(tài)
      client.dispatcher.executed(this)
      // 5. 通過連接器的鏈式調用進行請求處理并返回最終響應結果
      return getResponseWithInterceptorChain()
    } finally {
    // 6. 通知dispatcher自己已執(zhí)行完畢
      client.dispatcher.finished(this)
    }
  }
  
   //4 . dispatcher.executed()
  /** Used by `Call#execute` to signal it is in-flight. */
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }
  
  //Dispatcher中維護的ArrayDeque
   /** 準備執(zhí)行的異步請求隊列. */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  /** 正在執(zhí)行的異步請求隊列 */
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  /** 正在執(zhí)行的同步請求隊列 */
  private val runningSyncCalls = ArrayDeque<RealCall>()
  
  
  //5 .RealCall的getResponseWithInterceptorChain()
  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors//用戶在構建OkHttpClient是配置的連接器
    interceptors += RetryAndFollowUpInterceptor(client)//負責請求失敗后的重試和重定向
    interceptors += BridgeInterceptor(client.cookieJar)//對請求和響應的參數進行必要的處理
    interceptors += CacheInterceptor(client.cache)//讀取緩存數據返回睁本、更新緩存
    interceptors += ConnectInterceptor//負責跟服務器的鏈接操作
    if (!forWebSocket) {
    //創(chuàng)建OkHttpClient時設置的networkInterceptor
      interceptors += client.networkInterceptors
    }
    //向服務器發(fā)送請求數據,讀取響應數據
    interceptors += CallServerInterceptor(forWebSocket)

     //將請求對象及OkHttpClient的一些配置封裝在RealInterceptorChain中
    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)
      }
    }
  }


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

    calls++

    if (exchange != null) {
      check(exchange.finder.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. 實例化下一個攔截器
    val next = copy(index = index + 1, request = request)
    //獲取當前攔截器
    val interceptor = interceptors[index]

    //調用當前攔截器的intercept(),并將下一個攔截器的RealIterceptorChain對象傳遞下去,最后返回響應結果
    @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
  }
  

三. 異步請求流程分析

     //1. 異步請求 Call (RealCall)—>enqueue()
     client.newCall(request).enqueue(
        object : Callback {
        override fun onFailure(call: Call, e: IOException) {
             println(e.stackTrace.toString())
        }

           @Throws(IOException::class)
              override fun onResponse(call: Call, response: Response) {
                println(response.body.toString())
              }
      })
                
     //2. RealCall 的enqueue()     
    override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }   
  
  //3. dispatcher 的enqueue()
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
    //將請求添加到等待執(zhí)行的異步請求隊列中
      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()
  }         


 /**
   * Promotes eligible calls from [readyAsyncCalls] to [runningAsyncCalls] and runs them on the
   * executor service. Must not be called with synchronization because executing calls can call
   * into user code.
   *
   * @return true if the dispatcher is currently running calls.
   */
   //4. 不斷從readyAsyncCalls中取出要執(zhí)行的請求放到runningAsyncCalls中忠怖,并將readyAsyncCalls中的移除
  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()
        
     // 如果其中的runningAsynCalls不滿呢堰,且call占用的host小于最大數量,則將call加入到runningAsyncCalls中執(zhí)行凡泣,
   
        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]
       // 利用線程池執(zhí)行call
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

異步請求的dispatcher.enqueue(AsyncCall)中傳入是call 是一個AsyncCall枉疼,接下來看AsyncCall的實現(xiàn).它是RealCall的內部類,實際是一個Runnable鞋拟。

  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
    @Volatile var callsPerHost = AtomicInteger(0)
      private set

    fun reuseCallsPerHostFrom(other: AsyncCall) {
      this.callsPerHost = other.callsPerHost
    }

    val host: String
      get() = originalRequest.url.host

    val request: Request
        get() = originalRequest

    val call: RealCall
        get() = this@RealCall

    /**
     * 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 {
      //在線程遲中執(zhí)行
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          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 {
            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)
        }
      }
    }
  }

  • 通過源碼看到Dispatcher維護了三個ArrayDeque骂维,一個保存了正在執(zhí)行的同步任務;一個保存異步正在執(zhí)行的請求贺纲,另一個是異步等待執(zhí)行的請求航闺,異步右兩個ArrayDeque是因為Dispatcher默認支持最大的并發(fā)請求是64個,單個Host最多執(zhí)行5個并發(fā)請求,如果超過潦刃,則Call會先被放入到readyAsyncCall中侮措,當出現(xiàn)空閑的線程時,再將readyAsyncCall中的線程移入到runningAsynCalls中福铅,執(zhí)行請求萝毛。

  • 通過攔截器鏈處理项阴,得到響應結果后執(zhí)行finally中的代碼dispatcher.finished(this)滑黔,
    現(xiàn)在來看下這個方法,走到這环揽,一個請求流程就結束了略荡。

 /** Used by [AsyncCall.run] to signal completion. */
 //異步請求時調用
  internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

  /** Used by [Call.execute] to signal completion. */
  //同步請求時調用
  internal fun finished(call: RealCall) {
    finished(runningSyncCalls, call)
  }
  
   //最終都調用這個方法
  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
    //將當前call從其隊列中移除
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

總結:網絡請求是從OkHttpClient().newCall(request)開始的,通過創(chuàng)建的OkHttpClient對象和Request對象歉胶,構建出一個RealCall對象來執(zhí)行網絡請求汛兜,同步請求是在RealCallexecute()方法中,異步請求是在enqueue()中通今,在這兩個方法中都用OkHttpClient對象的dispatcher執(zhí)行對應的請求方法粥谬。對于同步請求,dispatcherexecute()就是將請求加入到runningSyncCalls這個雙端隊列中辫塌;對于異步請求漏策,dispatcher進行請求的分發(fā)執(zhí)行。在dispatcher將請求分發(fā)后調用getResponseWithInterceptorChain()方法臼氨,在這里掺喻,==依次==將client.interceptorsRetryAndFollowUpInterceptor、BridgeInterceptor储矩、CacheInterceptor感耙、ConnectInterceptor、client.networkInterceptors和CallServerInterceptor添加到一個集合中持隧,并創(chuàng)建出一個攔截器鏈RealInterceptorChain即硼,通過RealInterceptorChain.proceed()使每一個攔截器執(zhí)行完畢之后會調用下一個攔截器或者不調用并返回結果。顯然屡拨,我們最終拿到的響應就是這個鏈條執(zhí)行之后返回的結果只酥。

  • 整體的請求流程圖如下:


    image

四.OkHttp內置攔截器源碼分析

1. RetryAndFollowUpInterceptor

這個攔截器負責重試和重定向,當一個請求由于各種原因失敗了洁仗,如果是路由或者連接異常层皱,則嘗試恢復,否則赠潦,根據響應碼(ResponseCode),followup方法會對Request進行再處理以得到新的Request叫胖,然后沿著攔截器鏈繼續(xù)新的Request;當嘗試次數超過最大次數就拋出異常她奥。代碼邏輯相對比較簡單瓮增,這里就不貼出來了怎棱。

2. BridgeInterceptor

負責將用戶請求轉換為網絡請求,也就是根據 Request 信息組建請求 Header 以及設置響應數據绷跑,包括設置 Cookie 以及gzip拳恋。源碼就不貼出來了。

3. CacheInterceptor

負責根據請求的信息和緩存的響應的信息來判斷是否存在可用的緩存砸捏,讀取緩存直接返回谬运、否則就繼續(xù)使用責任鏈模式來從服務器中獲取響應。當獲取到響應的時候垦藏,更新緩存梆暖。

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
   //從緩存中獲取
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()
   //緩存策略,決定使用緩存還是從網絡獲取
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    //根據緩存策略掂骏,更新統(tǒng)計指標:請求次數轰驳、使用網絡請求次數、使用緩存次數
    cache?.trackResponse(strategy)
     //若緩存不可用弟灼,關閉
    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.
    //如果既無網絡請求可用级解,又沒有緩存,則返回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)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
    }

    // If we don't need the network, we're done.
    //緩存可用田绑,則返回緩存中數據
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build()
    }

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

        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
      } else {
        cacheResponse.body?.closeQuietly()
      }
    }

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

      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }

    return response
  }
  
  
  
     //  CacheStrategy中的核心方法 computeCandidate()
     
    /** Returns a strategy to use assuming the request can use the network. */
    private fun computeCandidate(): CacheStrategy {
      // No cached response. 沒有緩存俺陋,直接進行網絡請求
      if (cacheResponse == null) {
        return CacheStrategy(request, null)
      }

      // Drop the cached response if it's missing a required handshake. 是https請求,但是沒有握手昙篙,進行網絡請求
      if (request.isHttps && cacheResponse.handshake == null) {
        return CacheStrategy(request, null)
      }

      // If this response shouldn't have been stored, it should never be used as a response source.
      // This check should be redundant as long as the persistence store is well-behaved and the
      // rules are constant.
      //不能進行緩存
      if (!isCacheable(cacheResponse, request)) {
        return CacheStrategy(request, null)
      }

      val requestCaching = request.cacheControl
        //請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match(意味著本地緩存過期腊状,需要服務器驗證本地緩存是不是還能繼續(xù)使用)
      if (requestCaching.noCache || hasConditions(request)) {
        return CacheStrategy(request, null)
      }

      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())
      }
      //緩存過期了,但仍然可用苔可,給相應頭中添加了Warning缴挖,使用緩存
      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())//使用緩存
      }

      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      val conditionName: String
      val conditionValue: String?
      //流程走到這,說明緩存已經過期了
      //添加請求頭:If-Modified-Since或者If-None-Match
      //etag與If-None-Match配合使用
      //lastModified與If-Modified-Since配合使用
      //前者和后者的值是相同的
      //區(qū)別在于前者是響應頭焚辅,后者是請求頭映屋。
      //后者用于服務器進行資源比對,看看是資源是否改變了同蜻。
      // 如果沒有棚点,則本地的資源雖過期還是可以用的
      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) // No condition! Make a regular request.
      }

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

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

從上述CatchStragety.computeCandidate()方法可知,緩存策略如下:

1. 沒有緩存湾蔓,直接網絡請求瘫析;
2. 如果是Https,沒有進行握手,則進行網絡請求贬循;
3. 設置了不可緩存咸包,則進行網絡請求;
4. 請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match杖虾,則需要服務器驗證本地緩存是不是還能繼續(xù)使用烂瘫,進行網絡請求;
5. 可以緩存奇适,并且緩存過期過期了但是還可以使用坟比,這時給響應頭添加Warning后,使用緩存滤愕;
6. 緩存已經過期温算,添加請求頭:If-Modified-Since或者If-None-Match,進行網絡請求间影;

整個CatcheIncepter的執(zhí)行依靠CatchStragety的緩存策略,代碼中添加了注釋茄茁,這里整理下流程如下:

 1. 如果網絡不可用并且無可用的有效緩存魂贬,則返回504錯誤;
 2. 如果禁止了網絡請求裙顽,則直接使用緩存付燥;
 3. 如果沒有緩存且網絡請求可用,則進行網絡請求愈犹;
 4. 如果此時有緩存键科,并且網絡請求返回HTTP_NOT_MODIFIED(304),說明緩存還是有效的漩怎,則合并網絡響應和緩存結果勋颖。同時更新緩存;
 5. 如果沒有緩存勋锤,則將請求回來的結果寫入新的緩存中饭玲;
 6. 返回響應數據。

可以看到叁执,緩存的獲取茄厘、添加、更新等操作都是在Catche中初始化了一個DiskLruCache來完成的谈宛,具體方法如下:

//獲取緩存
 internal fun get(request: Request): Response? {
    val key = key(request.url)
    val snapshot: DiskLruCache.Snapshot = try {
      cache[key] ?: return null
    } catch (_: IOException) {
      return null // Give up because the cache cannot be read.
    }

    val entry: Entry = try {
      Entry(snapshot.getSource(ENTRY_METADATA))
    } catch (_: IOException) {
      snapshot.closeQuietly()
      return null
    }

    val response = entry.response(snapshot)
    if (!entry.matches(request, response)) {
      response.body?.closeQuietly()
      return null
    }

    return response
  }

//添加緩存
 internal fun put(response: Response): CacheRequest? {
    val requestMethod = response.request.method

    if (HttpMethod.invalidatesCache(response.request.method)) {
      try {
        remove(response.request)
      } catch (_: IOException) {
        // The cache cannot be written.
      }
      return null
    }

    if (requestMethod != "GET") {
      // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
      // POST requests, but the complexity of doing so is high and the benefit is low.
      return null
    }

    if (response.hasVaryAll()) {
      return null
    }

    val entry = Entry(response)
    var editor: DiskLruCache.Editor? = null
    try {
      editor = cache.edit(key(response.request.url)) ?: return null
      entry.writeTo(editor)
      return RealCacheRequest(editor)
    } catch (_: IOException) {
      abortQuietly(editor)
      return null
    }
  }

//更新緩存
 internal fun update(cached: Response, network: Response) {
    val entry = Entry(network)
    val snapshot = (cached.body as CacheResponseBody).snapshot
    var editor: DiskLruCache.Editor? = null
    try {
      editor = snapshot.edit() ?: return // edit() returns null if snapshot is not current.
      entry.writeTo(editor)
      editor.commit()
    } catch (_: IOException) {
      abortQuietly(editor)
    }
  }

4. ConnectInterceptor

這個攔截器打開與目標服務器的鏈接并進入下一個攔截器次哈。
通過RealCallinitExchange(chain)創(chuàng)建一個Exchange對象,并調用 Chain.proceed()方法吆录。
initExchange()方法中會先通過 ExchangeFinder 嘗試去 RealConnectionPool 中尋找已存在的連接窑滞,未找到則會重新創(chuàng)建一個RealConnection 并開始連接,然后將其存入RealConnectionPool,現(xiàn)在已經準備好了RealConnection 對象葛假,然后通過請求協(xié)議創(chuàng)建不同的ExchangeCodec 并返回障陶,返回的ExchangeCodec正是創(chuàng)建Exchange對象的一個參數。

  • 下面說一下在建立連接過程中涉及到的幾個重要類:
Route:

是連接到服務器的具體路由聊训。其中包含了 IP 地址抱究、端口、代理等參數带斑。
由于存在代理或者 DNS 可能返回多個 IP 地址的情況鼓寺,所以同一個接口地址可能會對應多個 route
在創(chuàng)建 Connection 時將會使用 Route 而不是直接用 IP 地址勋磕。

RouteSelector:

路由選擇器妈候,其中存儲了所有可用的 route,在準備連接時時會通過 RouteSelector.next() 方法獲取下一個 Route挂滓。
值得注意的是苦银,RouteSelector中包含了一個 routeDatabase 對象,其中存放著連接失敗的Route赶站,RouteSelector 會將其中存儲的上次連接失敗的route 放在最后幔虏,以此提高連接速度。

RealConnection:

RealConnection 實現(xiàn)了 Connection接口贝椿,其中使用 Socket建立HTTP/HTTPS連接,并且獲取 I/O 流想括,同一個 Connection 可能會承載多個 HTTP 的請求與響應。

RealConnectionPool:

這是用來存儲 RealConnection 的池子烙博,內部使用一個雙端隊列來進行存儲瑟蜈。
在 OkHttp 中,一個連接(RealConnection)用完后不會立馬被關閉并釋放掉渣窜,而且是會存儲到連接池(RealConnectionPool)中铺根。
除了緩存連接外,緩存池還負責定期清理過期的連接图毕,在 RealConnection 中會維護一個用來描述該連接空閑時間的字段夷都,每添加一個新的連接到連接池中時都會進行一次檢測,遍歷所有的連接予颤,找出當前未被使用且空閑時間最長的那個連接囤官,如果該連接空閑時長超出閾值,或者連接池已滿蛤虐,將會關閉該連接党饮。

ExchangeCodec:

ExchangeCodec 負責對Request 編碼及解碼 Response,也就是寫入請求及讀取響應驳庭,我們的請求及響應數據都通過它來讀寫刑顺。其實現(xiàn)類有兩個:Http1ExchangeCodecHttp2ExchangeCodec氯窍,分別對應兩種協(xié)議版本。

Exchange:

功能類似 ExchangeCodec蹲堂,但它是對應的是單個請求狼讨,其在 ExchangeCodec 基礎上擔負了一些連接管理及事件分發(fā)的作用。
具體而言柒竞,ExchangeRequest 一一對應政供,新建一個請求時就會創(chuàng)建一個 Exchange,該 Exchange 負責將這個請求發(fā)送出去并讀取到響應數據朽基,而發(fā)送與接收數據使用的是 ExchangeCodec输吏。

 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)
  }
  
  //RealCall 中的initExchange()初始化Exchange對象
  /** Finds a new or pooled connection to carry a forthcoming request and response. */
  internal fun initExchange(chain: RealInterceptorChain): Exchange {
    synchronized(connectionPool) {
      check(!noMoreExchanges) { "released" }
      check(exchange == null)
    }

    val codec = exchangeFinder!!.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder!!, codec)
    this.interceptorScopedExchange = result

    synchronized(connectionPool) {
      this.exchange = result
      this.exchangeRequestDone = false
      this.exchangeResponseDone = false
      return result
    }
  }
  
  //找到可用的resultConnection后根據協(xié)議創(chuàng)建ExchangeCodec并返回
  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec {
    try {
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      return resultConnection.newCodec(client, chain)
    } catch (e: RouteException) {
      trackFailure(e.lastConnectException)
      throw e
    } catch (e: IOException) {
      trackFailure(e)
      throw RouteException(e)
    }
  }
  
  //ExchangeFinder的findConnection方法中找已經存在的可用的鏈接
 
  /**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   */
  @Throws(IOException::class)
  private fun findConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean
  ): RealConnection {
    var foundPooledConnection = false
    var result: RealConnection? = null
    var selectedRoute: Route? = null
    var releasedConnection: RealConnection?
    val toClose: Socket?
    synchronized(connectionPool) {
    
       …………
       
        // Attempt to get a connection from the pool.
        //從connectPool中找可用的鏈接并返回
        if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
          foundPooledConnection = true
          result = call.connection
        } else if (nextRouteToTry != null) {
          selectedRoute = nextRouteToTry
          nextRouteToTry = null
        }
      }
    }
    toClose?.closeQuietly()

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection!!)
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result!!)
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result!!
    }
    
    
      …………
      
    // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        //創(chuàng)建一個新的RealConnection
        result = RealConnection(connectionPool, selectedRoute!!)
        connectingConnection = result
        
   var socket: Socket? = null
    synchronized(connectionPool) {
      connectingConnection = null
      // Last attempt at connection coalescing, which only occurs if we attempted multiple
      // concurrent connections to the same host.
      if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
        // We lost the race! Close the connection we created and return the pooled connection.
        result!!.noNewExchanges = true
        socket = result!!.socket()
        result = call.connection

        // It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
        // that case we will retry the route we just successfully connected with.
        nextRouteToTry = selectedRoute
      } else {
        connectionPool.put(result!!)//將新創(chuàng)建的RealConnection添加到connectPool中
        call.acquireConnectionNoEvents(result!!)
      }
    }
    socket?.closeQuietly()

    eventListener.connectionAcquired(call, result!!)
    
    return result!!
  }

5. CallServerInterceptor

這是OkHttp 的連接器鏈中的最后一個攔截器巍佑,負責利用exchange把Request中的數據發(fā)送給服務端乱投,并獲取到數據寫入到Response中选调。

到這里,OkHttp框架的核心邏輯已經梳理完了霎俩,回顧一下整體的架構實現(xiàn)哀军,用到的設計模式有:Builder模式(OKHttpClient的構建)、工廠方法模式(Call接口提供了內部接口Factory茸苇、責任鏈模式(攔截器鏈)排苍、享元模式(在Dispatcher的線程池)、策略模式(CacheInterceptor中數據選擇等学密。

參考資源

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末腻暮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子毯侦,更是在濱河造成了極大的恐慌哭靖,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侈离,死亡現(xiàn)場離奇詭異试幽,居然都是意外死亡,警方通過查閱死者的電腦和手機卦碾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門铺坞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洲胖,你說我怎么就攤上這事济榨。” “怎么了绿映?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵擒滑,是天一觀的道長腐晾。 經常有香客問我,道長丐一,這世上最難降的妖魔是什么藻糖? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮库车,結果婚禮上巨柒,老公的妹妹穿的比我還像新娘。我一直安慰自己凝颇,他們只是感情好潘拱,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拧略,像睡著了一般芦岂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垫蛆,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天禽最,我揣著相機與錄音,去河邊找鬼袱饭。 笑死川无,一個胖子當著我的面吹牛,可吹牛的內容都是我干的虑乖。 我是一名探鬼主播懦趋,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疹味!你這毒婦竟也來了仅叫?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糙捺,失蹤者是張志新(化名)和其女友劉穎诫咱,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體洪灯,經...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡坎缭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了签钩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掏呼。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖边臼,靈堂內的尸體忽然破棺而出哄尔,到底是詐尸還是另有隱情,我是刑警寧澤柠并,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布岭接,位于F島的核電站富拗,受9級特大地震影響,放射性物質發(fā)生泄漏鸣戴。R本人自食惡果不足惜啃沪,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窄锅。 院中可真熱鬧创千,春花似錦、人聲如沸入偷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疏之。三九已至殿雪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锋爪,已是汗流浹背丙曙。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留其骄,地道東北人亏镰。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像拯爽,于是被迫代替她去往敵國和親索抓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356