Okhttp做爲目前Android使用最爲普遍的網絡框架之一,咱們有必要去深刻了解一下,本文是Okhttp解析的第一篇,主要是從宏觀上認識Okhttp整個架構是如何實現的。html
HTTP是當今應用程序經過網絡交換數據和媒體的方式。 有效地使用 HTTP 可使應用加載得更快並節省帶寬。 Okhttp是一個高效的HTTP Client,高效性體如今:git
當網絡出現問題時,OkHttp 不會當即結束: 它會默默地從常見的鏈接問題中恢復過來。 若是您的服務有多個 IP 地址,若是第一次鏈接失敗,OkHttp 將嘗試替代地址。 這對於 IPv4 + IPv6和承載於冗餘數據中心的服務是必要的。 Okhttp 支持現代 TLS 特性(TLS 1.三、 ALPN、證書ping)。 它能夠配置爲回退到可用的鏈接。 而且Okhttp是易用的,其經過Builder模式設計請求 / 響應 API,支持同步阻塞調用和帶回調的異步調用。github
首先咱們來了解下HTTP client、request、response。 HTTP client的做用就是接受咱們的request並返回response。 request一般包含一個 URL, 一個方法 (好比GET/POST), 以及一個headers列表還可能包含一個body(特定內容類型的數據流)。 response則一般用響應代碼(好比200表示成功,404表示未找到)、headers和可選的body來回答request。web
咱們平常使用http都是按如下步驟: 一、建立httpClient 二、建立request 三、使用httpClient請求request而後獲取respone緩存
使用Okhttp也是如此,咱們建立OkhttpClient而後把Reques交給它,最後拿到Respone,可是Okhttp在內部實際進行http請求時並非這樣簡單的拿Request去請求而後得到Resopne返回。安全
下面就來看下Okhttp的請求機制,能夠歸納爲如下流程:服務器
Okhttp 能夠添加原始請求中缺乏的headers,包括Content-Length,Transfer-Encoding,User-Agent ,Host ,Connection , 和Content-Type。 除非Accept-Encoding頭已經存在,不然它將添加一個用於透明響應壓縮的 Accept-Encoding 頭。 若是你有 cookies,OkHttp 會添加一個 Cookie 頭。cookie
有些請求會有一個緩存response。 當這個緩存過時,OkHttp 能夠執行一個有條件的 GET 來下載新的response,這須要添加如 If-Modified-Since 和 If-None-Match 這樣的headers。網絡
Okhttp鏈接webserver時使用了URL、Address和Route。架構
Url是 HTTP 和互聯網的基礎,每一個 URL 標識一個特定的路徑。它是一個通用的,分散的網絡命名方案,它指定了如何訪問網絡資源、指定調用是純文本(http) 或加密(https)方式。它們沒有指定是否應該使用特定的代理服務器或者如何經過該代理服務器的身份驗證
Address指定一個 web 服務器(好比 github. com)和鏈接到該服務器所需的全部靜態配置: 端口號、 HTTPS 設置和首選網絡協議(好比 http / 2或 SPDY)。
具備相同Address的 url 也可能有相同的底層 TCP 套接字鏈接。 共享一個鏈接有很大的性能優點好比更低的延遲,更高的吞吐量(因爲 TCP 緩慢啓動)和節省電池。 Okhttp 使用一個 ConnectionPool 自動重用 http / 1.x 鏈接以及多路傳輸 http / 2和 SPDY 鏈接。 在 OkHttp 中,Address的一些字段來自 URL (scheme, hostname, port) ,其他字段來自 OkHttpClient。
Route提供實際鏈接到網絡服務器所需的動態信息。 它會嘗試的特定 IP 地址(由 DNS 查詢發現)、使用的準確代理服務器(若是使用 ProxySelector)以及協商的 TLS 版本(用於 HTTPS 鏈接)。 一個地址可能有多條Route。 例如,承載於多個數據中心的 web 服務器在其 DNS 響應中可能會產生多個 IP 地址。
當你使用 OkHttp 請求一個 URL 時,它是這樣作的:
若是鏈接有問題,OkHttp 會選擇另外一條route,再試一次。 這讓 OkHttp 在服務器地址的一個子集沒法訪問時從錯誤中恢復。 當池鏈接過期或者不支持當前使用的 TLS 版本時,它也頗有用。 一旦接收到respone,鏈接將返回到池中,以即可以在未來的請求中重用它。 鏈接在一段時間的不活動會被從鏈接池中清除。
若是使用透明壓縮,OkHttp 將刪除相應的 Content-Encoding 和 Content-Length,由於它們不適用於解壓縮的響應體。 若是條件 GET請求 成功,來自網絡和緩存的respone將按照規範的指示進行合併。
當你請求的 URL 被重定向,webserver 將返回一個響應代碼,好比302來指示新 URL。Okhttp將會重定向檢索最終的respone。 若是respone發出了一個受權驗證,OkHttp 將要求 Authenticator (若是配置了一個)知足這個驗證。 若是身份驗證者提供了憑據,那麼request會攜帶該憑據去重試。
有時候鏈接會失敗(好比池鏈接過期並斷開鏈接或沒法鏈接到網絡服務器自己)若是此時有一個可用的Route,OkHttp 會用該Route重試請求。
Okhttp在實現流程的時候還引入了一些概念好比Call、Interceptors、ConnectionSpec、Events等等。
通過重寫、重定向、重試等操做,簡單請求可能會產生許多請求和響應。 Okhttp 使用 Call 來封裝表示request,Call就是一個已經準備好能夠執行的請求。若是 url 被重定向,或者故障轉移到另外一個 IP 地址,那麼代碼將繼續工做,直至返回最終respone。
Call有兩種調用方式: 同步:線程阻塞直到響應可讀爲止 異步: 能夠在任何線程上對請求進行排隊,當響應可讀時在另外一個線程上被調回 能夠從任何線程取消Call調用, 這將致使調用失敗。在寫入請求body或讀取響應body時調用cancel會拋出IOException。
Dispatcher調度器,它實際就是負責Okhttp請求策略。 對於同步調用,調用請求的線程本身負責管理同時發出的請求數量。可是要注意的是太多的併發請求會浪費資源; 太少也很差。 對於異步調用,Dispatcher 實現最大併發請求的策略。 它設置了每一個 web 服務器的最大值併發請求數爲5,總併發數爲64。固然咱們也能夠自行設置併發數。
攔截器能夠說是Okhttp的精髓之一,它是一種強大的機制,它能夠監測、重寫和重試Call調用。系統提供了5種已經定義好的攔截器,上面說的request/respone 重寫,失敗重試等都是在攔截器中完成的。 對 chain.proceed (request)的調用是每一個攔截器實現的關鍵部分。 這個簡單的外觀方法是全部攔截器完成其功能的地方,而且還生成知足請求的響應。 注意若是 chain.proceed (request)被調用屢次,則必須關閉之前的響應body。 實際使用過程是經過攔截器鏈把攔截器鏈起來而後按順序調用攔截器。
Okhttp攔截器分爲2類:Application Interceptors和Network Interceptors。
這兩類攔截器本質上沒有區別只是它們做用的時機不一樣。由上圖咱們能夠看出Application Interceptors做用於Okhttp Core以前而Network Interceptors則做用於Okhttp Core以後。個人理解就是Application Interceptors調用是在請求發出以前,Network Interceptors則是在請求發出後與webserver鏈接的過程。 每種攔截器鏈都有其優勢: Application interceptors
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */ final class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request originalRequest = chain.request(); if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), gzip(originalRequest.body())) .build(); return chain.proceed(compressedRequest); } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // We don't know the compressed length in advance! } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } }
使用Interceptor重寫響應
/** Dangerous interceptor that rewrites the server's cache-control header. */ private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); return originalResponse.newBuilder() .header("Cache-Control", "max-age=60") .build(); } };
ConnectionSpec的引入是爲了HTTPS,在協商與 HTTPS 服務器的鏈接時,OkHttp 須要知道要提供哪些 TLS 版本和密碼套件。Okhttp爲了與儘量多的主機鏈接的同時保證鏈接的安全性引入ConnectionSpec,它實現了特定的安全性和鏈接性決策,Okhttp 包括四個內置的鏈接規範RESTRICTED_TLS、MODERN_TLS、COMPATIBLE_TLS、CLEARTEXT。 默認狀況下,OkHttp 將嘗試創建一個 MODERN_TLS 鏈接。 可是,經過配置client的 connectionSpecs,若是 MODERN_TLS失敗,回退到 COMPATIBLE_TLS 鏈接。
OkHttpClient client = new OkHttpClient.Builder() .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS)) .build();
Events的引入是爲了監控call請求,由於咱們有必要了解咱們應用的HTTP請求。具體來講就是監控如下內容: 應用程序 HTTP 請求調用的頻率和請求大小。 基礎網絡的性能監控,若是網絡差你應該減小請求或者改善網絡。
Okhttp提供了EventListener,咱們能夠繼承它並重寫咱們感興趣的方法來進行Event的監控。 上圖是在沒有重試和重定向的狀況下EventListener所能監控的Events流。下面是對應的代碼
class PrintingEventListener extends EventListener { private long callStartNanos; private void printEvent(String name) { long nowNanos = System.nanoTime(); if (name.equals("callStart")) { callStartNanos = nowNanos; } long elapsedNanos = nowNanos - callStartNanos; System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } @Override public void dnsStart(Call call, String domainName) { printEvent("dnsStart"); } @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) { printEvent("dnsEnd"); } ... }
上面Eventlistener只適用於沒有併發的狀況,若是有多個請求併發執行咱們須要使用Eventlistener. Factory來給每一個請求建立一個Eventlistener。
下面是一個給每一個請求建立一個帶惟一ID的Eventlistener的示例:
class PrintingEventListener extends EventListener { public static final Factory FACTORY = new Factory() { final AtomicLong nextCallId = new AtomicLong(1L); @Override public EventListener create(Call call) { long callId = nextCallId.getAndIncrement(); System.out.printf("%04d %s%n", callId, call.request().url()); return new PrintingEventListener(callId, System.nanoTime()); } }; final long callId; final long callStartNanos; public PrintingEventListener(long callId, long callStartNanos) { this.callId = callId; this.callStartNanos = callStartNanos; } private void printEvent(String name) { long elapsedNanos = System.nanoTime() - callStartNanos; System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } ... }
上面說的都是請求正常時event,接下來講下非正常狀況下的event流程。
當請求失敗時,將調用一個失敗方法 connectFailed () ,用於在創建到服務器的鏈接時發生故障,當 HTTP 請求永久失敗時調用 callFailed ()。 當發生故障時,有可能開始事件沒有相應的結束事件。
Okhttp是健壯的,能夠從一些鏈接故障中自動恢復。 在這種狀況下,connectFailed ()事件不是終結符,後面不跟 callFailed ()。 當嘗試重試時,事件偵聽器將收到多個相同類型的事件。 單個 HTTP 調用可能須要發出後續請求來處理身份驗證、重定向和 HTTP 層超時。 在這種狀況下,能夠嘗試多個鏈接、請求和響應。 重定向是單個請求可能觸發同一類型多個事件的另外一個緣由。
以上就是本文所有內容,接下來就是源碼分析了,不過建議在看源碼前把Okhttp的整個運行過程以及其中涉及的概念搞懂這樣看源碼纔會事半功倍。
原文出處:https://www.cnblogs.com/Robin132929/p/12151901.html