okhttp


okhttp時一個http client, 它脫離了對原生的依賴, 從創建socket開始,整套都是自己寫的 ,

我們簡單使用如下

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

 

1, OkHttpClient 相當於配置中⼼,所有的請求都會共享這些配置(例如出錯 是否重試、共享的連接池)。 OkHttpClient 中的配置主要有:

Dispatcher dispatcher :調度器,⽤於調度后台發起的⽹絡請求, 有后台總請求數和單主機總請求數的控制。

List protocols :⽀持的應⽤層協議,即 HTTP/1.1、 HTTP/2 等。

List connectionSpecs :應⽤層⽀持的 Socket 設置,即使⽤明⽂傳輸(⽤於 HTTP)還是某個版本的 TLS(⽤於 HTTPS)。

List interceptors :⼤多數時候使⽤的 Interceptor 都應該配置到這⾥。

List networkInterceptors :直接和⽹絡請求交互 的 Interceptor 配置到這⾥,例如如果你想查看返回的 301 報⽂或者未解壓 的 Response Body,需要在這⾥看。

CookieJar cookieJar :管理 Cookie 的控制器。OkHttp 提供了 Cookie 存取的判斷⽀持(即什么時候需要存 Cookie,什么時候需要讀取 Cookie,但沒有給出具體的存取實現。如果需要存取 Cookie,你得⾃⼰寫 實現,例如⽤ Map 存在內存⾥,或者⽤別的⽅式存在本地存儲或者數據 庫。

Cache cache :Cache 存儲的配置。默認是沒有,如果需要⽤,得⾃⼰ 配置出 Cache 存儲的⽂件位置以及存儲空間上限。 HostnameVerifier hostnameVerifier :⽤於驗證 HTTPS 握⼿過程 中下載到的證書所屬者是否和⾃⼰要訪問的主機名⼀致。 CertificatePinner certificatePinner :⽤於設置 HTTPS 握⼿ 過程中針對某個 Host 額外的的 Certificate Public Key Pinner,即把⽹站證 書鏈中的每⼀個證書公鑰直接拿來提前配置進 OkHttpClient ⾥去,作為正 常的證書驗證機制之外的⼀次額外驗證。 Authenticator authenticator :⽤於⾃動重新認證。配置之后,在 請求收到 401 狀態碼的響應是,會直接調⽤ authenticator ,⼿動加 ⼊ Authorization header 之后⾃動重新發起請求。

boolean followRedirects :遇到重定向的要求是,是否⾃動 follow。

boolean followSslRedirects 在重定向時,如果原先請求的是 http ⽽重定向的⽬標是 https,或者原先請求的是 https ⽽重定向的⽬標是 http,是否依然⾃動 follow。(記得,不是「是否⾃動 follow HTTPS URL 重定向的意思,⽽是是否⾃動 follow 在 HTTP 和 HTTPS 之間切換的重定 向)

boolean retryOnConnectionFailure :在請求失敗的時候是否⾃動 重試。注意,⼤多數的請求失敗並不屬於 OkHttp 所定義的「需要重試」, 這種重試只適⽤於「同⼀個域名的多個 IP 切換重試」「Socket 失效重試」 等情況。

int connectTimeout :建⽴連接(TCP 或 TLS)的超時時間。

int readTimeout :發起請求到讀到響應數據的超時時間。

int writeTimeout :發起請求並被⽬標服務器接受的超時時間。(為 什么?因為有時候對⽅服務器可能由於某種原因⽽不讀取你的 Request

 

2, client.newCall(request)生成了一個RealCall對象,當調⽤ RealCall.execute() 的時 候, RealCall.getResponseWithInterceptorChain() 會被調⽤,它 會發起⽹絡請求並拿到返回的響應,裝進⼀個 Response 對象並作為返回值返 回; RealCall.enqueue() 被調⽤的時候⼤同⼩異,區別在於 enqueue() 會使⽤ Dispatcher 的線程池來把請求放在后台線程進⾏,但 實質上使⽤的同樣也是 getResponseWithInterceptorChain() ⽅法。

3, okhttp內部請求的過程時一個鏈式結構, getResponseWithInterceptorChain() ⽅法做的事:把所有配置好的 Interceptor 放在⼀個 List ⾥,然后作為參數,創建⼀個 RealInterceptorChain 對象,並調⽤ chain.proceed(request) 來 發起請求和獲取響應

RealInterceptorChain 中,多個 Interceptor 會依次調⽤⾃⼰的 intercept() ⽅法。這個⽅法會做三件事: 1. 對請求進⾏預處理 2. 預處理之后,重新調⽤ RealIntercepterChain.proceed() 把請求 交給下⼀個 Interceptor 3. 在下⼀個 Interceptor 處理完成並返回之后,拿到 Response 進⾏后續 處理

當然了,最后⼀個 Interceptor 的任務只有⼀個:做真正的⽹絡請求並 拿到響應

我們可以為okhttp配置多個interceptor, 如果我們不配置,默認的Interceptor有五個

 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)

 

⾸先是開發者使⽤ addInterceptor(Interceptor) 所設置的,它們 會按照開發者的要求,在所有其他 Interceptor 處理之前,進⾏最早的 預處理⼯作,以及在收到 Response 之后,做最后的善后⼯作。如果你有統 ⼀的 header 要添加,可以在這⾥設置;

然后是 RetryAndFollowUpInterceptor :它會對連接做⼀些初始化⼯ 作,並且負責在請求失敗時的重試,以及重定向的⾃動后續請求。它的存 在,可以讓重試和重定向對於開發者是⽆感知的;

BridgeInterceptor :它負責⼀些不影響開發者開發,但影響 HTTP 交 互的⼀些額外預處理。例如,Content-Length 的計算和添加、gzip 的⽀持 (Accept-Encoding: gzip)、gzip 壓縮數據的解包,都是發⽣在這⾥;

CacheInterceptor :它負責 Cache 的處理。把它放在后⾯的⽹絡交互 相關 Interceptor 的前⾯的好處是,如果本地有了可⽤的 Cache,⼀個 請求可以在沒有發⽣實質⽹絡交互的情況下就返回緩存結果,⽽完全不需要 開發者做出任何的額外⼯作,讓 Cache 更加⽆感知;

ConnectInterceptor :它負責建⽴連接。在這⾥,OkHttp 會創建出⽹ 絡請求所需要的 TCP 連接(如果是 HTTP),或者是建⽴在 TCP 連接之上 的 TLS 連接(如果是 HTTPS),並且會創建出對應的 HttpCodec 對象 (⽤於編碼解碼 HTTP 請求);

然后是開發者使⽤ addNetworkInterceptor(Interceptor) 所設置 的,它們的⾏為邏輯和使⽤ addInterceptor(Interceptor) 創建的 ⼀樣,但由於位置不同,所以這⾥創建的 Interceptor 會看到每個請求 和響應的數據(包括重定向以及重試的⼀些中間請求和響應),並且看到的 是完整原始數據,⽽不是沒有加 Content-Length 的請求數據,或者 Body 還沒有被 gzip 解壓的響應數據。多數情況,這個⽅法不需要被使⽤,不過 如果你要做⽹絡調試,可以⽤它;

CallServerInterceptor :它負責實質的請求與響應的 I/O 操作,即 往 Socket ⾥寫⼊請求數據,和從 Socket ⾥讀取響應數據。

 

4, 連接池

okhttp內部維護了一個連接池對象, 每當有新的請求時,它會

1)如果連接池中有符合本次請求的連接(ip, 端口,tls協議等都一樣, 且沒有超過最大連接數), 有的話直接使用

2)嘗試從連接池中獲取一個不帶多路復用的連接

3)如果是http2, 那么再嘗試從連接池中獲取可以多路復用的連接

4, 如果還拿不到, 那就自己創建一個連接,放進連接池

5, 再從連接池中嘗試獲取可以多路復用的連接, 如果能拿到,就直接用,並且把自己剛剛創建的那個連接扔掉 (同步塊中)

(為什么創建了之后還要再從池里拿? 為了防止多個請求同時創建連接)

  1  @Throws(IOException::class)
  2   private fun findConnection(
  3     connectTimeout: Int,
  4     readTimeout: Int,
  5     writeTimeout: Int,
  6     pingIntervalMillis: Int,
  7     connectionRetryEnabled: Boolean
  8   ): RealConnection {
  9     var foundPooledConnection = false
 10     var result: RealConnection? = null
 11     var selectedRoute: Route? = null
 12     var releasedConnection: RealConnection?
 13     val toClose: Socket?
 14     synchronized(connectionPool) {
 15       if (call.isCanceled()) throw IOException("Canceled")
 16 
 17       releasedConnection = call.connection
 18       toClose = if (call.connection != null &&
 19           (call.connection!!.noNewExchanges || !call.connection!!.supportsUrl(address.url))) {
 20         call.releaseConnectionNoEvents()
 21       } else {
 22         null
 23       }
 24 
 25       if (call.connection != null) {
 26         // We had an already-allocated connection and it's good.
 27         result = call.connection
 28         releasedConnection = null
 29       }
 30 
 31       if (result == null) {
 32         // The connection hasn't had any problems for this call.
 33         refusedStreamCount = 0
 34         connectionShutdownCount = 0
 35         otherFailureCount = 0
 36 
 37         // Attempt to get a connection from the pool.
 38         if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
 39           foundPooledConnection = true
 40           result = call.connection
 41         } else if (nextRouteToTry != null) {
 42           selectedRoute = nextRouteToTry
 43           nextRouteToTry = null
 44         }
 45       }
 46     }
 47     toClose?.closeQuietly()
 48 
 49     if (releasedConnection != null) {
 50       eventListener.connectionReleased(call, releasedConnection!!)
 51     }
 52     if (foundPooledConnection) {
 53       eventListener.connectionAcquired(call, result!!)
 54     }
 55     if (result != null) {
 56       // If we found an already-allocated or pooled connection, we're done.
 57       return result!!
 58     }
 59 
 60     // If we need a route selection, make one. This is a blocking operation.
 61     var newRouteSelection = false
 62     if (selectedRoute == null && (routeSelection == null || !routeSelection!!.hasNext())) {
 63       var localRouteSelector = routeSelector
 64       if (localRouteSelector == null) {
 65         localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
 66         this.routeSelector = localRouteSelector
 67       }
 68       newRouteSelection = true
 69       routeSelection = localRouteSelector.next()
 70     }
 71 
 72     var routes: List<Route>? = null
 73     synchronized(connectionPool) {
 74       if (call.isCanceled()) throw IOException("Canceled")
 75 
 76       if (newRouteSelection) {
 77         // Now that we have a set of IP addresses, make another attempt at getting a connection from
 78         // the pool. This could match due to connection coalescing.
 79         routes = routeSelection!!.routes
 80         if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
 81           foundPooledConnection = true
 82           result = call.connection
 83         }
 84       }
 85 
 86       if (!foundPooledConnection) {
 87         if (selectedRoute == null) {
 88           selectedRoute = routeSelection!!.next()
 89         }
 90 
 91         // Create a connection and assign it to this allocation immediately. This makes it possible
 92         // for an asynchronous cancel() to interrupt the handshake we're about to do.
 93         result = RealConnection(connectionPool, selectedRoute!!)
 94         connectingConnection = result
 95       }
 96     }
 97 
 98     // If we found a pooled connection on the 2nd time around, we're done.
 99     if (foundPooledConnection) {
100       eventListener.connectionAcquired(call, result!!)
101       return result!!
102     }
103 
104     // Do TCP + TLS handshakes. This is a blocking operation.
105     result!!.connect(
106         connectTimeout,
107         readTimeout,
108         writeTimeout,
109         pingIntervalMillis,
110         connectionRetryEnabled,
111         call,
112         eventListener
113     )
114     call.client.routeDatabase.connected(result!!.route())
115 
116     var socket: Socket? = null
117     synchronized(connectionPool) {
118       connectingConnection = null
119       // Last attempt at connection coalescing, which only occurs if we attempted multiple
120       // concurrent connections to the same host.
121       if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
122         // We lost the race! Close the connection we created and return the pooled connection.
123         result!!.noNewExchanges = true
124         socket = result!!.socket()
125         result = call.connection
126 
127         // It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
128         // that case we will retry the route we just successfully connected with.
129         nextRouteToTry = selectedRoute
130       } else {
131         connectionPool.put(result!!)
132         call.acquireConnectionNoEvents(result!!)
133       }
134     }
135     socket?.closeQuietly()
136 
137     eventListener.connectionAcquired(call, result!!)
138     return result!!
139   }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM