OkHttp源码深度解析
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:這個是整個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的源碼解析,可得到如下示意圖(省略了部分攔截器):
結合源碼和該示意圖,可以得到如下結論:
了解了上面攔截器的構造過程,我們再來一個個的分析每個攔截器的功能和作用。
從這張局部圖來看,總共添加了五個攔截器(不包含自定義的攔截器如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)的循環,并在循環內部完成兩個重要的判定,如圖中的藍色方框:
重試的邏輯相對復雜,有如下的判定邏輯(具體代碼在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 攔截器的功能如下:
CacheInterceptor 攔截器的邏輯流程如下:
接下來的兩個應該是所有內部攔截器里最重要的兩個了,一個負責處理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連接的過程,這里我們通過兩個圖來了解下整個網絡連接的過程。
先來看方法調用的時序圖,梳理出關鍵步驟:
通過剛才的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由以下步驟組成:
這里我們可以看到,核心工作都由 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這個方法做了以下幾件事:
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源码深度解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 畸形牙尖怎么治疗
- 下一篇: Unix网络编程之IO模型