日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OkHttp源码深度解析

發布時間:2024/1/23 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OkHttp源码深度解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本文來自OPPO互聯網基礎技術團隊,轉載請注名作者。同時歡迎關注我們的公眾號:OPPO_tech,與你分享OPPO前沿互聯網技術及活動。

OkHttp應該是目前Android平臺上使用最為廣泛的開源網絡庫了,Android 在6.0之后也將內部的HttpUrlConnection的默認實現替換成了OkHttp。

大部分開發的同學可能都有接觸過OkHttp的源碼,但很少有比較全面的閱讀和了解的,目前網絡上的大部分源碼解析文章也都是點到為止,并且大段的貼源碼,這種解析方式是我無法認可的,因此才有了想要重新寫一篇解析OkHttp源碼的想法。

這篇文章的目的,一個是要比較全面的介紹OkHttp源碼,另一個是要盡量避免大段的貼出源碼,涉及到源碼的部分,會盡量通過調用關系圖來展示。

本篇選用的OkHttp源碼是目前最新的4.4.0版本,面向的讀者也是有一定使用基礎的Android開發同學。

OkHttp的源碼可以從github上下載到(https://github.com/square/OkHttp)。

直奔主題,文章將從一下幾個方面開始來拆解OkHttp的源碼:

  • 整體結構
  • 攔截器
  • 任務隊列
  • 連接復用和連接池
  • 1. 從一個例子出發

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

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url("https://www.google.com").build();
    Response response = client.newCall(request).execute();
    return response.body().string();

    這一段代碼就是日常使用OkHttp最常見的用法,跟進源碼后,可以得到一張更為詳細的流程圖,通過這張圖來看下內部的邏輯是如何流動的。

    涉及到了幾個核心類,我們一個個來看下。

  • OkHttpClient
  • Request 和 Response
  • RealCall
  • OkHttpClient:這個是整個OkHttp的核心管理類,所有的內部邏輯和對象歸OkHttpClient統一來管理,它通過Builder構造器生成,構造參數和類成員很多,這里先不做具體的分析。

    Request 和Response:Request是我們發送請求封裝類,內部有url, header , method,body等常見的參數,Response是請求的結果,包含code, message, header,body ;這兩個類的定義是完全符合Http協議所定義的請求內容和響應內容。

    RealCall?:負責請求的調度(同步的話走當前線程發送請求,異步的話則使用OkHttp內部的線程池進行);同時負責構造內部邏輯責任鏈,并執行責任鏈相關的邏輯,直到獲取結果。雖然OkHttpClient是整個OkHttp的核心管理類,但是真正發出請求并且組織邏輯的是RealCall類,它同時肩負了調度和責任鏈組織的兩大重任,接下來我們來著重分析下RealCall類的邏輯。

    RealCal類的源碼地址:https://github.com/square/OkH...

    RealCall類并不復雜,有兩個最重要的方法,execute() 和 enqueue(),一個是處理同步請求,一個是處理異步請求。跟進enqueue的源碼后發現,它只是通過異步線程和callback做了一個異步調用的封裝,最終邏輯還是會調用到execute()這個方法,然后調用了getResponseWithInterceptorChain()獲得請求結果。

    看來是 getResponseWithInterceptorChain() 方法承載了整個請求的核心邏輯,那么只需要把這個方法分析清楚了,真個OkHttp的請求流程就大體搞明白了。既然這么重要的方法,還是不能免俗的貼下完整的源碼。

    val interceptors = mutableListOf<Interceptor>()interceptors += client.interceptorsinterceptors += RetryAndFollowUpInterceptor(client)interceptors += BridgeInterceptor(client.cookieJar)interceptors += CacheInterceptor(client.cache)interceptors += ConnectInterceptorif (!forWebSocket) {interceptors += client.networkInterceptors}interceptors += CallServerInterceptor(forWebSocket)val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)var calledNoMoreExchanges = falsetry {val response = chain.proceed(originalRequest)............

    從源碼可以看到,即使是 getResponseWithInterceptorChain() 方法的邏輯其實也很簡單,它生成了一個Interceptors攔截器的List列表,按順序依次將:

    • client.Interceptors
    • RetryAndFollowUpInterceptor,
    • BridgeInterceptor
    • CacheInterceptor
    • ConnectInterceptor
    • client.networkInterceptors
    • CallServerInterceptor

    這些成員添加到這個List中,然后創建了一個叫RealInterceptorChain的類,最后的Response就是通過chain.proceed獲取到的。

    通過進一步分析RealInterceptorChain和Interceptors,我們得到了一個結論,OkHttp將整個請求的復雜邏輯切成了一個一個的獨立模塊并命名為攔截器(Interceptor),通過責任鏈的設計模式串聯到了一起,最終完成請求獲取響應結果。

    具體這些攔截器是如何串聯,每個攔截器都有什么功能,下面的內容會作更詳細的分析。

    2. OkHttp的核心:攔截器

    我們已經知道了OkHttp的核心邏輯就是一堆攔截器,那么它們是如何構造并關聯到一起的呢?這里就要來分析RealInterceptorChain這個類了。

    通過前面的分析可知,RealCall將Interceptors一個一個添加到List之后 ,就構造生成了一個RealInterceptorChain對象,并調用chain.proceed獲得響應結果。那么就來分析下chain.proceed這個方法到底干了啥。為了不讓篇幅太長,這里就不貼出源碼內容,僅給出分析后的結論,大家對照源碼可以很快看懂。

    RealInterceptorChain的源碼:https://github.com/square/OkH...

    根據對RealInterceptorChain的源碼解析,可得到如下示意圖(省略了部分攔截器):

    結合源碼和該示意圖,可以得到如下結論:

  • 攔截器按照添加順序依次執行
  • 攔截器的執行從RealInterceptorChain.proceed()開始,進入到第一個攔截器的執行邏輯
  • 每個攔截器在執行之前,會將剩余尚未執行的攔截器組成新的RealInterceptorChain
  • 攔截器的邏輯被新的責任鏈調用next.proceed()切分為start、next.proceed、end這三個部分依次執行
  • next.proceed() 所代表的其實就是剩余所有攔截器的執行邏輯
  • 所有攔截器最終形成一個層層內嵌的嵌套結構
  • 了解了上面攔截器的構造過程,我們再來一個個的分析每個攔截器的功能和作用。

    從這張局部圖來看,總共添加了五個攔截器(不包含自定義的攔截器如client.interceptors和client.networkInterceptors,這兩個后面再解釋)。

    先來大概的了解下每一個攔截器的作用

    • retryAndFollowUpInterceptor——失敗和重定向攔截器
    • BridgeInterceptor——封裝request和response攔截器
    • CacheInterceptor——緩存相關的過濾器,負責讀取緩存直接返回、更新緩存
    • ConnectInterceptor——連接服務,負責和服務器建立連接 這里才是真正的請求網絡
    • CallServerInterceptor——執行流操作(寫出請求體、獲得響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據 進行http請求報文的封裝與請求報文的解析

    下面就來一個個的攔截器進行分析。

    2.1 RetryAndFollowUpInterceptor

    源碼地址:https://github.com/square/OkH...

    根據源碼的邏輯走向,直接畫出對應的流程圖(這段邏輯在RetryAndFollowUpInterceptor的intercept()方法內部):

    從上圖中可以看出,RetryAndFollowUpInterceptor開啟了一個while(true)的循環,并在循環內部完成兩個重要的判定,如圖中的藍色方框:

  • 當請求內部拋出異常時,判定是否需要重試
  • 當響應結果是3xx重定向時,構建新的請求并發送請求
  • 重試的邏輯相對復雜,有如下的判定邏輯(具體代碼在RetryAndFollowUpInterceptor類的recover方法):

    • 規則1: client的retryOnConnectionFailure參數設置為false,不進行重試
    • 規則2: 請求的body已經發出,不進行重試
    • 規則3: 特殊的異常類型不進行重試(如ProtocolException,SSLHandshakeException等)
    • 規則4: 沒有更多的route(包含proxy和inetaddress),不進行重試

    前面這四條規則都不符合的條件下,則會重試當前請求。重定向的邏輯則相對簡單,這里就不深入了。

    2.2 Interceptors和NetworkInterceptors的區別

    前面提到,在OkHttpClient.Builder的構造方法有兩個參數,使用者可以通過addInterceptor 和 addNetworkdInterceptor 添加自定義的攔截器,分析完 RetryAndFollowUpInterceptor 我們就可以知道這兩種自動攔截器的區別了。

    從前面添加攔截器的順序可以知道 Interceptors 和 networkInterceptors 剛好一個在 RetryAndFollowUpInterceptor 的前面,一個在后面。

    結合前面的責任鏈調用圖可以分析出來,假如一個請求在 RetryAndFollowUpInterceptor 這個攔截器內部重試或者重定向了 N 次,那么其內部嵌套的所有攔截器也會被調用N次,同樣 networkInterceptors 自定義的攔截器也會被調用 N 次。而相對的 Interceptors 則一個請求只會調用一次,所以在OkHttp的內部也將其稱之為 Application Interceptor。

    2.3 BridgeInterceptor 和 CacheInterceptor

    BridageInterceptor 攔截器的功能如下:

  • 負責把用戶構造的請求轉換為發送到服務器的請求 、把服務器返回的響應轉換為用戶友好的響應,是從應用程序代碼到網絡代碼的橋梁
  • 設置內容長度,內容編碼
  • 設置gzip壓縮,并在接收到內容后進行解壓。省去了應用層處理數據解壓的麻煩
  • 添加cookie
  • 設置其他報頭,如User-Agent,Host,Keep-alive等。其中Keep-Alive是實現連接復用的必要步驟
  • CacheInterceptor 攔截器的邏輯流程如下:

  • 通過Request嘗試到Cache中拿緩存,當然前提是OkHttpClient中配置了緩存,默認是不支持的。
  • 根據response,time,request創建一個緩存策略,用于判斷怎樣使用緩存。
  • 如果緩存策略中設置禁止使用網絡,并且緩存又為空,則構建一個Response直接返回,注意返回碼=504
  • 緩存策略中設置不使用網絡,但是又緩存,直接返回緩存
  • 接著走后續過濾器的流程,chain.proceed(networkRequest)
  • 當緩存存在的時候,如果網絡返回的Resposne為304,則使用緩存的Resposne。
  • 構建網絡請求的Resposne
  • 當在OkHttpClient中配置了緩存,則將這個Resposne緩存起來。
  • 緩存起來的步驟也是先緩存header,再緩存body。
  • 返回Resposne
  • 接下來的兩個應該是所有內部攔截器里最重要的兩個了,一個負責處理Dns和Socket連接,另一個則負責Http請求體的發送。

    2.4 ConnectInterceptor

    上面已經提到了,connectInterceptor應該是最重要的攔截器之一了,它同時負責了Dns解析和Socket連接(包括tls連接)。

    源碼地址:https://github.com/square/OkH...

    這個類本身很簡單,從源碼來看,關鍵的代碼只有一句。

    val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)

    從Transmitter獲得了一個新的ExChange的對象,這句簡單的代碼仔細跟進去以后,會發現其實埋藏了非常多的邏輯,涉及整個網絡連接建立的過程,其中包括dns過程和socket連接的過程,這里我們通過兩個圖來了解下整個網絡連接的過程。

    先來看方法調用的時序圖,梳理出關鍵步驟:

  • ConnectInterceptor調用transmitter.newExchange
  • Transmitter先調用ExchangeFinder的find()獲得ExchangeCodec
  • ExchangeFinder調用自身的findHealthConnectio獲得RealConnection
  • ExchangeFinder通過剛才獲取的RealConnection的codec()方法獲得ExchangeCodec
  • Transmitter獲取到了ExchangeCodec,然后new了一個ExChange,將剛才的ExchangeCodec包含在內
  • 通過剛才的5步,最終Connectinterceptor通過Transmitter獲取到了一個Exchange的類,這個類有兩個實現,一個是Http1ExchangeCodec,一個是Http2Exchangecodec,分別對應的是Http1協議和Http2協議。

    那么獲取到Exchange類有什么用呢?再來看這幾個類的關系圖,如下:

    從上面可以看到,前面獲得的Exchange類里面包含了ExchangeCodec對象,而這個對象里面又包含了一個RealConnection對象,RealConnection的屬性成員有socket、handlShake、protocol等,可見它應該是一個Socket連接的包裝類,而ExchangeCode對象是對RealConnection操作(writeRequestHeader、readResposneHeader)的封裝。

    通過這兩個圖可以很清晰的知道,最終獲得的是一個已經建立連接的Socket對象,也就是說,在ConnectInterceptor內部已經完成了socket連接,那么具體是哪一步完成的呢?

    看上面的時序圖,可以知道,獲得RealConnection的ExchangeFinder調用的findHealthConnection()方法,因此,socket連接的獲取和建立都是在這里完成的。

    同樣,在socket進行連接之前,其實還有一個dns的過程,也是隱含在findHealthConnection 里的內部邏輯,詳細的過程在后面DNS的過程再進行分析,這里ConnectionInterceptor的任務已經完成了。

    另外還需要注意的一點是,在執行完ConnectInterceptor之后,其實添加了自定義的網絡攔截器networkInterceptors,按照順序執行的規定,所有的networkInterceptor執行執行,socket連接其實已經建立了,可以通過realChain拿到socket做一些事情了,這也就是為什么稱之為network Interceptor的原因。

    2.5 CallServerInterceptor

    CalllServerInterceptor是最后一個攔截器了,前面的攔截器已經完成了socket連接和tls連接,那么這一步就是傳輸http的頭部和body數據了。

    CallServerInterceptor源碼:https://github.com/square/OkH...

    CallServerInterceptor由以下步驟組成:

  • 向服務器發送 request header
  • 如果有 request body,就向服務器發送
  • 讀取 response header,先構造一個 Response 對象
  • 如果有 response body,就在 3 的基礎上加上 body 構造一個新的 Response 對象
  • 這里我們可以看到,核心工作都由 HttpCodec 對象完成,而 HttpCodec 實際上利用的是 Okio,而 Okio 實際上還是用的 Socket,只不過一層套一層,層數有點多。

    3. 整體架構

    至此為止,所有的攔截器都講完了,我們已經知道了一個完整的請求流程是如何發生的。那么這個時候再來看OkHttp的架構圖就比較清晰了

    整個OkHttp的架構縱向來看就是五個內部攔截器,橫向來看被切分成了幾個部分,而縱向的攔截器就是通過對橫向分層的調用來完成整個請求過程,從這兩個方面來把握和理解OkHttp就比較全面了。

    針對橫向的部分將在接下來的部分進行詳細分析。

    3.1 連接復用,DNS和Socket的連接

    通過前面的分析知道,Socket連接和Dns過程都是在ConnecInterceptor中通過Transmitter和ExchangeFinder來完成的,而在前面的時序圖中可以看到,最終建立Socket連接的方法是通過ExchangeFinder的findConnection來完成的,可以說一切秘密都是findConnection方法中。

    因此接下來詳細解析下findConnection(),這里貼出源碼和注釋。

    synchronized(connectionPool) {//前面有一大段判定當前的conencection是否需要釋放,先刪除.....if (result == null) {// 1, 第一次嘗試從緩沖池里面獲取RealConnection(Socket的包裝類)if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {foundPooledConnection = trueresult = transmitter.connection} else if (nextRouteToTry != null) {//2, 如果緩沖池中沒有,則看看有沒有下一個Route可以嘗試,這里只有重試的情況會走進來selectedRoute = nextRouteToTrynextRouteToTry = null} else if (retryCurrentRoute()) {//3,如果已經設置了使用當前Route重試,那么會繼續使用當前的RouteselectedRoute = transmitter.connection!!.route()}}}if (result != null) {// 4,如果前面發現ConnectionPool或者transmiter中有可以復用的Connection,這里就直接返回了return result!!}// 5, 如果前面沒有獲取到Connection,這里就需要通過routeSelector來獲取到新的Route來進行Connection的建立var newRouteSelection = falseif (selectedRoute == null && (routeSelection == null || !routeSelection!!.hasNext())) {newRouteSelection = true//6,獲取route的過程其實就是DNS獲取到域名IP的過程,這是一個阻塞的過程,會等待DNS結果返回routeSelection = routeSelector.next()}var routes: List<Route>? = nullsynchronized(connectionPool) {if (newRouteSelection) {// Now that we have a set of IP addresses, make another attempt at getting a connection from// the pool. This could match due to connection coalescing.routes = routeSelection!!.routes//7,前面如果通過routeSelector拿到新的Route,其實就是相當于拿到一批新的IP,這里會再次嘗試從ConnectionPool// 中檢查是否有可以復用的Connectionif (connectionPool.transmitterAcquirePooledConnection( address, transmitter, routes, false)) {foundPooledConnection = trueresult = transmitter.connection}}if (!foundPooledConnection) {if (selectedRoute == null) {//8,前面我們拿到的是一批IP,這里通過routeSelection獲取到其中一個IP,Route是proxy和InetAddress的包裝類selectedRoute = routeSelection!!.next()}// Create a connection and assign it to this allocation immediately. This makes it possible// for an asynchronous cancel() to interrupt the handshake we're about to do.//9,用新的route創建RealConnection,注意這里還沒有嘗試連接result = RealConnection(connectionPool, selectedRoute!!)connectingConnection = result}}// If we found a pooled connection on the 2nd time around, we're done.// 10,注釋說得很清楚,如果第二次從connectionPool獲取到Connection可以直接返回了if (foundPooledConnection) {eventListener.connectionAcquired(call, result!!)return result!!}// Do TCP + TLS handshakes. This is a blocking operation.//11,原文注釋的很清楚,這里是進行TCP + TLS連接的地方result!!.connect(connectTimeout, readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled,call,eventListener)//后面一段是將連接成功的RealConnection放到ConnectionPool里面,這里就不貼出來了}return result!!

    從上面的流程可以看到,findConnection這個方法做了以下幾件事:

  • 檢查當前exchangeFinder所保存的Connection是否滿足此次請求
  • 檢查當前連接池ConnectionPool中是否滿足此次請求的Connection
  • 檢查當前RouteSelector列表中,是否還有可用Route(Route是proxy,IP地址的包裝類),如果沒有就發起DNS請求
  • 通過DNS獲取到新的Route之后,第二次從ConnectionPool查找有無可復用的Connection,否則就創建新的RealConnection
  • 用RealConnection進行TCP和TLS連接,連接成功后保存到ConnectionPool
  • Connection的連接復用

    可以看到,第二步和第四步對ConnectionPool做了兩次復用檢查,第五步創建了新的RealConnection之后就會寫會到ConnectionPool中。

    因此這里就是OkHttp的連接復用其實是通過ConnectionPool來實現的,前面的類圖中也反映出來,ConnectionPool內部有一個connections的ArrayDeque對象就是用來保存緩存的連接池。

    DNS過程

    從前面解析的步驟可知,Dns的過程隱藏在了第三步RouteSelector檢查中,整個過程在findConnection方法中寫的比較散,可能不是特別好理解,但是只要搞明白了RouteSelector, RouteSelection,Route這三個類的關系,其實就比較容易理解了,如下圖中展示了三個類之間的關系。

    從圖中可以得到如下結論:

    • RouteSelector在調用next遍歷在不同proxy情況下獲得下一個Selection封裝類,Selection持有一個Route的列表,也就是每個proxy都對應有Route列表
    • Selection其實就是針對List<Route>封裝的一個迭代器,通過next()方法獲得下一個Route,Route持有proxy、address和inetAddress,可以理解為Route就是針對IP和Proxy配對的一個封裝
    • RouteSelector的next()方法內部調用了nextProxy(), nextProxy()又會調用resetNextInetSocketAddres()方法
    • resetNextInetSocketAddres通過address.dns.lookup獲取InetSocketAddress,也就是IP地址

    通過上面一系列流程知道,IP地址最終是通過address的dns獲取到的,而這個dns又是怎么構建的呢?

    反向追蹤代碼,定位到address的dns是transmitter在構建address的時候,將內置的client.dns傳遞進來,而client.dns是在OkHttpclient的構建過程中傳遞進來Dns.System,里面的lookup是通過InetAddress.getAllByName 方法獲取到對應域名的IP,也就是默認的Dns實現。

    至此,整個DNS的過程就真相大白了。OkHttp在這一塊設計的時候,為了強調接耦和開放性,將DNS的整個過程隱藏的比較深,如果不仔細debug跟代碼的話,可能還不是很容易發現。

    3.2 Socket連接的建立

    通過Dns獲得Connectoin之后,就是建立連接的過程了,在findConnection中只體現為一句代碼,如下:

    result!!.connect(connectTimeout,readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled,call,eventListener )

    這里的result是RealConnection類型的對象,就是調用了RealConnection.connect方法,終于離開findConnection 了,接下來看下connect 方法的源碼。

    //省略前面一大段 ....while (true) {try {if (route.requiresTunnel()) {//這里進入的條件是,通過http代理了https請求,有一個特殊的協議交換過程connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)} else {//建立socket連接connectSocket(connectTimeout, readTimeout, call, eventListener)}//如果前面判定是https請求,這里就是https的tls建立過程establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)break} catch (e: IOException) {//清理資源socket?.closeQuietly()//對異常做二次封裝,然后拋出if (routeException == null) {routeException = RouteException(e)} else {routeException.addConnectException(e)}if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {throw routeException}}}

    connect方法并不復雜,先會判定是否有代理的情況做一些特殊處理,然后調用系統方法建立socket連接。

    如果是https請求,還有一個tls的連接要建立,這中間如果有拋出異常,會做一個二次封裝再拋出去。

    4. 總結

    到此為止,基本上OkHttp源碼設計的一個全貌都有了,有一些內容因為日常使用經常會遇到,比如OkHttpClient的Builde參數,比如Request和Response的用法,這里就不再多講。另外針對http2和https的支持,因為涉及到更為復雜的機制和原理,以后有機會另開一篇文章來說。

    http://weixin.qq.com/r/3By8pK7EefMGrerH90nO?(二維碼自動識別)

    總結

    以上是生活随笔為你收集整理的OkHttp源码深度解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。