okhttp簡述

這篇文章主要講 Android 網絡請求時所使用到的各個請求庫的關系,以及 OkHttp3 的介紹。(如理解有誤,請大家幫忙指出)
建議先對 HTTP 協議有一個了解,「ruanyifeng」大神文章
HTTP 協議入門
互聯網協議入門(一)
互聯網協議入門(二)
簡介
開始一直不是很清楚各個 Android 網絡庫的本質區別,Android-async-http、HttpClient、HttpUrlConnection、Volley、OkHttp、Retrofit...等等,我們可以使用它們實現 Android 端的網絡請求,但是它們卻又與其他的庫略有不同
我們知道,要想實現網絡請求,就要發送我們的請求到服務器端,并接收響應。其中,上面所寫的庫中,HttpClient、HttpUrlConnection、OkHttp 是實現了 Http 協議的,可以幫助我們發送請求,接收響應,我們稱之為 Http 客戶端,而 Android-async-http、Volley、Retrofit 只是以這三個 Http 客戶端為底層實現,進而封裝的請求庫,這就是它們的區別
Android Http 客戶端
Http 客戶端:可以發出請求并接收響應,實現了 HTTP 協議。至于怎么實現,大致就是通過數據流傳遞 Request 信息,并在接收到 Response 數據流后,進行解析。那數據流是怎么發送呢,請返回頂端,看「ruanyifeng」大神的文章,這里面涉及到的是 DNS、TCP/IP、MAC 地址等信息,不在本文探討范圍之內。
Android 相關的 Http 客戶端發展到目前為止,以HttpClient
、HttpUrlConnection
、OkHttp
為主。
HttpClient:Apache 出品,Android 不建議使用,SDK 6.0 已經刪除了相關類。

HttpUrlConnection:輕量極的 HTTP 客戶端,API 簡單,易擴展,不過從 Android4.4 開始 HttpURLConnection 的底層實現采用的是 OkHttp。

OkHttp:高性能的 http 庫,支持同步、異步,而且實現了 spdy、http2、websocket 協議,api 簡潔易用。

其實我們甚至可以自己來實現 HTTP 協議,寫一個 HTTP 客戶端。
Android 網絡請求庫
網絡請求庫是對 Android Http 客戶端進行了進一步封裝,當然,稱其為網絡請求庫是為了與上面三個區分開來。網絡請求庫也可以稱為 HTTP 客戶端,它有客戶端的所要求的功能,但是其底層使用的就是上面三個客戶端其中的一種(基本上),雖然直接使用 Android Http 客戶端請求網絡亦無不可,不過封裝之后則更加方便我們使用
Android-async-http:異步網絡庫,底層使用的是 HttpClient,但是 Android 不建議使用 HttpClient,因此,該庫已不適合在 Android 開發中使用

Volley:異步網絡庫,在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在Android 2.2 及以下版本,使用的是 HttpClient。Btw,Volley 已經停止了更新。

Retrofit:一個 RESTful 的 HTTP 網絡請求框架的封裝。從 Retrofit 2.0 開始,內置 OkHttp,前者專注于接口的封裝,后者專注于網絡請求的高效

...

當然還有很多網絡請求庫,就不一一列舉了。
OkHttp
OkHttp 優勢
為什么說 OkHttp 高效呢?來看一下其官方介紹。
OkHttp 官方介紹
HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
OkHttp is an HTTP client that’s efficient by default:
HTTP/2 support allows all requests to the same host to share a socket.
Connection pooling reduces request latency (if HTTP/2 isn’t available).
Transparent GZIP shrinks download sizes.
Response caching avoids the network completely for repeat requests.

OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.
Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.
OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.


OkHttp 是一個高效的 HTTP 客戶端:
支持 HTTP/2 ,共享同一個Socket來處理同一個服務器的所有請求
如果 HTTP/2 不可用,則通過連接池來減少請求延時
無縫的支持 GZIP 來減少數據流量
緩存響應數據來減少重復的網絡請求

OkHttp 會從很多常用的連接問題中自動恢復。如果你的服務器配置了多個 IP 地址,當第一個 IP 連接失敗的時候,OkHttp 會自動嘗試下一個 IP。OkHttp 還處理了代理服務器問題和 SSL 握手失敗問題。
使用 OkHttp 無需重寫程序中的網絡代碼。OkHttp 實現了幾乎和 HttpURLConnection 一樣的 API 。如果使用了 Apache HttpClient,OkHttp 也提供了一個對應的 okhttp-apache 模塊。
OkHttp 支持 Android 2.3 以上版本,java 最低要求 1.7 版本
摘自:http://frodoking.github.io/2015/03/12/android-okhttp/
OkHttp 從調用看源碼
簡單調用:
OkHttpClient client = new OkHttpClient();String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string();}

看一下調用過程
創建OkHttpClient
對象
創建Request
對象
通過OkHttpClient
對象調用newCall()
方法,傳入創建好的Request
對象
執行execute()
方法,得到Response
對象。

OkHttpClient:
/** * Factory for calls, which can be used to send HTTP requests and read their responses. * ... * OkHttp performs best when you create a single OkHttpClient instance * ... /public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory{.../* * Prepares the {@code request} to be executed at some point in the future. /@Override public Call newCall(Request request) { return new RealCall(this, request, false / for web socket /);}/* * Uses {@code request} to connect a new web socket. */@Override public WebSocket newWebSocket(Request request, WebSocketListener listener) { RealWebSocket webSocket = new RealWebSocket(request, listener, new Random()); webSocket.connect(this); return webSocket;}...}

我們可以看到 源碼中OkHttpClient
實現了Call.Factory
,WebSocket.Factory
的接口,就是說,它可以新建一個Call
,也可以新建一個web socket
.
Factory for calls, which can be used to send HTTP requests and read their responses....

Call 的工廠類,Call 用來發送 Http 請求,并讀取響應
OkHttp performs best when you create a single OkHttpClient instance

同時,它實現了Cloneable
借口,源碼中 javadoc 里建議全局只有一個OkHttpClient
實例,如果我們使用clone()
方法得到一個新的實例,這個新實例的變化不會影響到原來的實例。
暫且不管OkHttpClient
中其他屬性和方法,我們再來看看Request

Request:
通過 Builder 模式,可以設置請求的url
、請求類型method
、請求頭域headers
、請求體body
、請求tag
、緩存要求cacheControl

新建Call
對象時,傳入該對象,根據設置的屬性發起請求。
再來看看Call

Call:
/** * A call is a request that has been prepared for execution. A call can be canceled. As this object * represents a single request/response pair (stream), it cannot be executed twice. */public interface Call extends Cloneable { // 返回創建 Call 時所傳入的 request Request request(); // 執行請求(同步) Response execute() throws IOException; // 執行請求(異步) void enqueue(Callback responseCallback); // 取消請求 void cancel(); // 請求是否正在執行 boolean isExecuted(); // 請求是否取消 boolean isCanceled(); // 克隆 Call 實例 Call clone(); interface Factory { Call newCall(Request request); }}

通過Call
的execute()
或enqueue(responseCallback)
方法發起請求。
RealCall
是Call
的實現類,我們來看一下關于execute()
方法的實現。
@Override public Response execute() throws IOException { // 判斷 execute() 方法是否已經被執行 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } // 獲取 Call 的堆棧信息,設置到 RetryAndFollowUpInterceptor 對象中,在里面創建 StreamAllocation 時用到 // RetryAndFollowUpInterceptor:請求失敗重試和重定向攔截器 captureCallStackTrace(); try { // 將該請求放入 Dispatcher 中,會加入一個 Deque(雙向隊列) 中,最后由 Dispatcher 分派請求任務 client.dispatcher().executed(this); // 通過攔截器后,獲取到 Response Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); }}

名詞解釋:
攔截器:觀察,修改以及可能短路的請求輸出和響應請求的回來。通常情況下攔截器用來添加,移除或者轉換請求或者回應的頭部信息。
攔截器可以控制在請求前后做一些操作,如打印日志等。
Dispatcher:異步請求策略,可以設置每個主機最大請求數(默認為5),和最大并發請求數(默認是64)。
其實不論是同步還是異步請求,都會經過 Dispatcher,不同點在于
同步
Dispatcher會在同步執行任務隊列中記錄當前被執行過得任務Call,同時在當前線程中去執行Call的getResponseWithInterceptorChain()方法,直接獲取當前的返回數據Response;

異步
首先來說一下Dispatcher,Dispatcher內部實現了懶加載無邊界限制的線程池方式,同時該線程池采用了SynchronousQueue這種阻塞隊列。SynchronousQueue每個插入操作必須等待另一個線程的移除操作,同樣任何一個移除操作都等待另一個線程的插入操作。因此此隊列內部其 實沒有任何一個元素,或者說容量是0,嚴格說并不是一種容器。由于隊列沒有容量,因此不能調用peek操作,因為只有移除元素時才有元素。顯然這是一種快速傳遞元素的方式,也就是說在這種情況下元素總是以最快的方式從插入者(生產者)傳遞給移除者(消費者),這在多任務隊列中是最快處理任務的方式。對于高頻繁請求的場景,無疑是最適合的。
異步執行是通過Call.enqueue(Callback responseCallback)來執行,在Dispatcher中添加一個封裝了Callback的Call的匿名內部類Runnable來執行當前的Call。這里一定要注意的地方這個AsyncCall是Call的匿名內部類。AsyncCall的execute方法仍然會回調到Call的getResponseWithInterceptorChain方法來完成請求,同時將返回數據或者狀態通過Callback來完成。

摘自:http://frodoking.github.io/2015/03/12/android-okhttp/
繼續往下看,調用了 getResponseWithInterceptorChain() 獲得 response,這個方法中,添加了各種攔截器,并調用 proceed() 開始執行攔截器
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. // 將所有的攔截器添加到 list 中,通過 Chain 的 chain.proceed(request) 方法開始執行攔截器 // 同時,所有的攔截器,除 CallServerInterceptor 外,都要調用 proceed() 方法,它是所有攔截器順序調用的關鍵,下面會解釋 List<Interceptor> interceptors = new ArrayList<>(); // 添加創建 OkHttpClient 時設置的自定義攔截器 interceptors.addAll(client.interceptors()); // 請求失敗重試和重定向攔截器 interceptors.add(retryAndFollowUpInterceptor); // 補全缺失的一些http header。對后續Interceptor的執行的影響主要為修改了Request。 interceptors.add(new BridgeInterceptor(client.cookieJar())); // 處理http緩存。對后續Interceptor的執行的影響為,若緩存中有所需請求的響應,則后續Interceptor不再執行。 interceptors.add(new CacheInterceptor(client.internalCache())); // 借助于前面分配的StreamAllocation對象建立與服務器之間的連接,并選定交互所用的協議是HTTP 1.1還是HTTP 2。對后續Interceptor的執行的影響為,創建了HttpStream和connection。 interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { // 添加網絡攔截器(上面是應用攔截器) interceptors.addAll(client.networkInterceptors()); } // Interceptor鏈中的最后一個Interceptor,用于處理IO,與服務器進行數據交換(不調用 proceed() 方法的攔截器,完成之后返回 Response) interceptors.add(new CallServerInterceptor(forWebSocket)); // 生成 Chain 對象,調用 proceed() 方法,在攔截器的實現中,會新建 Chain 對象,同時也會調用 proceed() 方法,類似遞歸的形式 Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest);}

我們再來看 Chain 對象中的 proceed() 方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException { ... // Call the next interceptor in the chain. // 咚咚咚 敲黑板,這邊就是攔截器順序調用的核心 // 新建一個 Chain 對象,獲取下一個攔截器,調用攔截器的 intercept() 方法 // 在攔截器實現的該方法中會再次調用 proceed() 方法,直到最后一個攔截器返回 response RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); ... return response;}

在最后的攔截器 CallServerInterceptor 中,將會請求服務器,返回 response。
以上是同步請求的過程,異步請求過程類似,不同點在于 Dispatcher 分派任務,異步請求使用 enqueue() 方法
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); // 添加任務到異步 Deque 中,會根據當前請求情況,決定是否當即進行請求 client.dispatcher().enqueue(new AsyncCall(responseCallback));}

Dispatcher.class
synchronized void enqueue(AsyncCall call) { // 判斷請求數等是否達到上限 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); // 通過 ExecutorService 執行請求 executorService().execute(call); } else { readyAsyncCalls.add(call); }}

RealCall.class(RealCall.AsyncCall)
final class AsyncCall extends NamedRunnable { ... // 異步請求執行后會調用到該方法 @Override protected void execute() { boolean signalledCallback = false; try { // 同樣通過 getResponseWithInterceptorChain() 方法獲取 response Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } }}

以上就是 OkHttp 調用過程,比較簡單,基本上就是調用 Call 執行方法,將請求添加到 Dispatcher,根據是否緩存從不同的源獲取數據,又根據同步還是異步,分別執行 execute() 返回 response 或者是添加到請求隊列,通過回調獲取 response
了解調用過程后,我們再分析其他 OkHttp 特性就有了方向
如 OkHttp 的失敗重連及重定向機制,可以從 RetryAndFollowUpInterceptor 入手,這個攔截器就是用來實現該功能的
如 緩存策略,我們可以從 CacheInterceptor 入手,其中用到了CacheStrategy
類,可以確定我們是用緩存數據、網絡數據還是兩個皆有,我們可以看這個類入手來了解 OkHttp 的緩存機制
等等

初研究 Android 網絡請求 與 OkHttp,記與此。
參考:
http://www.lxweimin.com/p/2fa728c8b366
OkHttp-Wiki
OkHttp-Wiki-譯(簡書)
http://frodoking.github.io/2015/03/12/android-okhttp/
http://www.lxweimin.com/p/5c98999bc34f

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,677評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,799評論 18 139
  • Okhttp使用指南與源碼分析 標簽(空格分隔): Android 使用指南篇# 為什么使用okhttp### A...
    背影殺手不太冷閱讀 8,174評論 2 119
  • 無法對你表白, 放任感情流逝好幾年。 愛你說不出口, 卡在欲說還休的怨言中。 我無能為力, 你不知我對你孤單的思念...
    菁鶴堂_劉昭川鶴閱讀 451評論 0 0
  • 有人說,失敗是成功之母。我想,失敗對于成功的作用自然不可或缺。但是,我個人認為一個人最大的成功是來源于他的自律,他...
    灰姑娘玻璃鞋閱讀 733評論 0 1