我都把OkHttp解釋的這麼明白了,您還不會嗎?

我都把OkHttp解釋的這麼明白了,您還不會嗎?

 

前言

最近都在學校上課,三天滿課,剩下還要課程複習維持績點,基本上維持周更也已經比較吃力了,不過還是會繼續堅持,之後的推文基本上會在周天推,嘻嘻。

在面試中OkHttp作爲我們基本屬於必用的第三方庫來說,也是一個非常重要的考點,所以對其原理的掌握也會讓我們的能力得到一定的提升。

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

基本使用

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

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個類進行。

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

OkHttpClient、Request

首先是OkHttpClient和Request。爲什麼這兩個一起講解呢?因爲兩個構造方式相同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
      // 。。。。。
    }

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

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

那接下來就又不是什麼大問題了,主要就看到我們的註釋1、2、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是從哪裏來的????

我都把OkHttp解釋的這麼明白了,您還不會嗎?

 

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
  }

我都把OkHttp解釋的這麼明白了,您還不會嗎?

 

如圖所示,哪個攔截器能攔截成功,就會返回我們需要的數據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的工作流程梳理。

 

我都把OkHttp解釋的這麼明白了,您還不會嗎?

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