偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

面試突擊:OkHttp 原理八連問

移動開發(fā) Android
OkHttp 可以說是 Android 開發(fā)中最常見的網(wǎng)絡(luò)請求框架,OkHttp 使用方便,擴(kuò)展性強(qiáng),功能強(qiáng)大,OKHttp 源碼與原理也是面試中的???。

[[434054]]

OkHttp 可以說是 Android 開發(fā)中最常見的網(wǎng)絡(luò)請求框架,OkHttp 使用方便,擴(kuò)展性強(qiáng),功能強(qiáng)大,OKHttp 源碼與原理也是面試中的???。

但是 OKHttp 的源碼內(nèi)容比較多,想要學(xué)習(xí)它的源碼往往千頭萬緒,一時抓不住重點(diǎn). 本文從幾個問題出發(fā)梳理 OKHttp 相關(guān)知識點(diǎn),以便快速構(gòu)建 OKHttp 知識體,本文主要包括以下內(nèi)容

  1.   OKHttp 請求的整體流程是怎樣的?
  2.   OKHttp 分發(fā)器是怎樣工作的?
  3.   OKHttp 攔截器是如何工作的?
  4.   應(yīng)用攔截器和網(wǎng)絡(luò)攔截器有什么區(qū)別?
  5.   OKHttp 如何復(fù)用 TCP 連接?
  6.   OKHttp 空閑連接如何清除?
  7.   OKHttp 有哪些優(yōu)點(diǎn)?
  8.   OKHttp 框架中用到了哪些設(shè)計(jì)模式?

1. OKHttp請求整體流程介紹

首先來看一個最簡單的 Http 請求是如何發(fā)送的。 

  1. val okHttpClient = OkHttpClient()  
  2. val request: RequestRequest = Request.Builder()  
  3.     .url("https://www.google.com/")  
  4.     .build()  
  5. okHttpClient.newCall(request).enqueue(object :Callback{  
  6.     override fun onFailure(call: Call, e: IOException) {  
  7.     }  
  8.     override fun onResponse(call: Call, response: Response) {  
  9.     }  
  10. }) 

這段代碼看起來比較簡單,OkHttp 請求過程中最少只需要接觸 OkHttpClient、Request、Call、 Response,但是框架內(nèi)部會進(jìn)行大量的邏輯處理。

所有網(wǎng)絡(luò)請求的邏輯大部分集中在攔截器中,但是在進(jìn)入攔截器之前還需要依靠分發(fā)器來調(diào)配請求任務(wù)。關(guān)于分發(fā)器與攔截器,我們在這里先簡單介紹下,后續(xù)會有更加詳細(xì)的講解

  1.  分發(fā)器:內(nèi)部維護(hù)隊(duì)列與線程池,完成請求調(diào)配;
  2.  攔截器:五大默認(rèn)攔截器完成整個請求過程。

整個網(wǎng)絡(luò)請求過程大致如上所示

  1.  通過建造者模式構(gòu)建 OKHttpClient 與 Request
  2.  OKHttpClient 通過 newCall 發(fā)起一個新的請求
  3.  通過分發(fā)器維護(hù)請求隊(duì)列與線程池,完成請求調(diào)配
  4.  通過五大默認(rèn)攔截器完成請求重試,緩存處理,建立連接等一系列操作
  5.  得到網(wǎng)絡(luò)請求結(jié)果

2. OKHttp分發(fā)器是怎樣工作的?

分發(fā)器的主要作用是維護(hù)請求隊(duì)列與線程池,比如我們有100個異步請求,肯定不能把它們同時請求,而是應(yīng)該把它們排隊(duì)分個類,分為正在請求中的列表和正在等待的列表, 等請求完成后,即可從等待中的列表中取出等待的請求,從而完成所有的請求

而這里同步請求各異步請求又略有不同

同步請求 

  1. synchronized void executed(RealCall call) {  
  2.  runningSyncCalls.add(call);  

因?yàn)橥秸埱蟛恍枰€程池,也不存在任何限制。所以分發(fā)器僅做一下記錄。后續(xù)按照加入隊(duì)列的順序同步請求即可

異步請求 

  1. synchronized void enqueue(AsyncCall call) {  
  2.  //請求數(shù)最大不超過64,同一Host請求不能超過5個  
  3.  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost)    {  
  4.   runningAsyncCalls.add(call);  
  5.   executorService().execute(call);  
  6.  } else {  
  7.   readyAsyncCalls.add(call);  
  8.  }  

當(dāng)正在執(zhí)行的任務(wù)未超過最大限制64,同時同一 Host 的請求不超過5個,則會添加到正在執(zhí)行隊(duì)列,同時提交給線程池。否則先加入等待隊(duì)列。每個任務(wù)完成后,都會調(diào)用分發(fā)器的 finished 方法,這里面會取出等待隊(duì)列中的任務(wù)繼續(xù)執(zhí)行

3. OKHttp攔截器是怎樣工作的?

經(jīng)過上面分發(fā)器的任務(wù)分發(fā),下面就要利用攔截器開始一系列配置了 

  1. # RealCall  
  2.   override fun execute(): Response {  
  3.     try {  
  4.       client.dispatcher.executed(this)  
  5.       return getResponseWithInterceptorChain()  
  6.     } finally {  
  7.       client.dispatcher.finished(this) 
  8.     }  
  9.   } 

我們再來看下 RealCall的execute方法,可以看出,最后返回了 getResponseWithInterceptorChain ,責(zé)任鏈的構(gòu)建與處理其實(shí)就是在這個方法里面 

  1. internal fun getResponseWithInterceptorChain(): Response {  
  2.     // Build a full stack of interceptors.  
  3.     val interceptors = mutableListOf<Interceptor>()  
  4.     interceptors += client.interceptors 
  5.     interceptors += RetryAndFollowUpInterceptor(client)  
  6.     interceptors += BridgeInterceptor(client.cookieJar)  
  7.     interceptors += CacheInterceptor(client.cache) 
  8.     interceptors += ConnectInterceptor  
  9.     if (!forWebSocket) {  
  10.       interceptors += client.networkInterceptors  
  11.     }  
  12.     interceptors += CallServerInterceptor(forWebSocket)  
  13.     val chain = RealInterceptorChain 
  14.         call = this,interceptorsinterceptors = interceptors,index = 0  
  15.     )  
  16.     val response = chain.proceed(originalRequest)  
  17.   } 

如上所示,構(gòu)建了一個 OkHttp 攔截器的責(zé)任鏈

責(zé)任鏈,顧名思義,就是用來處理相關(guān)事務(wù)責(zé)任的一條執(zhí)行鏈,執(zhí)行鏈上有多個節(jié)點(diǎn),每個節(jié)點(diǎn)都有機(jī)會(條件匹配)處理請求事務(wù),如果某個節(jié)點(diǎn)處理完了就可以根據(jù)實(shí)際業(yè)務(wù)需求傳遞給下一個節(jié)點(diǎn)繼續(xù)處理或者返回處理完畢。

如上所示責(zé)任鏈添加的順序及作用,如下表所示:

攔截器 作用
應(yīng)用攔截器 拿到的是原始請求,可以添加一些自定義 header、通用參數(shù)、參數(shù)加密、網(wǎng)關(guān)接入等等。
RetryAndFollowUpInterceptor 處理錯誤重試和重定向
BridgeInterceptor 應(yīng)用層和網(wǎng)絡(luò)層的橋接攔截器,主要工作是為請求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存響應(yīng)結(jié)果的cookie,如果響應(yīng)使用gzip壓縮過,則還需要進(jìn)行解壓。
CacheInterceptor 緩存攔截器,如果命中緩存則不會發(fā)起網(wǎng)絡(luò)請求。
ConnectInterceptor 連接攔截器,內(nèi)部會維護(hù)一個連接池,負(fù)責(zé)連接復(fù)用、創(chuàng)建連接(三次握手等等)、釋放連接以及創(chuàng)建連接上的socket流。
networkInterceptors(網(wǎng)絡(luò)攔截器) 用戶自定義攔截器,通常用于監(jiān)控網(wǎng)絡(luò)層的數(shù)據(jù)傳輸。
CallServerInterceptor 請求攔截器,在前置準(zhǔn)備工作完成后,真正發(fā)起了網(wǎng)絡(luò)請求。

我們的網(wǎng)絡(luò)請求就是這樣經(jīng)過責(zé)任鏈一級一級的遞推下去,最終會執(zhí)行到 CallServerInterceptor的intercept 方法,此方法會將網(wǎng)絡(luò)響應(yīng)的結(jié)果封裝成一個 Response 對象并 return。之后沿著責(zé)任鏈一級一級的回溯,最終就回到 getResponseWithInterceptorChain 方法的返回,如下圖所示:

4. 應(yīng)用攔截器和網(wǎng)絡(luò)攔截器有什么區(qū)別?

從整個責(zé)任鏈路來看,應(yīng)用攔截器是最先執(zhí)行的攔截器,也就是用戶自己設(shè)置 request 屬性后的原始請求,而網(wǎng)絡(luò)攔截器位于 ConnectInterceptor 和 CallServerInterceptor 之間,此時網(wǎng)絡(luò)鏈路已經(jīng)準(zhǔn)備好,只等待發(fā)送請求數(shù)據(jù)。它們主要有以下區(qū)別

    1.  首先,應(yīng)用攔截器在 RetryAndFollowUpInterceptor 和 CacheInterceptor 之前,所以一旦發(fā)生錯誤重試或者網(wǎng)絡(luò)重定向,網(wǎng)絡(luò)攔截器可能執(zhí)行多次,因?yàn)橄喈?dāng)于進(jìn)行了二次請求,但是應(yīng)用攔截器永遠(yuǎn)只會觸發(fā)一次。另外如果在 CacheInterceptor 中命中了緩存就不需要走網(wǎng)絡(luò)請求了,因此會存在短路網(wǎng)絡(luò)攔截器的情況。

    2.  其次,除了 CallServerInterceptor 之外,每個攔截器都應(yīng)該至少調(diào)用一次 realChain.proceed 方法。實(shí)際上在應(yīng)用攔截器這層可以多次調(diào)用 proceed 方法(本地異常重試)或者不調(diào)用 proceed 方法(中斷),但是網(wǎng)絡(luò)攔截器這層連接已經(jīng)準(zhǔn)備好,可且僅可調(diào)用一次 proceed 方法。

    3.  最后,從使用場景看,應(yīng)用攔截器因?yàn)橹粫{(diào)用一次,通常用于統(tǒng)計(jì)客戶端的網(wǎng)絡(luò)請求發(fā)起情況;而網(wǎng)絡(luò)攔截器一次調(diào)用代表了一定會發(fā)起一次網(wǎng)絡(luò)通信,因此通??捎糜诮y(tǒng)計(jì)網(wǎng)絡(luò)鏈路上傳輸?shù)臄?shù)據(jù)。

5. OKHttp如何復(fù)用TCP連接?

ConnectInterceptor 的主要工作就是負(fù)責(zé)建立 TCP 連接,建立 TCP 連接需要經(jīng)歷三次握手四次揮手等操作,如果每個 HTTP 請求都要新建一個 TCP 消耗資源比較多 而 Http1.1 已經(jīng)支持 keep-alive ,即多個 Http 請求復(fù)用一個 TCP 連接,OKHttp 也做了相應(yīng)的優(yōu)化,下面我們來看下 OKHttp 是怎么復(fù)用 TCP 連接的

ConnectInterceptor 中查找連接的代碼會最終會調(diào)用到 ExchangeFinder.findConnection 方法,具體如下: 

  1. # ExchangeFinder  
  2. //為承載新的數(shù)據(jù)流 尋找 連接。尋找順序是 已分配的連接、連接池、新建連接  
  3. private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,  
  4.     int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {  
  5.   synchronized (connectionPool) {  
  6.     // 1.嘗試使用 已給數(shù)據(jù)流分配的連接.(例如重定向請求時,可以復(fù)用上次請求的連接)  
  7.     releasedConnection = transmitter.connection;  
  8.     result = transmitter.connection;  
  9.     if (result == null) {  
  10.       // 2. 沒有已分配的可用連接,就嘗試從連接池獲取。(連接池稍后詳細(xì)講解)  
  11.       if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {  
  12.         result = transmitter.connection;  
  13.       }  
  14.     }  
  15.   }  
  16.   synchronized (connectionPool) {  
  17.     if (newRouteSelection) {  
  18.       //3. 現(xiàn)在有了IP地址,再次嘗試從連接池獲取??赡軙?yàn)檫B接合并而匹配。(這里傳入了routes,上面的傳的null)  
  19.       routes = routeSelection.getAll(); 
  20.      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {  
  21.         foundPooledConnection = true 
  22.         result = transmitter.connection;  
  23.       }  
  24.     }  
  25.   // 4.第二次沒成功,就把新建的連接,進(jìn)行TCP + TLS 握手,與服務(wù)端建立連接. 是阻塞操作  
  26.   result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,  
  27.       connectionRetryEnabled, call, eventListener);  
  28.   synchronized (connectionPool) {  
  29.     // 5. 最后一次嘗試從連接池獲取,注意最后一個參數(shù)為true,即要求 多路復(fù)用(http2.0)  
  30.     //意思是,如果本次是http2.0,那么為了保證 多路復(fù)用性,(因?yàn)樯厦娴奈帐植僮鞑皇蔷€程安全)會再次確認(rèn)連接池中此時是否已有同樣連接  
  31.     if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {  
  32.       // 如果獲取到,就關(guān)閉我們創(chuàng)建里的連接,返回獲取的連接  
  33.       result = transmitter.connection;  
  34.     } else {  
  35.       //最后一次嘗試也沒有的話,就把剛剛新建的連接存入連接池  
  36.       connectionPool.put(result);  
  37.     }  
  38.   }  
  39.   return result;  

上面精簡了部分代碼,可以看出,連接攔截器使用了5種方法查找連接:

  1.  首先會嘗試使用 已給請求分配的連接。(已分配連接的情況例如重定向時的再次請求,說明上次已經(jīng)有了連接)
  2.  若沒有已分配的可用連接,就嘗試從連接池中 匹配獲取。因?yàn)榇藭r沒有路由信息,所以匹配條件:address 一致—— host、port、代理等一致,且匹配的連接可以接受新的請求。
  3.  若從連接池沒有獲取到,則傳入 routes 再次嘗試獲取,這主要是針對 Http2.0 的一個操作, Http2.0 可以復(fù)用 square.com 與 square.ca 的連接
  4.  若第二次也沒有獲取到,就創(chuàng)建 RealConnection 實(shí)例,進(jìn)行 TCP + TLS 握手,與服務(wù)端建立連接。
  5.  此時為了確保 Http2.0 連接的多路復(fù)用性,會第三次從連接池匹配。因?yàn)樾陆⒌倪B接的握手過程是非線程安全的,所以此時可能連接池新存入了相同的連接。
  6.  第三次若匹配到,就使用已有連接,釋放剛剛新建的連接;若未匹配到,則把新連接存入連接池并返回。

以上就是連接攔截器嘗試復(fù)用連接的操作,流程圖如下:

6. OKHttp空閑連接如何清除?

上面說到我們會建立一個 TCP 連接池,但如果沒有任務(wù)了,空閑的連接也應(yīng)該及時清除,OKHttp 是如何做到的呢? 

  1. # RealConnectionPool  
  2.  private val cleanupQueue: TaskQueue = taskRunner.newQueue()  
  3.  private val cleanupTask = object : Task("$okHttpName ConnectionPool") {  
  4.    override fun runOnce(): Long = cleanup(System.nanoTime())  
  5.  }  
  6.  long cleanup(long now) {  
  7.    int inUseConnectionCount = 0;//正在使用的連接數(shù)  
  8.    int idleConnectionCount = 0;//空閑連接數(shù)  
  9.    RealConnection longestIdleConnection = null;//空閑時間最長的連接  
  10.    long longestIdleDurationNs = Long.MIN_VALUE;//最長的空閑時間 
  11.    //遍歷連接:找到待清理的連接, 找到下一次要清理的時間(還未到最大空閑時間)  
  12.    synchronized (this) {  
  13.      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {  
  14.        RealConnection connection = i.next();  
  15.        //若連接正在使用,continue,正在使用連接數(shù)+1  
  16.        if (pruneAndGetAllocationCount(connection, now) > 0) {  
  17.          inUseConnectionCount++;  
  18.          continue;  
  19.        }  
  20.  //空閑連接數(shù)+1  
  21.        idleConnectionCount++;  
  22.        // 賦值最長的空閑時間和對應(yīng)連接  
  23.        long idleDurationNs = now - connection.idleAtNanos;  
  24.        if (idleDurationNs > longestIdleDurationNs) {  
  25.          longestIdleDurationNs = idleDurationNs 
  26.          longestIdleConnection = connection 
  27.        }  
  28.      }  
  29.   //若最長的空閑時間大于5分鐘 或 空閑數(shù) 大于5,就移除并關(guān)閉這個連接  
  30.      if (longestIdleDurationNs >= this.keepAliveDurationNs  
  31.          || idleConnectionCount > this.maxIdleConnections) {  
  32.        connections.remove(longestIdleConnection);  
  33.      } else if (idleConnectionCount > 0) {  
  34.        // else,就返回 還剩多久到達(dá)5分鐘,然后wait這個時間再來清理  
  35.        return keepAliveDurationNs - longestIdleDurationNs;  
  36.      } else if (inUseConnectionCount > 0) {  
  37.        //連接沒有空閑的,就5分鐘后再嘗試清理.  
  38.        return keepAliveDurationNs;  
  39.      } else {  
  40.        // 沒有連接,不清理  
  41.        cleanupRunning = false 
  42.        return -1;  
  43.      }  
  44.    }  
  45. //關(guān)閉移除的連接  
  46.    closeQuietly(longestIdleConnection.socket());  
  47.    //關(guān)閉移除后 立刻 進(jìn)行下一次的 嘗試清理  
  48.    return 0;  
  49.  } 

思路還是很清晰的:

    1.  在將連接加入連接池時就會啟動定時任務(wù)

    2.  有空閑連接的話,如果最長的空閑時間大于5分鐘 或 空閑數(shù) 大于5,就移除關(guān)閉這個最長空閑連接;如果 空閑數(shù) 不大于5 且 最長的空閑時間不大于5分鐘,就返回到5分鐘的剩余時間,然后等待這個時間再來清理。

    3.  沒有空閑連接就等5分鐘后再嘗試清理。

    4.  沒有連接不清理。

流程如下圖所示:

7. OKHttp有哪些優(yōu)點(diǎn)?

  •  使用簡單,在設(shè)計(jì)時使用了外觀模式,將整個系統(tǒng)的復(fù)雜性給隱藏起來,將子系統(tǒng)接口通過一個客戶端 OkHttpClient 統(tǒng)一暴露出來。
  •  擴(kuò)展性強(qiáng),可以通過自定義應(yīng)用攔截器與網(wǎng)絡(luò)攔截器,完成用戶各種自定義的需求
  •  功能強(qiáng)大,支持 Spdy、Http1.X、Http2、以及 WebSocket 等多種協(xié)議
  •  通過連接池復(fù)用底層 TCP(Socket),減少請求延時
  •  無縫的支持 GZIP 減少數(shù)據(jù)流量
  •  支持?jǐn)?shù)據(jù)緩存,減少重復(fù)的網(wǎng)絡(luò)請求
  •  支持請求失敗自動重試主機(jī)的其他 ip,自動重定向

8. OKHttp框架中用到了哪些設(shè)計(jì)模式?

  •  構(gòu)建者模式:OkHttpClient 與 Request 的構(gòu)建都用到了構(gòu)建者模式
  •  外觀模式:OkHttp使用了外觀模式,將整個系統(tǒng)的復(fù)雜性給隱藏起來,將子系統(tǒng)接口通過一個客戶端 OkHttpClient 統(tǒng)一暴露出來
  •  責(zé)任鏈模式: OKHttp 的核心就是責(zé)任鏈模式,通過5個默認(rèn)攔截器構(gòu)成的責(zé)任鏈完成請求的配置
  •  享元模式: 享元模式的核心即池中復(fù)用, OKHttp 復(fù)用 TCP 連接時用到了連接池,同時在異步請求中也用到了線程池  

 

責(zé)任編輯:龐桂玉 來源: 安卓開發(fā)精選
相關(guān)推薦

2022-02-14 08:25:50

Go語言面試

2010-05-13 10:40:56

富士康

2024-08-07 13:40:00

2021-07-12 07:08:52

TCP協(xié)議面試

2020-09-30 18:19:27

RedisJava面試

2014-12-22 11:28:01

2022-01-05 09:55:26

asynawait前端

2014-12-21 08:49:53

2022-05-14 21:19:22

ThreadLocaJDKsynchroniz

2019-12-19 09:23:45

Java多線程數(shù)據(jù)

2022-02-28 07:01:22

線程中斷interrupt

2022-04-11 07:40:45

synchroniz靜態(tài)方法程序

2022-07-11 07:10:48

HTTP協(xié)議類型

2015-04-07 16:09:28

鋼七連華為

2020-07-28 00:58:20

IP地址子網(wǎng)TCP

2021-12-01 11:50:50

HashMap面試Java

2022-06-06 07:35:26

MySQLInnoDBMyISAM

2022-05-05 07:38:32

volatilJava并發(fā)

2022-04-20 07:47:00

notify喚醒線程JVM

2022-07-27 07:36:01

TCP可靠性
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號