A. Android通過OKhttp從伺服器端獲取數據
①簡單的異棚洞步Get請求
第一步,創建OKHttpClient對象
第二步,創建Request請求
第三步,創建一個Call對象
第四步,將請求添大和賀加到調度中
不多說,直接上代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//okHttp的基本使用 --- get方法
String url = "https://api.douban.com/v2/movie/top250?start=0&count=10";
//1,創建OKHttpClient對象
OkHttpClient mOkHttpClient = new OkHttpClient();
//2,創建一個Request
Request request = new Request.Builder().url(url).build();
//3,創建一個call對象
Call call = mOkHttpClient.newCall(request);
//4,將請求添加到調滾派度中
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
if (response.isSuccessful()) {
final String message = response.body().string();
handler.post(new Runnable() {
@Override
public void run() {
tv_message.setText(message);
progressBar.setVisibility(View.GONE);
}
});
}
}
});
B. OkHttp源碼解析 (三)——代理和路由
初看OkHttp源碼,由於對Address、Route、Proxy、ProxySelector、RouteSelector等理解不夠,讀源碼非常吃力,看了幾遍依然對於尋找復用連接、創建連接、連接伺服器、連接代理伺服器、創建隧道連接等邏輯似懂非懂,本篇決定梳理一遍相關的概念及基本原理。
● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY
一個http請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,http請求用80;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後,發送http報文數據。
一個https請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,https請求用443;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後進行TLS握手,可通過java標准款提供的SSLSocket完成;
5、握手成功後,發送https報文數據。
1、分類
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5
2、HTTP代理分類及說明
普通代理
HTTP/1.1 協議的第一部分。其代理過程為:
● client 請求 proxy
● proxy 解析請求獲取 origin server 地址
● proxy 向 origin server 轉發請求
● proxy 接收 origin server 的響應
● proxy 向 client 轉發響應
其中proxy獲取目的伺服器地址的標准方法是解析 request line 里的 request-URL。因為proxy需要解析報文,因此普通代理無法適用於https,因為報文都是加密的。
隧道代理
通過 Web 代理伺服器用隧道方式傳輸基於 TCP 的協議。
請求包括兩個階段,一是連接(隧道)建立階段,二是數據通信(請求響應)階段,數據通信是基於 TCP packet ,代理伺服器不會對請求及響應的報文作任何的處理,都是原封不動的轉發,因此可以代理 HTTPS請求和響應。
代理過程為:
● client 向 proxy 發送 CONNET 請求(包含了 origin server 的地址)
● proxy 與 origin server 建立 TCP 連接
● proxy 向 client 發送響應
● client 向 proxy 發送請求,proxy 原封不動向 origin server 轉發請求,請求數據不做任何封裝,為原生 TCP packet.
3、SOCKS代理分類及說明
● SOCKS4:只支持TCP協議(即傳輸控制協議)
● SOCKS5: 既支持TCP協議又支持UDP協議(即用戶數據包協議),還支持各種身份驗證機制、伺服器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反過來卻不行,比如我們常用的聊天工具QQ在使用代理時就要求用SOCKS5代理,因為它需要使用UDP協議來傳輸數據。
有了上面的基礎知識,下面分析結合源碼分析OkHttp路由相關的邏輯。OkHttp用Address來描述與目標伺服器建立連接的配置信息,但請求輸入的可能是域名,一個域名可能對於多個ip,真正建立連接是其中一個ip,另外,如果設置了代理,客戶端是與代理伺服器建立直接連接,而不是目標伺服器,代理又可能是域名,可能對應多個ip。因此,這里用Route來描述最終選擇的路由,即客戶端與哪個ip建立連接,是代理還是直連。下面對比下Address及Route的屬性,及路由選擇器RouteSelector。
描述與目標伺服器建立連接所需要的配置信息,包括目標主機名、埠、dns,SocketFactory,如果是https請求,包括TLS相關的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理伺服器信息Proxy 、ProxySelector 。
Route提供了真正連接伺服器所需要的動態信息,明確需要連接的伺服器IP地址及代理伺服器,一個Address可能會有很多個路由Route供選擇(一個DNS對應對個IP)。
Address和Route都是數據對象,沒有提供操作方法,OkHttp另外定義了RouteSelector來完成選擇的路由的操作。
1、讀取代理配置信息:resetNextProxy()
讀取代理配置:
● 如果有指定代理(不讀取系統配置,在OkHttpClient實例中指定),則只用1個該指定代理;
● 如果沒有指定,則讀取系統配置的,可能有多個。
2、獲取需要嘗試的socket地址(目標伺服器或者代理伺服器):resetNextInetSocketAddress()
結合Address的host和代理,解析要嘗試的套接字地址(ip+埠)列表:
● 直連或者SOCK代理, 則用目標伺服器的主機名和埠,如果是HTTP代理,則用代理伺服器的主機名和埠;
● 如果是SOCK代理,根據目標伺服器主機名和埠號創建未解析的套接字地址,列表只有1個地址;
● 如果是直連或HTTP代理,先DNS解析,得到InetAddress列表(沒有埠),再創建InetSocketAddress列表(帶上埠),InetSocketAddress與InetAddress的區別是前者帶埠信息。
3、獲取路由列表:next()
選擇路由的流程解析:
● 遍歷每個代理對象,可能多個,直連的代理對象為Proxy.DIRECT(實際是沒有中間代理的);
● 對每個代理獲取套接字地址列表;
● 遍歷地址列表,創建Route,判斷Route如果在路由黑名單中,則添加到失敗路由列表,不在黑名單中則添加到待返回的Route列表;
● 如果最後待返回的Route列表為空,即可能所有路由都在黑名單中,實在沒有新路由了,則將失敗的路由集合返回;
● 傳入Route列表創建Selection對象,對象比較簡單,就是一個目標路由集合,及讀取方法。
為了避免不必要的嘗試,OkHttp會把連接失敗的路由加入到黑名單中,由RouteDatabase管理,該類比較簡單,就是一個失敗路由集合。
1、創建Address
Address的創建在RetryAndFollowUpInteceptor里,每次請求會聲明一個新的Address及StreamAllocation對象,而StreamAllocation使用Address創建RouteSelector對象,在連接時RouteSelector確定請求的路由。
每個Requst都會構造一個Address對象,構造好了Address對象只是有了與伺服器連接的配置信息,但沒有確定最終伺服器的ip,也沒有確定連接的路由。
2、創建RouteSelector
在StreamAllocation聲明的同時會聲明路由選擇器RouteSelector,為一次請求尋找路由。
3、選擇可用的路由Route
下面在測試過程跟蹤實例對象來理解,分別測試直連和HTTP代理HTTP2請求路由的選擇過程:
● 直連請求流程
● HTTP代理HTTPS流程
請求url: https://www.jianshu.com/p/63ba15d8877a
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、確定協議
測試方法:
● 在PC端打開Charles,設置埠,如何設置代理,網上有教程,比較簡單;
● 手機打開WIFI,選擇連接的WIFI修改網路,在高級選項中設置中指定了代理伺服器,ip為PC的ip,埠是Charles剛設置的埠;
● OkHttpClient不指定代理,發起請求。
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、創建隧道
由於是代理https請求,需要用到隧道代理。
從圖可以看出,建立隧道其實是發送CONNECT請求,header包括欄位Proxy-Connection,目標主機名,請求內容類似:
6、確定協議,SSL握手
1、代理可分為HTTP代理和SOCK代理;
2、HTTP代理又分為普通代理和隧道代理;普通代理適合明文傳輸,即http請求;隧道代理僅轉發TCP包,適合加密傳輸,即https/http2;
3、SOCK代理又分為SOCK4和SOCK5,區別是後者支持UDP傳輸,適合代理聊天工具如QQ;
4、沒有設置代理(OkHttpClient沒有指定同時系統也沒有設置),客戶端直接與目標伺服器建立TCP連接;
5、設置了代理,代理http請求時,客戶端與代理伺服器建立TCP連接,如果代理伺服器是域名,則解釋代理伺服器域名,而目標伺服器的域名由代理伺服器解析;
6、設置了代理,代理https/http2請求時,客戶端與代理伺服器建立TCP連接,發送CONNECT請求與代理伺服器建立隧道,並進行SSL握手,代理伺服器不解析數據,僅轉發TCP數據包。
如何正確使用 HTTP proxy
OkHttp3中的代理與路由
HTTP 代理原理及實現(一)
C. Android網路請求庫【OkHttp4.9.3】基本用法與原理分析
OkHttp是一套處理 HTTP 網路請求的依賴庫,由 Square 公司設計研發並開源,目前可以在 Java 和 Kotlin 中使用。對於 Android App 來說,OkHttp 現在幾乎已經占據了所有的網路請求操作,Retrofit + OkHttp實現網路請求似乎成了一種標配。因此它也是每一個 Android 開發工程師的必備技能,了解其內部實現原理可以更好地進行功能擴展、封裝以及優化。
OkHttp的高效性體現在:
第一步:創建OkHttpClient,創建OkHttpClient有兩種方式:
OkHttpClient提供了豐富的配置方法,例如添加攔截器、指定連接池、設置請求超時等等。
第二步:創建請求
使用Request.Builder() 構建Request實例
第三步:發起網路請求
OkHttp支持同步和非同步兩種請求方式
OkHttp的使用方法非常簡單,三步操作就可以發起一個簡單的同步或非同步請求。我們也可以很輕松地對網路請求進行配置,例如添加請求頭、設置請求方式、設置請求超時等等,這些配置參數會在源碼分析過程中詳細介紹。
現在我們已經學會了三步操作發起網路請求,接下來以這三個步驟為切入點,深入到源碼中學習OkHttp的實現原理,廢話少說馬上開車。
OkHttpClient創建方式有兩種,我們看看兩種方式有什麼區別。
第一種直接使用默認構造函數,內部依然是使用建造者模式
第二種使用建造者模式
兩種方式最終都是調用構造函數OkHttpClient(builder:Builder),由參數builder負責所有的參數配置工作。
當您創建單個OkHttpClient實例並將其用於所有 HTTP 調用時,OkHttp 性能最佳。 這是因為每個OkHttpClient都擁有自己的連接池和線程池,重用連接和線程可減少延遲並節省內存。 相反,為每個請求創建一個客戶端會浪費空閑池上的資源。
Request同樣使用建造者模式來創建,這里貼上部分重要源碼,很簡單就不細說了。
OkHttp發起網路請求分為同步請求和非同步請求兩種方式,我們只分析非同步請求流程,因為只要理解了非同步請求過程,基本上也就明白同步請求是怎麼一回事了。
RealCall是連接應用層與網路層的橋梁,負責處理連接、請求、響應和數據流。
Dispatcher維護著一套非同步任務執行策略,分析策略之前先介紹幾個重要概念:
client.dispatcher.enqueue(AsyncCall(responseCallback)) 執行步驟為:
AsyncCall實現了Runnable介面,因此一旦被線程池中的線程處理就會調用它的run()方法:
話休絮煩,我們開始分析攔截器責任鏈:
責任鏈執行流程:首先獲取當前攔截器interceptor,並且調用interceptor.intercept(next)執行攔截器操作。這里的next表示的是index+1後的責任鏈對象,攔截器的intercept()方法內部會調用next.proceed(request)方法再次進入到責任鏈,由於此時index已經加1,所以處理的是下一個攔截器。
如此循環往復,直到處理完責任鏈上最後一個攔截器為止。
注意除最後一個攔截器CallServerInterceptor不會調用chain.proceed(request)方法之外,其他攔截器都應該至少調用一次chain.proceed(request)方法。
為了驗證上面的結論,我們進入到RetryAndFollowUpInterceptor的intercept()方法一探究竟:
可以看到注釋1處重新進入責任鏈處理下一個攔截器。
有興趣可以自行查看最後一個攔截器CallServerInterceptor源碼,此處只給出本人閱讀源碼後得出的結論:
以上就是攔截器責任鏈的工作流程,我們再通過流程圖仔細感受一下。
分析完攔截器責任鏈,我們繼續分析AsyncCall#run()方法:
我們看到,如果()方法成功獲得服務端返回的數據,則調用responseCallback.onResponse(this@RealCall, response)方法完成非同步回調;如果服務端數據獲取失敗(請求異常),則調用responseCallback.onFailure(this@RealCall, canceledException)方法完成非同步回調
需要注意的是,responseCallback回調是在子線程中完成的,所以如果想把數據顯示到UI上,需要切換回主線程進行UI操作。
OkHttp發起網路請求全過程:
【知識點】OkHttp 原理 8 連問
D. 網路請求框架-OkHttp原理解析
okhttp是square公司貢獻的一個處理網路請求的開源框架,是目前Android開發使用最廣泛的一個網路框架,從Android4.4開始,httpURLconnection的底層實現採用的就是okhttp。內部實現就是利用java基礎,對socket進行封裝,實現http通信。最重要的兩個關鍵點就是分發器和5個攔截器。
分發器 就是內部維護隊列和線程池,完成請求分配,總結就是用於對非同步任務加入隊列管理,然後判斷條件,控制數量,加入線程池執行非同步請求任務。
五個默認攔截器 就是利用責任鏈模式對網路請求進行層層處理,完成整個請求過程,簡單總結如下。
1.橋接攔截器對用戶發出的請求添加缺少的請求配置欄位,比如keep-alive等
2.緩存攔截器就是查詢有沒有符合判斷條件的已緩存的網路請求,執行復用,直接返回response
3.連接攔截器就是創建請求,加入連接器 或者訪問連接池,根據條件判斷,是否能懟已創建的tcp請求進行復用
4.請求伺服器攔截器就是對scoket進行操作,請求網路訪問伺服器,返回response,
5.重試和重定向攔截器就是對返回的response進行code判斷,決定是否要重試或者重定向操作。
1.支持http2.0版本,並且允許對同一主機的所有請求共享一個套接字
2.即使不是http2.0版本,通過連接池,減少請求延遲
3.默認使用Gzip 壓縮數據
4.響應緩存,避免重復請求網路
最簡單的http請求案例
1.利用建造者模式構建okHttpClient實例對象,構建過程中可以動態配置參數,請求時間,響應時間,緩存信息等。
2.創建Request對象,設置請求方式,鏈接地址,參數等信息。
3.把request對象,傳給client,通過newCall函數,得到RealCall對象。
4.RealCall 分為同步和非同步執行
5.同步執行時,分發器只是做個記錄,把請求任務加到隊列中,然後直接通過攔截器訪問伺服器,返回response。
6.非同步執行
6.1先對非同步任務進一步封裝,把任務放到AsyncCall對象中
2.分發器 把 封裝後的非同步任務 添加到等待運行的隊列中
7. 通過攔截器,獲取response
okhttp 默認提供5個攔截器 重試重定向攔截器,橋接攔截器,緩存攔截器,連接攔截器,訪問伺服器攔截器。還可以自定義攔截器。
自定義攔截器分為應用攔截器(通過addInterceptor 添加)和網路攔截器(通過addNetworkInterceptor攔截)
攔截器採用責任鏈的設計默認,讓請求者和處理者解耦,最終請求從前往後,響應從後往前。
首先先判斷用戶是否取消了請求,如果沒有取消,就把請求交個橋接攔截器。
在獲得響應結果response的時候根據響應碼,判斷是否需要重試或者重定向, 重試不限制次數,重定向最多20次 ,如果需要重試或者重定向,那麼會再一次重新執行所有攔截器。
有如下幾種情況不會重試:IO異常,線路異常,配置client實例時配置不允許重試,協議異常,證書異常等等。
先獲取用戶發送的請求,判斷條件用戶是否已經配置過請求頭欄位,若用戶沒有配置,則將http協議必備的請求頭欄位補齊,比如Content-Type,Content-Length等,然後交給下一個攔截器。
在獲得響應結果response之後,調用保存cookie的介面(也可以在配置client的時候,設置cookjar進行cookie回調數據),並且解析gzip數據
獲取結果之後,對cookie進行保存,對返回的數據進行gzip解壓
就是根據緩存策略從緩存中查找是否有合適的緩存response,如果有合適的緩存,直接返回給請求任務,不在繼續執行後面的攔截器。
獲得響應結果response後,根據條件判斷,決定是否要緩存。
維護一個連接池,負責對連接的服務。在把請求交給下一個攔截器之前。會先在連接池中找到一個合適的連接(滿足適配條件相同,並且沒有正在被使用)或者新建一個連接,並且接入連接池,獲得對應的socket流,把請求交給下一個攔截器。獲得response結果後不會進行額外的處理。
連接池, 也稱之為對象池,主要用來存放request請求連接,內部維護了一個LinkedQueue隊列用來存放請求。在添加新的請求對象時,都會執行一個周期性任務,用以對連接池進行清理操作。
1.隊列長度超過5,清理最近未被使用連接,LRE演算法
2.存儲的連接,5分鍾未被復用,清理
拿到上一個攔截器返回的請求,真正的與伺服器進行通信,向伺服器發送數據,解析讀取響應的數據,返回給上一個攔截器。
1.創建request =>OkHttpClient=>RealCall()
2.同步執行 ,分發器添加同步任務,執行攔截器,訪問伺服器,返回reponse,觸發非同步分發流程。
3.非同步執行 ,封裝任務= >AsyncCall ,實現runnable介面。添加任務到非同步任務等待隊列,執行分發任務,判斷非同步任務是否能加入正在執行的非同步任務隊列,滿足兩個條件
同時執行的非同步任務數量不得大於64個
對同一個主機的訪問任務,最多不得大於5個
4.加入正在執行的非同步任務隊列,通過線程池執行任務,經過5個默認攔截器訪問伺服器,返回response,執行非同步任務分發。
分發器工作 分為同步任務和非同步任務兩種
同步任務 就是把任務加入同步任務隊列,加個標記,執行結束之後,觸發非同步任務的分發操作。
非同步任務 先封裝任務到asyncCall對象,實現了runnable介面。把任務加入等待執行隊列,執行分發操作。
先遍歷等待任務隊列,判斷是否符合加入正在運行的非同步任務隊列,要同時滿足兩個條件。
同時執行的非同步任務數量不得大於64個
對同一個主機的訪問任務,最多不得大於5個
當滿足條件後,從等待隊列中刪除任務,把任務加入正在執行的隊列中,通過自定義的線程池,執行任務,任務執行結束後,再次執行分發操作。
攔截器採用了責任鏈設計默認,讓請求者和執行者解耦,請求者只需要將請求發給責任鏈即可,無需關心請求過程和細節。okHttp 默認有5個攔截器,重試重定向攔截器,橋接攔截器,緩存攔截器,連接攔截器,請求服務攔截器。工作細節參考上面攔截器原理分析部分
1.位置的關系,應用攔截器 放在責任鏈最頂端,網路攔截器放在責任鏈倒數第二的位置。所以應用攔截器 最先攔截,最後響應,網路攔截器 倒數第二攔截,第二響應。如果列印請求日誌的情況,應用攔截器列印的是用戶請求信息,經過重試重定向,橋接,緩存,鏈接 等攔截器的層層包裝,網路攔截器列印的是實際請求的信息。
2.應用攔截器一定會被執行,網路攔截器不一定被執行。
利用連接池,緩存所有的有效連接對象。
清理機制:垃圾連接
1.超過5分鍾沒有用過的鏈接
2.超過5個閑置鏈接後,從最久閑置的鏈接開始執行清理(LRU)