国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

面試突擊:OkHttp 原理八連問

移動開發 Android
OkHttp 可以說是 Android 開發中最常見的網絡請求框架,OkHttp 使用方便,擴展性強,功能強大,OKHttp 源碼與原理也是面試中的常客。

[[434054]]

OkHttp 可以說是 Android 開發中最常見的網絡請求框架,OkHttp 使用方便,擴展性強,功能強大,OKHttp 源碼與原理也是面試中的常客。

但是 OKHttp 的源碼內容比較多,想要學習它的源碼往往千頭萬緒,一時抓不住重點. 本文從幾個問題出發梳理 OKHttp 相關知識點,以便快速構建 OKHttp 知識體,本文主要包括以下內容

  1.   OKHttp 請求的整體流程是怎樣的?
  2.   OKHttp 分發器是怎樣工作的?
  3.   OKHttp 攔截器是如何工作的?
  4.   應用攔截器和網絡攔截器有什么區別?
  5.   OKHttp 如何復用 TCP 連接?
  6.   OKHttp 空閑連接如何清除?
  7.   OKHttp 有哪些優點?
  8.   OKHttp 框架中用到了哪些設計模式?

1. OKHttp請求整體流程介紹

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

  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,但是框架內部會進行大量的邏輯處理。

所有網絡請求的邏輯大部分集中在攔截器中,但是在進入攔截器之前還需要依靠分發器來調配請求任務。關于分發器與攔截器,我們在這里先簡單介紹下,后續會有更加詳細的講解

  1.  分發器:內部維護隊列與線程池,完成請求調配;
  2.  攔截器:五大默認攔截器完成整個請求過程。

整個網絡請求過程大致如上所示

  1.  通過建造者模式構建 OKHttpClient 與 Request
  2.  OKHttpClient 通過 newCall 發起一個新的請求
  3.  通過分發器維護請求隊列與線程池,完成請求調配
  4.  通過五大默認攔截器完成請求重試,緩存處理,建立連接等一系列操作
  5.  得到網絡請求結果

2. OKHttp分發器是怎樣工作的?

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

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

同步請求 

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

因為同步請求不需要線程池,也不存在任何限制。所以分發器僅做一下記錄。后續按照加入隊列的順序同步請求即可

異步請求 

  1. synchronized void enqueue(AsyncCall call) {  
  2.  //請求數最大不超過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.  }  

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

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

經過上面分發器的任務分發,下面就要利用攔截器開始一系列配置了 

  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 ,責任鏈的構建與處理其實就是在這個方法里面 

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

如上所示,構建了一個 OkHttp 攔截器的責任鏈

責任鏈,顧名思義,就是用來處理相關事務責任的一條執行鏈,執行鏈上有多個節點,每個節點都有機會(條件匹配)處理請求事務,如果某個節點處理完了就可以根據實際業務需求傳遞給下一個節點繼續處理或者返回處理完畢。

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

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

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

4. 應用攔截器和網絡攔截器有什么區別?

從整個責任鏈路來看,應用攔截器是最先執行的攔截器,也就是用戶自己設置 request 屬性后的原始請求,而網絡攔截器位于 ConnectInterceptor 和 CallServerInterceptor 之間,此時網絡鏈路已經準備好,只等待發送請求數據。它們主要有以下區別

    1.  首先,應用攔截器在 RetryAndFollowUpInterceptor 和 CacheInterceptor 之前,所以一旦發生錯誤重試或者網絡重定向,網絡攔截器可能執行多次,因為相當于進行了二次請求,但是應用攔截器永遠只會觸發一次。另外如果在 CacheInterceptor 中命中了緩存就不需要走網絡請求了,因此會存在短路網絡攔截器的情況。

    2.  其次,除了 CallServerInterceptor 之外,每個攔截器都應該至少調用一次 realChain.proceed 方法。實際上在應用攔截器這層可以多次調用 proceed 方法(本地異常重試)或者不調用 proceed 方法(中斷),但是網絡攔截器這層連接已經準備好,可且僅可調用一次 proceed 方法。

    3.  最后,從使用場景看,應用攔截器因為只會調用一次,通常用于統計客戶端的網絡請求發起情況;而網絡攔截器一次調用代表了一定會發起一次網絡通信,因此通常可用于統計網絡鏈路上傳輸的數據。

5. OKHttp如何復用TCP連接?

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

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

  1. # ExchangeFinder  
  2. //為承載新的數據流 尋找 連接。尋找順序是 已分配的連接、連接池、新建連接  
  3. private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,  
  4.     int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {  
  5.   synchronized (connectionPool) {  
  6.     // 1.嘗試使用 已給數據流分配的連接.(例如重定向請求時,可以復用上次請求的連接)  
  7.     releasedConnection = transmitter.connection;  
  8.     result = transmitter.connection;  
  9.     if (result == null) {  
  10.       // 2. 沒有已分配的可用連接,就嘗試從連接池獲取。(連接池稍后詳細講解)  
  11.       if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {  
  12.         result = transmitter.connection;  
  13.       }  
  14.     }  
  15.   }  
  16.   synchronized (connectionPool) {  
  17.     if (newRouteSelection) {  
  18.       //3. 現在有了IP地址,再次嘗試從連接池獲取。可能會因為連接合并而匹配。(這里傳入了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.第二次沒成功,就把新建的連接,進行TCP + TLS 握手,與服務端建立連接. 是阻塞操作  
  26.   result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,  
  27.       connectionRetryEnabled, call, eventListener);  
  28.   synchronized (connectionPool) {  
  29.     // 5. 最后一次嘗試從連接池獲取,注意最后一個參數為true,即要求 多路復用(http2.0)  
  30.     //意思是,如果本次是http2.0,那么為了保證 多路復用性,(因為上面的握手操作不是線程安全)會再次確認連接池中此時是否已有同樣連接  
  31.     if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {  
  32.       // 如果獲取到,就關閉我們創建里的連接,返回獲取的連接  
  33.       result = transmitter.connection;  
  34.     } else {  
  35.       //最后一次嘗試也沒有的話,就把剛剛新建的連接存入連接池  
  36.       connectionPool.put(result);  
  37.     }  
  38.   }  
  39.   return result;  

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

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

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

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

上面說到我們會建立一個 TCP 連接池,但如果沒有任務了,空閑的連接也應該及時清除,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;//正在使用的連接數  
  8.    int idleConnectionCount = 0;//空閑連接數  
  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,正在使用連接數+1  
  16.        if (pruneAndGetAllocationCount(connection, now) > 0) {  
  17.          inUseConnectionCount++;  
  18.          continue;  
  19.        }  
  20.  //空閑連接數+1  
  21.        idleConnectionCount++;  
  22.        // 賦值最長的空閑時間和對應連接  
  23.        long idleDurationNs = now - connection.idleAtNanos;  
  24.        if (idleDurationNs > longestIdleDurationNs) {  
  25.          longestIdleDurationNs = idleDurationNs 
  26.          longestIdleConnection = connection 
  27.        }  
  28.      }  
  29.   //若最長的空閑時間大于5分鐘 或 空閑數 大于5,就移除并關閉這個連接  
  30.      if (longestIdleDurationNs >= this.keepAliveDurationNs  
  31.          || idleConnectionCount > this.maxIdleConnections) {  
  32.        connections.remove(longestIdleConnection);  
  33.      } else if (idleConnectionCount > 0) {  
  34.        // else,就返回 還剩多久到達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. //關閉移除的連接  
  46.    closeQuietly(longestIdleConnection.socket());  
  47.    //關閉移除后 立刻 進行下一次的 嘗試清理  
  48.    return 0;  
  49.  } 

思路還是很清晰的:

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

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

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

    4.  沒有連接不清理。

流程如下圖所示:

7. OKHttp有哪些優點?

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

8. OKHttp框架中用到了哪些設計模式?

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

 

責任編輯:龐桂玉 來源: 安卓開發精選
相關推薦

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協議面試

2014-12-22 11:28:01

2020-09-30 18:19:27

RedisJava面試

2014-12-21 08:49:53

2022-01-05 09:55:26

asynawait前端

2022-02-28 07:01:22

線程中斷interrupt

2022-07-11 07:10:48

HTTP協議類型

2022-04-11 07:40:45

synchroniz靜態方法程序

2022-05-14 21:19:22

ThreadLocaJDKsynchroniz

2019-12-19 09:23:45

Java多線程數據

2015-04-07 16:09:28

鋼七連華為

2022-05-05 07:38:32

volatilJava并發

2022-06-06 07:35:26

MySQLInnoDBMyISAM

2022-04-20 07:47:00

notify喚醒線程JVM

2022-07-06 07:35:19

group byMySQL

2022-07-27 07:36:01

TCP可靠性

2020-07-28 00:58:20

IP地址子網TCP
點贊
收藏

51CTO技術棧公眾號

亚洲精品无码久久久久久| 国产不卡免费视频| 日韩精品一区二区三区在线播放| 人妻少妇精品无码专区二区| 久久99国产成人小视频| 亚洲精品aⅴ中文字幕乱码| 369你懂的电影天堂| 一本久道综合久久精品| 日韩美女视频中文字幕| 国产无遮挡裸体视频在线观看| 婷婷丁香久久五月婷婷| 成年人网站大全| 97国产成人高清在线观看| 色婷婷综合在线| 亚洲国产精品久久久久久女王| 国产精品诱惑| 日韩精品一区二区三区中文不卡 | 久久成人av少妇免费| 国产v亚洲v天堂无码| 日韩电影免费观看高清完整版| 亚洲一区中文在线| 亚洲人成77777| 成人看片毛片免费播放器| 国产在线视频一区二区三区| 久久久伊人欧美| 蝌蚪视频在线播放| 老司机久久99久久精品播放免费| 欧美www在线| av在线免费观看网站| 亚洲综合久久久| 亚洲精品一区二区三区樱花| 久久精品在线| 日本道色综合久久影院| 黄色视屏网站在线免费观看| 精品在线观看视频| 91精品久久久久久久久不口人| 成人爽a毛片免费啪啪| 亚洲曰韩产成在线| 亚洲人成网站在线观看播放 | 色屁屁一区二区| 国产精品第2页| 久久99精品久久| 欧美精品一区二区三区中文字幕| 欧美精品免费视频| 可以看毛片的网址| 成人在线视频一区二区| 日本福利视频在线| 亚洲欧美另类小说| 日本不卡免费播放| 91麻豆精品久久久久蜜臀| 凹凸日日摸日日碰夜夜爽1| 日韩成人一区二区三区在线观看| 蜜桃视频在线观看91| 国产成人免费xxxxxxxx| 快播亚洲色图| 欧美黑人巨大videos精品| 在线成人激情视频| 东凛在线观看| 成人av免费在线播放| 免费在线观看91| 妖精视频一区二区三区免费观看| 欧美伊人精品成人久久综合97| 成人性做爰片免费视频| 成人高潮a毛片免费观看网站| 日韩专区中文字幕| 国产一区二区三区黄网站| 欧美一级片久久久久久久| 国产精品成人av| 亚洲成人精品电影在线观看| a在线欧美一区| 4480yy私人影院高清不卡| 9191国产精品| 自拍偷拍欧美日韩| 97久久精品午夜一区二区| 久久精品国产一区二区三区免费看| 日本一本二本在线观看| 亚洲一区二区av电影| 影音先锋在线视频| 欧美激情视频给我| 麻豆网站在线| 国产精品系列在线| 春日野结衣av| 99精品视频免费全部在线| 亚洲一区二区久久久久久久| 欧美三级电影网址| 欧美大片免费观看| 给我免费播放日韩视频| 不卡av电影在线观看| 免费成人动漫| 国产精品久久久亚洲| 轻轻草成人在线| 成人福利视频在线观看| 精品中文字幕一区二区三区四区 | 国产乱码精品一区二三赶尸艳谈| 日韩av综合中文字幕| 国产传媒av在线| 国产香蕉一区二区三区在线视频 | 国产精品免费成人| 欧美丝袜美女中出在线| 99久久久国产精品免费调教网站| 国产伦精品一区二区三区精品视频| 国产高清一区日本| 国产视频精选在线| 7777免费精品视频| 九色综合国产一区二区三区| 小香蕉视频在线| 色悠悠久久88| 久久综合影视| 中文字幕网站视频在线| 欧美成人免费一级人片100| 神马久久av| 狠狠97人人婷婷五月| 亚洲国产精品热久久| 欧美精品一卡| 中文在线a√在线8| 久久成人综合视频| 国产成人在线视频网址| 羞羞的视频在线看| 成人h片在线播放免费网站| 国产精品一二三| caopon在线免费视频| 国产精品一二三视频| 国产欧美综合在线观看第十页| 直接在线观看的三级网址| 91麻豆国产精品| 亚洲综合男人的天堂| 欧美第一在线视频| 国产精品网站免费| 亚洲精品videossex少妇| 亚洲手机在线| 欧美日韩国产综合视频| 日本久久中文字幕| 国产网红主播福利一区二区| 一区二区三区伦理| 久久国产精品 国产精品| 在线观看av一区二区| 国产aⅴ精品一区二区三区久久| 亚洲欧美国产中文| 欧美高清性猛交| 2023国产精品视频| 日本高清精品| 成人3d动漫一区二区三区| 欧美精品在线播放| 中文av一区二区| 狼人精品一区二区三区在线| 久久成人免费观看| 自拍偷拍亚洲欧美| 91网站视频在线观看| 先锋影音一区二区| 91在线短视频| 国产高清亚洲一区| 97人人做人人爽香蕉精品| 国产性xxxx18免费观看视频| 欧美激情综合亚洲一二区| 国产精品伦一区| 日韩欧美电影| 2024短剧网剧在线观看| 一区二区三区四区视频在线| 俺去亚洲欧洲欧美日韩| 国产精品久久久久9999吃药| 亚洲国产一区二区在线观看 | 黄色精品一区二区| 美女精品在线| 日韩不卡在线| 毛片手机在线观看| 国严精品久久久久久亚洲影视 | 久久综合网络一区二区| 久久久久久久性潮| 99爱视频在线观看| 久热国产精品视频一区二区三区| 亚洲摸下面视频| 亚洲欧洲性图库| 国产精品久久久免费| 毛片免费看不卡网站| 嫩草av久久伊人妇女超级a| 国产福利成人在线| 91精品婷婷国产综合久久竹菊| 91亚洲精华国产精华精华液| 日韩激情图片| 中文字幕资源网在线观看免费| 日本久久精品一区二区| 国产欧美一区二区三区另类精品 | 超碰成人在线免费| 国内精品一区视频| 黄色影院一级片| 日韩免费观看视频| 日韩欧美高清dvd碟片| 成人丝袜视频网| 999精品色在线播放| 欧美成人免费电影| 亚洲第一se情网站| 国产亚洲黄色片| 成人av资源在线播放| 亚洲欧洲自拍偷拍| 日本韩国欧美在线| 久久久亚洲午夜电影| 麻豆视频观看网址久久| 精品视频亚洲| 激情亚洲影院在线观看| 二区在线观看|