錦囊篇|一文摸懂OkHttp

前言

在面試中,OkHttp做爲咱們基本屬於必用的第三方庫來講,也是一個很是重要的考點,因此對其原理的掌握也會讓咱們的能力獲得必定的提高。java

OkHttp官網地址:square.github.io/okhttp/git

基本使用

先一段引入關於OkHttp的使用,這是直接拉取了官網掛着的使用方法。由於在通常的使用過程當中,後臺可能會經過比較帶有的session或者cookie來判斷當前用戶是否和緩存的用戶相同,因此通常一個項目總體使用單例模式來建立OkHttpClient的對象。github

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製代碼

源碼解析

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

Call call = client.newCall(request);
call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {

        }

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

        }
});
複製代碼

這是咱們的在okhttp中使用的方法,整個項目的解析將圍繞下面5個類進行。web

  • OkHttpClient: 全局管理者
  • Request: 請求體
  • Call: 請求發起者
  • Callback: 數據接收通道
  • Response: 響應數據體

OkHttpClient、Request

首先是OkHttpClientRequest。 爲何這兩個一塊兒講解呢?由於兩個構造方式相同OkHttpClient是一個全局掌控者,Request是一個請求體的封裝。面試

public final class Request {
  final HttpUrl url; // 路徑
  final String method; // 請求方式
  final Headers headers; // 請求頭
  final @Nullable RequestBody body; // 請求體
  final Object tag;
}

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  final Dispatcher dispatcher; // 分發器
  final @Nullable Proxy proxy; //代理
  final List<Protocol> protocols; //協議
  final List<ConnectionSpec> connectionSpecs; //傳輸層版本和鏈接協議
  final List<Interceptor> interceptors; // 攔截器
  final List<Interceptor> networkInterceptors; // 網絡攔截器
  final EventListener.Factory eventListenerFactory;
  final ProxySelector proxySelector; //代理選擇
  final CookieJar cookieJar; //cookie
  final @Nullable Cache cache; //緩存
  final @Nullable InternalCache internalCache; //內部緩存
  final SocketFactory socketFactory; //socket 工廠
  final @Nullable SSLSocketFactory sslSocketFactory; //安全套接層socket 工廠,用於HTTPS
  final @Nullable CertificateChainCleaner certificateChainCleaner;  // 驗證確認響應證書 適用 HTTPS 請求鏈接的主機名。
  final HostnameVerifier hostnameVerifier; // 主機名字確認
  final CertificatePinner certificatePinner; // 證書鏈
  final Authenticator proxyAuthenticator; // 代理身份驗證
  final Authenticator authenticator; // 本地身份驗證
  final ConnectionPool connectionPool; // 鏈接池,複用鏈接
  final Dns dns; // 域名
  final boolean followSslRedirects; // 安全套接層重定向
  final boolean followRedirects; // 本地重定向
  final boolean retryOnConnectionFailure; // 重試鏈接失敗
  final int connectTimeout; // 鏈接超時
  final int readTimeout; // read 超時
  final int writeTimeout;  // write 超時 
  final int pingInterval;
}
複製代碼

能看到OkHttpClient的內部元素不少,可是咱們不少時間並不會進行直接的使用,是由於他本身已經作了不少層的封裝,另外他們這種建立對象的模式又稱爲建造者設計模式。設計模式

internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      this.connectionPool = okHttpClient.connectionPool
      this.interceptors += okHttpClient.interceptors
      // 。。。。。
    }
複製代碼

對建造者設計模式作一個比較通俗的介紹,就是將咱們草稿圖上的數據應用到真實的場景中去。promise

val client = OkHttpClient.Builder().build()
// 調用Builder()的builder()函數
// 最後是建立了OkHttpClient對象,咱們本來的數據是存儲在OkHttpClient的Builder中
fun build(): OkHttpClient = OkHttpClient(this)
複製代碼

可是說了這麼久,仍是有一個問題啊,我沒看到他對數據進行了使用啊??彆着急,如今咱們進入咱們的使用環節了。緩存

Call:任務的執行者

接下來就是Call這個類,根據模版寫法,咱們知道須要將封裝好的Request請求體數據塞入OkHttpClient中返回的就是一個Call安全

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼

經過進入newCall()方法,咱們知道返回的數據實際上是實現Call的接口一個具體類RealCall,具體操做咱們不用知道,咱們只用知道返回的一個具體類是什麼就能夠了,由於日後的操做都是圍繞一個具體的東西展開的。 在看模版的下一句話call.enqueue(...),進入函數,咱們能夠看到下述的函數。服務器

override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      // 一個Call只能進行一次的執行操做
      executed = true
    }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback)) // 1 -->
  }
複製代碼

其餘都還好,直接看到上述最後一行代碼,由於咱們須要將任務發佈出去,而且拿到數據,那麼天然須要一個分發器了和一個接收回饋數據的通道了,這顯然就是咱們上文中OkHttpClient中所看到的dispatcher和咱們在外部定義好的Callback ==> responseCallback

internal fun enqueue(call: AsyncCall) {
    // 使用同步機制對請求數據進行控制
    synchronized(this) {
      readyAsyncCalls.add(call)

      // 我的理解:對同一個host發起多個請求是爲了加快查詢速度,減小資源浪費
      // 他會從正在執行運行的Call中先進行查找,再從準備執行的Call中查找
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute() // 1 ==>
  }
// 1 ==>
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()

        // 正在運行的請求數量不能大於64個
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        // 能夠存在的host數量爲5個
        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.executeOn(executorService)
    }
    return isRunning
  }
複製代碼

想來對整個的處理過程已經有一個比較詳細的講解了,可是咱們仍是沒有看到數據的返回操做,甚至說具體的運行,不過咱們可以注意到一箇中途意外冒出的變量executorService,這個變量是從哪裏來的呢?

溯源咱們可以發現,他在Dispatcher中就已經有過了初始化操做。

@get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }
複製代碼

一看到要說ThreadPoolExecutor,哦哦哦哦!線程池,可是和什麼線程池長得特別像呢?進入已經定義好的Executors類中查找,可以查找到以下的代碼段:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
複製代碼

是否是有點像呢?那好,咱們就認定了它是咱們的CachedThreadPool線程池。

ok!fine!用的線程池來進行異步操做,那確定就是說明裏面有一個線程了,那這個線程是啥,咱們是否內心有點數呢?若是沒有,也沒啥關係,下面咱們將繼續引出。

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

      var success = false
      try {
        executorService.execute(this) // (1)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException) // (2)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // (3)
        }
      }
    }
複製代碼

那接下來就又不是什麼大問題了,主要就看到咱們的註釋一、二、3。

  1. executorService.execute(this):對於線程池而言運行的顯然是線程,而this幾就是咱們的AsyncCall,經過對AsyncCall的觀察咱們也是可以得知它是繼承了Runnable的,因此異步進行的操做來源咱們也已經清楚了。
  2. responseCallback.onFailure(),也就是經過咱們傳入的Callback接收數據的錯誤反饋。
  3. client.dispatcher.finished(this):爲何須要這個呢?其實他本來有這樣的一段英文註釋,This call is no longer running!,也就是說明這個函數是爲了通知Dispatcher咱們的AsyncCall已經完成了運行。

又開始有問題了吧,看着就着急。咋就沒看到responseCallback()onResponse方法的使用呢???

那咱們作一個猜想吧,其實我看了一下基本也是正解了。咱們的不是Runnable嘛,而數據是放在線程池中run()來運行的,那麼onResponse()方法的出現應該是在run()的這個函數中了。接下來咱們繼續收看代碼

override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain() // (1)
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response) //(2)
        } catch (e: IOException) {
            // 。。。。。
            responseCallback.onFailure(this@RealCall, e)
        } catch (t: Throwable) {
            // 。。。。。
            responseCallback.onFailure(this@RealCall, e)
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }
複製代碼

在這裏的註釋(2)中,咱們很幸運的看到了onResponse()的方法調用了。好那接下來就是下一個問題了,Response是從哪裏來的????

Response的誕生

上面不是寫着嘛??getResponseWithInterceptorChain()這個函數裏來的唄。哇哦!!沒錯了,那它是怎麼來的? 🤔🤔🤔

又要看代碼了,好煩好煩。。。

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
    // 咱們以前上面也出現過forWebSocket這個flag
    // 其實它是okhttp爲了長鏈接而準備的
    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
    )

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

爲了儘可能讓代碼簡潔明瞭,我截取了一些關鍵代碼,以供參考。

其實他就是經過一堆的攔截器來獲取數據的,可是顯然這裏不是終點站,由於咱們看到的return中就仍是一個函數,說明答案還在這個函數中。經過觀察咱們很容易得知,這個的操做的具體類是一個叫作RealInterceptorChain的類。

override fun proceed(request: Request): Response {
    // 不斷調用下一個攔截器對相應的數據進行返回
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    val response = interceptor.intercept(next)

    return response
  }
複製代碼

如圖所示,哪一個攔截器能攔截成功,就會返回咱們須要的數據 Response,固然這個數據你須要注意,並不必定是成功的數據,通常來講數據成功的獲取都須要走到咱們的響應攔截器以後才能真正的成功。

CacheInterceptor緩存攔截器的源碼解讀

這裏咱們須要重點講解一下CacheInterceptor這個類,咱們截取他的intercept()方法,由於裏面涉及了咱們面試時可能會頻繁使用的響應碼

override fun intercept(chain: Interceptor.Chain): Response {
    // 依據咱們傳入的request獲得cache中緩存的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

    cache?.trackResponse(strategy)

    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body?.closeQuietly()
    }

    // 本地查詢到的網絡請求和緩存數據皆爲空的狀況下
    // 爆HTTP_GATEWAY_TIMEOUT,網關超時的錯誤
    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 (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 (cacheResponse != null) {
      // HTTP_NOT_MODIFIED:304,說明咱們本地的緩存是最新的
      // 沒有必要將數據從服務器拉取進行更新了
      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
  }
複製代碼

總結

最後咱們經過一張圖來完成對整個OkHttp的工做流程梳理。

相關文章
相關標籤/搜索