網絡請求工具-OkHttp

1.簡介

大體的請求流程以下 html

請求大體被分爲了三個階段:準備、隊列、預處理。markdown

  1. 其中準備包括OkHttpClient、Request等等的實例化;
  2. 隊列階段在同步執行的網絡請求因爲直接調用:execute(),因此不存在隊列階段,而且會由於網絡I/O阻塞掉當前的線程。而enqueue()方法則是異步方法,會經過Dispatcher利用ExecutorService實現,而最終進行網絡請求時和同步execute()接口一致;
  3. 預處理:預處理則是針對網絡請求的預處理,OkHttp的實現方式是採用了一個攔截器鏈(Interceptor Chain),這一系列的攔截器將會爲咱們的網絡請求添加一些header信息,或者是預約好請求失敗的重試方式、從鏈接池中獲取可用的實例以複用等等。接下來將會按照這三個階段進行閱讀源代碼。

2.準備階段

OkHttpClient是一個請求端,咱們能夠經過new來建立一個OkHttpClient:cookie

OkHttpClient httpClient = new OkHttpClient();
複製代碼

點進入方法內,找到空參數的構造方法:網絡

//OkHttpClient.decompiled.Java
public OkHttpClient() {
  this(new OkHttpClient.Builder());
}
複製代碼

發現,咱們實際上默認傳入了一個Builder對象,而這個Builder則樹咱們的一個配置對象,咱們能夠經過Builder來對OkHttpClient進行一個配置,Builder的構造函數以下:併發

public Builder() {
     this.dispatcher = new Dispatcher();
     this.connectionPool = new ConnectionPool();
     boolean var1 = false;
     this.interceptors = (List)(new ArrayList());
     var1 = false;
     this.networkInterceptors = (List)(new ArrayList());
     this.eventListenerFactory = Util.asFactory(EventListener.NONE);
     this.retryOnConnectionFailure = true;
     this.authenticator = Authenticator.NONE;
     this.followRedirects = true;
     this.followSslRedirects = true;
     this.cookieJar = CookieJar.NO_COOKIES;
     this.dns = Dns.SYSTEM;
     this.proxyAuthenticator = Authenticator.NONE;
     SocketFactory var10001 = SocketFactory.getDefault();
     Intrinsics.checkNotNullExpressionValue(var10001, "SocketFactory.getDefault()");
     this.socketFactory = var10001;
     this.connectionSpecs = OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp();
     this.protocols = OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp();
     this.hostnameVerifier = (HostnameVerifier)OkHostnameVerifier.INSTANCE;
     this.certificatePinner = CertificatePinner.DEFAULT;
     this.connectTimeout = 10000;
     this.readTimeout = 10000;
     this.writeTimeout = 10000;
     this.minWebSocketMessageToCompress = 1024L;
  }
複製代碼

這提供了一長串的構造參數,具體的意義稍候再說,咱們只須要知道OkHttpClient的Build流程。app

咱們能夠將OkHttpClient理解爲一個請求器,它用來將請求發送出去,而請求就是Request,咱們在聲明完OkHttpClient後,須要聲明Request對象:框架

Request request = new Request.Builder()
            .url(url).post(requestBody).build();
         
複製代碼

其中的URL天然而然就是請求的地址,而RequestBody則是請求的請求體,自己是一個抽象類,咱們能夠設置請求的MediaType、Content和ContentLength等等。以下代碼就設置了Content-Type爲「text/html」(create方法中還會在其後拼接上:"; charset=utf-8"),請求的數據爲data,這個data爲表單數據,最終會存放在請求的body中,格式爲var1=x&var2=y。異步

RequestBody requestBody = RequestBody.create(MediaType.parse("text/html;charset=utf-8"), data);
複製代碼

接下來,咱們經過OkHttpClient.newCall調用生成一個Call對象:socket

Call x = httpClient.newCall(request);

//newCall方法內:返回了一個RealCall對象;
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
複製代碼

而RealCall的構造函數:async

class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
	//Omit
}
複製代碼

咱們在newCall方法中,填入了OkHttpClient、基本的請求、以及是不是WebSocket請求三個參數。這步走完,最終獲得了一個Call對象。

3.同步請求

若是咱們採用RealCall.execute()方法,那麼將會走同步請求,即在當前線程執行這個請求,而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)
    }
  }
 
//Java版本
@Override 
public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  try {
    client.dispatcher().executed(this);                                
    Response result = getResponseWithInterceptorChain();                
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);                                
  }
}
複製代碼

這裏因爲OkHttp後來使用Kotlin重寫了,因此暫時按照Java的來講明:Java中的executed是一個boolean類型的變量,用於檢查該RealCall是否已經被execute過了,配合sychronized加鎖,這也說明了這個ReadCall只會被執行一次。

dispatcher是client的成員之一,能夠視做事件的分發器,調用executed(this)即執行網絡請求,而getResponseWithInterceptorChain();則是走了第三步:預處理;使整個請求經過Interceptor Chain,最終得到Response,若是得到的Response爲空,則說明被攔截器鏈所取消了。

而Kotlin版本中,executed被聲明成了:「能夠用原子方式更新的boolean值」,即高併發下,保證只有一個線程可以訪問,其實就等同於Sychronized(){}包裹的一段代碼。 compareAndSet(原先的值,要修改的值),能夠參考CAS的實現,後面的代碼大致上差不太多。

4. 隊列(異步請求)

如今回到得到RealCall對象的時候,咱們一般不但願網絡請求在主線程直接執行,這樣會致使畫面沒法及時渲染出幀,影響用戶體驗,甚至會在Android中致使ANR。咱們經過:

httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

            }
        });
複製代碼

來將一個請求插入請求隊列,其中咱們要重寫兩個回調函數,分別是失敗、成功時的回調函數。

enqueue的方法體以下:

override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed"}
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
複製代碼

一樣地,一個RealCall只能執行一次,CallStart()是開始執行的一個回調,而最後經過Dispatch進行事件分發,將一個異步的AsyncCall添加到發送隊列中:

internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

	  //若是不是WebSocket
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)//重用一個已經存在的Call
      }
    }
    promoteAndExecute()//通知線程池去取出一個Call來處理;
   
複製代碼

在PromoteAndExecute方法中:

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }
	//遍歷executableCalls
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
複製代碼

首先,遍歷readyAsyncCalls,有兩種特殊狀況:

  1. 當runningAsyncCalls超過maxRequests時,說明線程要處理的任務已經滿了,直接跳出循環;
  2. 當asyncCall.callsPerHost().get()也就是當前Call所對應的host已經接受了足夠多的Call請求,那麼循環繼續;

若是兩者都不知足,則執行如下:(主要是往executableCalls中添加執行的請求和往runningAsyncCalls裏添加要執行的請求。)

而後,遍歷剛纔的獲得的executableCalls,並用調用線程池執行任務。具體是調用asyncCall的executeOn(executorService())方法實現的。

executeOn方法的方法以下:

fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        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!
        }
      }
    }
複製代碼

而AsyncCall的類以下:

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
		
		    fun executeOn(executorService: ExecutorService) {
		      client.dispatcher.assertThreadDoesntHoldLock()
		
		      var success = false
		      try {
		        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)
	        }
	      }
	    }
	  }
複製代碼

終於,咱們在重寫的run()方法中,看到了和直接調用RealCall.execute類似的代碼,經過getResponseWithInterceptorChain將一個請求經過第三步的攔截器鏈處理,再發送出去,最終成功獲取到Response或者失敗,報錯。

try {
	val response = getResponseWithInterceptorChain()
	signalledCallback = true
	responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {//omit	}
複製代碼

這裏的responseCallBack是AsyncCall在構造時,傳入的構造函數,調用處就在enqueue方法中:

override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
複製代碼

這個responseCallback就是咱們在

httpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {}

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {}
});
複製代碼

中new 出的Callback()。

5. 預處理(攔截器鏈Interceptor Chain)

不管是同步請求,仍是異步請求,咱們最終都走到了這個方法:

getResponseWithInterceptorChain()
複製代碼

責任鏈模式的攔截器,總體能夠在請求發出前再進行統一的包裝,包括給請求加上請求頭(Cookie或者是Token)、攔截不正確的請求等等。內置有五層攔截器,不包括(newWork攔截器),該方法的方法體以下:

@Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors//0(用戶自定義的App攔截器,非netWorkInterceptor)
    interceptors += RetryAndFollowUpInterceptor(client)//1
    interceptors += BridgeInterceptor(client.cookieJar)//2
    interceptors += CacheInterceptor(client.cache)//3
    interceptors += ConnectInterceptor//4
    //若是不是WebSocket,則添加用戶本身自定義的netWork攔截器
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)//5

	
    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 {
	  //focus
      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)
      }
    }
  }
複製代碼

最終,走到了:

val response = chain.proceed(originalRequest)
複製代碼

點開proceed方法:

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 鏈中的下一個攔截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }	
複製代碼

Interceptor是對攔截器的抽象接口定義,在intercept方法中,將會接收一個Chain。而Chain就是攔截鏈的抽象定義,這樣一來,當攔截器的具體實如今調用intercept方法時,就能夠經過Chain攔截鏈,調用request方法取出客戶端請求,而後再調用process方法從而建立下一個節點的Chain,這時,在下一個節點的Chain中,將會定位到下一個Interceptor攔截器。由此類推,直至最後一個攔截器Interceptor,再也不執行Chain的process方法。由於執行到最後一個攔截器時,後面再也不有攔截器,因此在最後一個攔截器調用後,就須要將最終的響應結果,反饋給客戶端了。各個攔截器的職責以下:

在getResponseWithInterceptorChain()方法中,咱們傳入最原始的:originalRequest

val response = chain.proceed(originalRequest)
複製代碼

而在RetryAndFollowUp攔截器中,咱們找到:

//RetryAndFollowUpInterceptor.kt
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
	// 省略
    var request = chain.request
    // 省略 ······
    response = realChain.proceed(request)
    // 省略   
	}
//······
}
複製代碼

咱們在該攔截器中,有一次調用了chain.proceed(request),而BridgeInterceptor同理:

//BridgeInterceptor.kt
val networkResponse = chain.proceed(requestBuilder.build())
複製代碼

這裏甚至傳入了一個新的利用RequestBuilder.build()構造的request,最終走到最後一層攔截器:CallServerInterceptor中,沒有了chain.proceed,取而代之的是:

var response = responseBuilder
    .request(request)
    .handshake(exchange.connection.handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build()
複製代碼

不出意外的話,咱們在此處將會拿到這個請求的結果,而後逐層返回,返回到CallBack的回調處。

參考來源
  1. OKHttp攔截器之責任鏈模式
  2. OkHttp框架中攔截器設計精髓之責任鏈模式
  3. OkHttp解析
  4. 一些簡單的攔截器interceptor
相關文章
相關標籤/搜索