Okhttp基本使用及源碼分析

該文章所用的okhttp源碼版本:4.2.0

基本使用

Okhttp的使用步驟分爲三步 :
1.創建OkHttpClient,初始化一些連接參數。

OkHttpClient client = new OkHttpClient();

//如果需要添加攔截器,則需要用構建者模式
OkHttpClient client = new OkHttpClient.Builder()
		.addInterceptor(AppInterceptor())
		.addNetworkInterceptor(NetworkInterceptor())
		.build();

2.創建Request,初始化請求體,method,header,url等信息。

Request request = new Request.Builder()
      .url(url)
      .build();

3.同步&異步執行請求,獲得響應Response。

Call call = client.newCall(request);

//同步
Response response = call.execute();

//異步
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, final Response response) throws IOException {
    }
});

源碼流程

代碼流程的前兩步主要是初始化兩個結構體,第三步開始創建了一個Call對象,該對象真正的實現類是RealCall。call.execute()和call.enqueue()就是同步,異步執行的兩個入口,現在分別從這兩個路徑來分析。

  /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call {
    return RealCall.newRealCall(this, request, forWebSocket = false)
  }
  companion object {
    fun newRealCall(
      client: OkHttpClient,
      originalRequest: Request,
      forWebSocket: Boolean
    ): RealCall {
      // Safely publish the Call instance to the EventListener.
      return RealCall(client, originalRequest, forWebSocket).apply {
        transmitter = Transmitter(client, this)
      }
    }
  }

同步執行call.execute()

同步執行代碼比較簡單,我們看RealCall.execute()

  override fun execute(): Response {
    synchronized(this) {
      //檢查該任務是否執行,如已執行則拋出異常
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.timeoutEnter()
    transmitter.callStart()
    try {
      //把該任務加入Dispatcher.runningSyncCalls隊列中,加入隊列的目的主要用於查詢,取消任務等操作
      client.dispatcher.executed(this)
      //真正的執行代碼
      return getResponseWithInterceptorChain()
    } finally {
      //任務執行完畢,從隊列中移除
      client.dispatcher.finished(this)
    }
  }

最終真正執行任務的是函數getResponseWithInterceptorChain(),該函數也是異步執行的入口,因此我們後面來具體分析。

異步執行call.enqueue()

異步執行我們來看RealCall.enqueue()

  override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

主要是構造一個AsyncCall回調,傳入Dispatcher.enqueue方法中,我們先來看AsyncCall這個類,

  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
	...

    fun executeOn(executorService: ExecutorService) {
      assert(!Thread.holdsLock(client.dispatcher))
      var success = false
      try {
        //使用傳入的線程池執行run方法
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
		...
        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
        transmitter.timeoutEnter()
        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(INFO, "Callback failure for ${toLoggableString()}", e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }
  }

AsyncCall實現了Runnable接口,通過executeOn傳入的線程池執行run函數。那麼這個executeOn方法在哪調用的呢?我們接着看Dispatcher類,它是一個任務執行類,裏面初始化了一個線程池,異步任務都是通過該線程池執行。

class Dispatcher constructor() {
...

  /** Ready async calls in the order they'll be run. */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  private var executorServiceOrNull: ExecutorService? = null

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

該線程池行爲類似於CachedThreadPool,核心線程爲0,最大線程很大,隊列使用的是同步阻塞隊列。正因爲隊列阻塞,任何新加入的任務都會被立即執行,任務完成後過了超時時間會自動回收。同時,該線程池可以通過構造函數實現自定義替換。下來我們來看enqueue方法。

  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.get().forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host())
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

  private fun promoteAndExecute(): Boolean {
    assert(!Thread.holdsLock(this))

    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的執行方法
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

enqueue方法最終回調了AsyncCall的run方法,這樣就開啓了一個新線程來執行任務。因此不論是同步還是異步,執行任務的函數都是getResponseWithInterceptorChain(),他們的差別只是在於是否使用了線程池來執行。

核心流程-攔截器

到這裏任務還沒有真正執行,上述代碼只是決定是否需要新開線程來執行任務。要了解核心代碼我們先來了解一下攔截器機制。Okhttp攔截器共分爲七層,採用的是責任連模式,及一級一級的向上執行,如果中途有攔截器處理了請求,那麼後續攔截器將不再執行。我們可以自定義第一次層應用攔截器和第六層網絡攔截器,可以在攔截器裏面添加請求頭,重定向URL等操作,而無需關注具體攔截的是哪個請求,這種方式可以理解爲面向切面(AOP)的編程方式。爲了更好的理解攔截器機制,我們來看下面的代碼。

  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(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest)
      if (transmitter.isCanceled) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw transmitter.noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null)
      }
    }
  }

這段代碼可以看出添加了七個攔截器,並構建了一個RealInterceptorChain,然後調用了它的proceed方法。我們來看RealInterceptorChain.proceed

  //RealInterceptorChain.proceed
  fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
    if (index >= interceptors.size) throw AssertionError()

    calls++
	...
    // Call the next interceptor in the chain.
    //下一個攔截器,index每次會加一指向當前鏈的下一條
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
    val interceptor = interceptors[index]

    //注意這行代碼,這裏實現了責任連模式
    //調用當前攔截器的intercept方法,並在參數中傳入下一條鏈。
    //在intercept方法的實現中會繼續調next.proceed()實現鏈式調用。
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    // Confirm that the next interceptor made its required call to chain.proceed().
    check(exchange == null || 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 inner class 攔截器1 : Interceptor {
        override fun intercept(攔截器2): Response {
            val response = 攔截器2.proceed(request)
            return response
        }
    }
	
	internal inner class 攔截器2 : Interceptor {
        override fun intercept(攔截器3): Response {
            val response = 攔截器3.proceed(request)
            return response
        }
    }
    
    //整個代碼的調用順序就類似於此
    攔截器1.intercept(攔截器2)
    攔截器2.intercept(攔截器3)
    ...
    ...
    攔截器6.intercept(攔截器7)

那麼看到這裏,最終執行網絡請求的攔截器到底是哪個呢?這樣看來肯定是最後一個咯。如果是從緩存中獲取,那麼最後一個就是CacheInterceptor。如果是正常獲取那麼最後一個就是CallServerInterceptor,我們來看它的代碼。

/** This is the last interceptor in the chain. It makes a network call to the server. */
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
	val realChain = chain as RealInterceptorChain
	//exchange 真正執行網絡請求的類
    val exchange = realChain.exchange()
    val request = realChain.request()
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

	//請求頭
    exchange.writeRequestHeaders(request)
	...

	//寫入請求
	if (requestBody.isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          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.
          val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
          requestBody.writeTo(bufferedRequestBody)
          bufferedRequestBody.close()
    }

	//得到響應頭
	exchange.responseHeadersEnd(response)
	...
	
	
	//構造響應response
    response = if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
}

可見真正執行網絡請求的類是Exchange,在該類中所有IO請求都和一個叫ExchangeCodec的類相關。這個類是上一條鏈中構建傳遞下來的。繼續跟代碼,

/** Opens a connection to the target server and proceeds to the next interceptor. */
object ConnectInterceptor : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val request = realChain.request()
    val transmitter = realChain.transmitter()

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    val doExtensiveHealthChecks = request.method != "GET"
	//這裏創建的exchange
    val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)

    return realChain.proceed(request, transmitter, exchange)
  }
}

  //transmitter.newExchange
  /** Returns a new exchange to carry a new request and response. */
  internal fun newExchange(chain: Interceptor.Chain, doExtensiveHealthChecks: Boolean): Exchange {
	...
	//哈哈,發現codec創建的代碼在這裏
    val codec = exchangeFinder!!.find(client, chain, doExtensiveHealthChecks)
    val result = Exchange(this, call, eventListener, exchangeFinder!!, codec)
	
	...
  }

exchangeFinder!!.find裏面調用了resultConnection.newCodec,這個地方真正創建了socket

  @Throws(SocketException::class)
  internal fun newCodec(client: OkHttpClient, chain: Interceptor.Chain): ExchangeCodec {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    val http2Connection = this.http2Connection

    return if (http2Connection != null) {
      //http2請求
      Http2ExchangeCodec(client, this, chain, http2Connection)
    } else {
      //http1請求
      socket.soTimeout = chain.readTimeoutMillis()
      source.timeout().timeout(chain.readTimeoutMillis().toLong(), MILLISECONDS)
      sink.timeout().timeout(chain.writeTimeoutMillis().toLong(), MILLISECONDS)
      Http1ExchangeCodec(client, this, source, sink)
    }
  }

以請求頭exchange.writeRequestHeaders(request)函數爲例,最終調用的是Http2ExchangeCodec.writeRequestHeaders

  //Http2ExchangeCodec.writeRequestHeaders
  override fun writeRequestHeaders(request: Request) {
    if (stream != null) return

    val hasRequestBody = request.body != null
    val requestHeaders = http2HeadersList(request)
    //建立IO流
    stream = connection.newStream(requestHeaders, hasRequestBody)
    // We may have been asked to cancel while creating the new stream and sending the request
    // headers, but there was still no stream to close.
    if (canceled) {
      stream!!.closeLater(ErrorCode.CANCEL)
      throw IOException("Canceled")
    }
    stream!!.readTimeout().timeout(chain.readTimeoutMillis().toLong(), TimeUnit.MILLISECONDS)
    stream!!.writeTimeout().timeout(chain.writeTimeoutMillis().toLong(), TimeUnit.MILLISECONDS)
  }

通過進一步分析connection.newStream方法,發現ohttp最終使用了Okio來封裝網絡請求。Okio調用的代碼還有很大一部分,這裏不做具體分析了,有興趣的讀者可以繼續往下看源碼。

通過分析我們知道了Okhttp有七大攔截器,那麼它們的作用又分別是什麼呢?

interceptors
應用攔截器,可實現連接日誌添加,添加請求頭,重定向等自定義操作。

RetryAndFollowUpInterceptor
重試和重定向攔截器,它負責連接失敗時重試和重定向操作。

BridgeInterceptor
橋攔截器,連接用戶請求信息 和 HTTP 請求的橋樑。負責把用戶請求轉換爲發送到服務器的請求,並把服務器的響應轉化爲用戶需要的響應

CacheInterceptor
緩存攔截器,負責讀取緩存、更新緩存,判斷請求是否直接從緩存中獲取

ConnectInterceptor - Opens a connection to the target server and proceeds to the next interceptor
連接攔截器,負責和服務器建立連接

networkInterceptors -
網絡攔截器,可自定義

CallServerInterceptor -This is the last interceptor in the chain. It makes a network call to the server
負責向服務器發送數據,從服務器讀取響應數據

Okhttp中的每一個攔截器都負責不能的職能,並且值得深入去分析,這也是Okhttp的精華所在。

總結

Okhttp異步調用採用了線程池來實現,該線程池類似CachedThreadPool,有任務來時直接執行無需等待。

Okhttp採用責任鏈模式工作模式,按照順序依次調用攔截器的intercept方法把任務往後傳遞,如果中途有攔截器處理了事件,那麼不在傳遞下去,整個事件結束,如果沒有人處理該事件那麼傳遞給最後一個攔截器處理。

Okhttp可通過自定義應用攔截器和網絡攔截器來幫助我們實現對網絡請求的靈活控制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章