這篇文章主要講 Android 網(wǎng)絡(luò)請(qǐng)求時(shí)所使用到的各個(gè)請(qǐng)求庫(kù)的關(guān)系,以及 OkHttp3 的介紹。(如理解有誤,請(qǐng)大家?guī)兔χ赋?
建議先對(duì) HTTP 協(xié)議有一個(gè)了解,「ruanyifeng」大神文章
HTTP 協(xié)議入門
互聯(lián)網(wǎng)協(xié)議入門(一)
互聯(lián)網(wǎng)協(xié)議入門(二)
簡(jiǎn)介
開始一直不是很清楚各個(gè) Android 網(wǎng)絡(luò)庫(kù)的本質(zhì)區(qū)別,Android-async-http、HttpClient、HttpUrlConnection、Volley、OkHttp、Retrofit...等等,我們可以使用它們實(shí)現(xiàn) Android 端的網(wǎng)絡(luò)請(qǐng)求,但是它們卻又與其他的庫(kù)略有不同
我們知道,要想實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,就要發(fā)送我們的請(qǐng)求到服務(wù)器端,并接收響應(yīng)。其中,上面所寫的庫(kù)中,HttpClient、HttpUrlConnection、OkHttp 是實(shí)現(xiàn)了 Http 協(xié)議的,可以幫助我們發(fā)送請(qǐng)求,接收響應(yīng),我們稱之為 Http 客戶端,而 Android-async-http、Volley、Retrofit 只是以這三個(gè) Http 客戶端為底層實(shí)現(xiàn),進(jìn)而封裝的請(qǐng)求庫(kù),這就是它們的區(qū)別
Android Http 客戶端
Http 客戶端:可以發(fā)出請(qǐng)求并接收響應(yīng),實(shí)現(xiàn)了 HTTP 協(xié)議。至于怎么實(shí)現(xiàn),大致就是通過數(shù)據(jù)流傳遞 Request 信息,并在接收到 Response 數(shù)據(jù)流后,進(jìn)行解析。那數(shù)據(jù)流是怎么發(fā)送呢,請(qǐng)返回頂端,看「ruanyifeng」大神的文章,這里面涉及到的是 DNS、TCP/IP、MAC 地址等信息,不在本文探討范圍之內(nèi)。
Android 相關(guān)的 Http 客戶端發(fā)展到目前為止,以HttpClient
、HttpUrlConnection
、OkHttp
為主。
HttpClient:Apache 出品,Android 不建議使用,SDK 6.0 已經(jīng)刪除了相關(guān)類。
HttpUrlConnection:輕量極的 HTTP 客戶端,API 簡(jiǎn)單,易擴(kuò)展,不過從 Android4.4 開始 HttpURLConnection 的底層實(shí)現(xiàn)采用的是 OkHttp。
OkHttp:高性能的 http 庫(kù),支持同步、異步,而且實(shí)現(xiàn)了 spdy、http2、websocket 協(xié)議,api 簡(jiǎn)潔易用。
其實(shí)我們甚至可以自己來實(shí)現(xiàn) HTTP 協(xié)議,寫一個(gè) HTTP 客戶端。
Android 網(wǎng)絡(luò)請(qǐng)求庫(kù)
網(wǎng)絡(luò)請(qǐng)求庫(kù)是對(duì) Android Http 客戶端進(jìn)行了進(jìn)一步封裝,當(dāng)然,稱其為網(wǎng)絡(luò)請(qǐng)求庫(kù)是為了與上面三個(gè)區(qū)分開來。網(wǎng)絡(luò)請(qǐng)求庫(kù)也可以稱為 HTTP 客戶端,它有客戶端的所要求的功能,但是其底層使用的就是上面三個(gè)客戶端其中的一種(基本上),雖然直接使用 Android Http 客戶端請(qǐng)求網(wǎng)絡(luò)亦無不可,不過封裝之后則更加方便我們使用
Android-async-http:異步網(wǎng)絡(luò)庫(kù),底層使用的是 HttpClient,但是 Android 不建議使用 HttpClient,因此,該庫(kù)已不適合在 Android 開發(fā)中使用
Volley:異步網(wǎng)絡(luò)庫(kù),在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在Android 2.2 及以下版本,使用的是 HttpClient。Btw,Volley 已經(jīng)停止了更新。
Retrofit:一個(gè) RESTful 的 HTTP 網(wǎng)絡(luò)請(qǐng)求框架的封裝。從 Retrofit 2.0 開始,內(nèi)置 OkHttp,前者專注于接口的封裝,后者專注于網(wǎng)絡(luò)請(qǐng)求的高效
...
當(dāng)然還有很多網(wǎng)絡(luò)請(qǐng)求庫(kù),就不一一列舉了。
OkHttp
OkHttp 優(yōu)勢(shì)
為什么說 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 是一個(gè)高效的 HTTP 客戶端:
支持 HTTP/2 ,共享同一個(gè)Socket來處理同一個(gè)服務(wù)器的所有請(qǐng)求
如果 HTTP/2 不可用,則通過連接池來減少請(qǐng)求延時(shí)
無縫的支持 GZIP 來減少數(shù)據(jù)流量
緩存響應(yīng)數(shù)據(jù)來減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求
OkHttp 會(huì)從很多常用的連接問題中自動(dòng)恢復(fù)。如果你的服務(wù)器配置了多個(gè) IP 地址,當(dāng)?shù)谝粋€(gè) IP 連接失敗的時(shí)候,OkHttp 會(huì)自動(dòng)嘗試下一個(gè) IP。OkHttp 還處理了代理服務(wù)器問題和 SSL 握手失敗問題。
使用 OkHttp 無需重寫程序中的網(wǎng)絡(luò)代碼。OkHttp 實(shí)現(xiàn)了幾乎和 HttpURLConnection 一樣的 API 。如果使用了 Apache HttpClient,OkHttp 也提供了一個(gè)對(duì)應(yīng)的 okhttp-apache 模塊。
OkHttp 支持 Android 2.3 以上版本,java 最低要求 1.7 版本
摘自:http://frodoking.github.io/2015/03/12/android-okhttp/
OkHttp 從調(diào)用看源碼
簡(jiǎn)單調(diào)用:
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();}
看一下調(diào)用過程
創(chuàng)建OkHttpClient
對(duì)象
創(chuàng)建Request
對(duì)象
通過OkHttpClient
對(duì)象調(diào)用newCall()
方法,傳入創(chuàng)建好的Request
對(duì)象
執(zhí)行execute()
方法,得到Response
對(duì)象。
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
實(shí)現(xiàn)了Call.Factory
,WebSocket.Factory
的接口,就是說,它可以新建一個(gè)Call
,也可以新建一個(gè)web socket
.
Factory for calls, which can be used to send HTTP requests and read their responses....
Call 的工廠類,Call 用來發(fā)送 Http 請(qǐng)求,并讀取響應(yīng)
OkHttp performs best when you create a single OkHttpClient instance
同時(shí),它實(shí)現(xiàn)了Cloneable
借口,源碼中 javadoc 里建議全局只有一個(gè)OkHttpClient
實(shí)例,如果我們使用clone()
方法得到一個(gè)新的實(shí)例,這個(gè)新實(shí)例的變化不會(huì)影響到原來的實(shí)例。
暫且不管OkHttpClient
中其他屬性和方法,我們?cè)賮砜纯碦equest
Request:
通過 Builder 模式,可以設(shè)置請(qǐng)求的url
、請(qǐng)求類型method
、請(qǐng)求頭域headers
、請(qǐng)求體body
、請(qǐng)求tag
、緩存要求cacheControl
新建Call
對(duì)象時(shí),傳入該對(duì)象,根據(jù)設(shè)置的屬性發(fā)起請(qǐng)求。
再來看看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 { // 返回創(chuàng)建 Call 時(shí)所傳入的 request Request request(); // 執(zhí)行請(qǐng)求(同步) Response execute() throws IOException; // 執(zhí)行請(qǐng)求(異步) void enqueue(Callback responseCallback); // 取消請(qǐng)求 void cancel(); // 請(qǐng)求是否正在執(zhí)行 boolean isExecuted(); // 請(qǐng)求是否取消 boolean isCanceled(); // 克隆 Call 實(shí)例 Call clone(); interface Factory { Call newCall(Request request); }}
通過Call
的execute()
或enqueue(responseCallback)
方法發(fā)起請(qǐng)求。
RealCall
是Call
的實(shí)現(xiàn)類,我們來看一下關(guān)于execute()
方法的實(shí)現(xiàn)。
@Override public Response execute() throws IOException { // 判斷 execute() 方法是否已經(jīng)被執(zhí)行 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } // 獲取 Call 的堆棧信息,設(shè)置到 RetryAndFollowUpInterceptor 對(duì)象中,在里面創(chuàng)建 StreamAllocation 時(shí)用到 // RetryAndFollowUpInterceptor:請(qǐng)求失敗重試和重定向攔截器 captureCallStackTrace(); try { // 將該請(qǐng)求放入 Dispatcher 中,會(huì)加入一個(gè) Deque(雙向隊(duì)列) 中,最后由 Dispatcher 分派請(qǐng)求任務(wù) client.dispatcher().executed(this); // 通過攔截器后,獲取到 Response Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); }}
名詞解釋:
攔截器:觀察,修改以及可能短路的請(qǐng)求輸出和響應(yīng)請(qǐng)求的回來。通常情況下攔截器用來添加,移除或者轉(zhuǎn)換請(qǐng)求或者回應(yīng)的頭部信息。
攔截器可以控制在請(qǐng)求前后做一些操作,如打印日志等。
Dispatcher:異步請(qǐng)求策略,可以設(shè)置每個(gè)主機(jī)最大請(qǐng)求數(shù)(默認(rèn)為5),和最大并發(fā)請(qǐng)求數(shù)(默認(rèn)是64)。
其實(shí)不論是同步還是異步請(qǐng)求,都會(huì)經(jīng)過 Dispatcher,不同點(diǎn)在于
同步
Dispatcher會(huì)在同步執(zhí)行任務(wù)隊(duì)列中記錄當(dāng)前被執(zhí)行過得任務(wù)Call,同時(shí)在當(dāng)前線程中去執(zhí)行Call的getResponseWithInterceptorChain()方法,直接獲取當(dāng)前的返回?cái)?shù)據(jù)Response;
異步
首先來說一下Dispatcher,Dispatcher內(nèi)部實(shí)現(xiàn)了懶加載無邊界限制的線程池方式,同時(shí)該線程池采用了SynchronousQueue這種阻塞隊(duì)列。SynchronousQueue每個(gè)插入操作必須等待另一個(gè)線程的移除操作,同樣任何一個(gè)移除操作都等待另一個(gè)線程的插入操作。因此此隊(duì)列內(nèi)部其 實(shí)沒有任何一個(gè)元素,或者說容量是0,嚴(yán)格說并不是一種容器。由于隊(duì)列沒有容量,因此不能調(diào)用peek操作,因?yàn)橹挥幸瞥貢r(shí)才有元素。顯然這是一種快速傳遞元素的方式,也就是說在這種情況下元素總是以最快的方式從插入者(生產(chǎn)者)傳遞給移除者(消費(fèi)者),這在多任務(wù)隊(duì)列中是最快處理任務(wù)的方式。對(duì)于高頻繁請(qǐng)求的場(chǎng)景,無疑是最適合的。
異步執(zhí)行是通過Call.enqueue(Callback responseCallback)來執(zhí)行,在Dispatcher中添加一個(gè)封裝了Callback的Call的匿名內(nèi)部類Runnable來執(zhí)行當(dāng)前的Call。這里一定要注意的地方這個(gè)AsyncCall是Call的匿名內(nèi)部類。AsyncCall的execute方法仍然會(huì)回調(diào)到Call的getResponseWithInterceptorChain方法來完成請(qǐng)求,同時(shí)將返回?cái)?shù)據(jù)或者狀態(tài)通過Callback來完成。
摘自:http://frodoking.github.io/2015/03/12/android-okhttp/
繼續(xù)往下看,調(diào)用了 getResponseWithInterceptorChain() 獲得 response,這個(gè)方法中,添加了各種攔截器,并調(diào)用 proceed() 開始執(zhí)行攔截器
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. // 將所有的攔截器添加到 list 中,通過 Chain 的 chain.proceed(request) 方法開始執(zhí)行攔截器 // 同時(shí),所有的攔截器,除 CallServerInterceptor 外,都要調(diào)用 proceed() 方法,它是所有攔截器順序調(diào)用的關(guān)鍵,下面會(huì)解釋 List<Interceptor> interceptors = new ArrayList<>(); // 添加創(chuàng)建 OkHttpClient 時(shí)設(shè)置的自定義攔截器 interceptors.addAll(client.interceptors()); // 請(qǐng)求失敗重試和重定向攔截器 interceptors.add(retryAndFollowUpInterceptor); // 補(bǔ)全缺失的一些http header。對(duì)后續(xù)Interceptor的執(zhí)行的影響主要為修改了Request。 interceptors.add(new BridgeInterceptor(client.cookieJar())); // 處理http緩存。對(duì)后續(xù)Interceptor的執(zhí)行的影響為,若緩存中有所需請(qǐng)求的響應(yīng),則后續(xù)Interceptor不再執(zhí)行。 interceptors.add(new CacheInterceptor(client.internalCache())); // 借助于前面分配的StreamAllocation對(duì)象建立與服務(wù)器之間的連接,并選定交互所用的協(xié)議是HTTP 1.1還是HTTP 2。對(duì)后續(xù)Interceptor的執(zhí)行的影響為,創(chuàng)建了HttpStream和connection。 interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { // 添加網(wǎng)絡(luò)攔截器(上面是應(yīng)用攔截器) interceptors.addAll(client.networkInterceptors()); } // Interceptor鏈中的最后一個(gè)Interceptor,用于處理IO,與服務(wù)器進(jìn)行數(shù)據(jù)交換(不調(diào)用 proceed() 方法的攔截器,完成之后返回 Response) interceptors.add(new CallServerInterceptor(forWebSocket)); // 生成 Chain 對(duì)象,調(diào)用 proceed() 方法,在攔截器的實(shí)現(xiàn)中,會(huì)新建 Chain 對(duì)象,同時(shí)也會(huì)調(diào)用 proceed() 方法,類似遞歸的形式 Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest);}
我們?cè)賮砜?Chain 對(duì)象中的 proceed() 方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException { ... // Call the next interceptor in the chain. // 咚咚咚 敲黑板,這邊就是攔截器順序調(diào)用的核心 // 新建一個(gè) Chain 對(duì)象,獲取下一個(gè)攔截器,調(diào)用攔截器的 intercept() 方法 // 在攔截器實(shí)現(xiàn)的該方法中會(huì)再次調(diào)用 proceed() 方法,直到最后一個(gè)攔截器返回 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 中,將會(huì)請(qǐng)求服務(wù)器,返回 response。
以上是同步請(qǐng)求的過程,異步請(qǐng)求過程類似,不同點(diǎn)在于 Dispatcher 分派任務(wù),異步請(qǐng)求使用 enqueue() 方法
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); // 添加任務(wù)到異步 Deque 中,會(huì)根據(jù)當(dāng)前請(qǐng)求情況,決定是否當(dāng)即進(jìn)行請(qǐng)求 client.dispatcher().enqueue(new AsyncCall(responseCallback));}
Dispatcher.class
synchronized void enqueue(AsyncCall call) { // 判斷請(qǐng)求數(shù)等是否達(dá)到上限 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); // 通過 ExecutorService 執(zhí)行請(qǐng)求 executorService().execute(call); } else { readyAsyncCalls.add(call); }}
RealCall.class(RealCall.AsyncCall)
final class AsyncCall extends NamedRunnable { ... // 異步請(qǐng)求執(zhí)行后會(huì)調(diào)用到該方法 @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 調(diào)用過程,比較簡(jiǎn)單,基本上就是調(diào)用 Call 執(zhí)行方法,將請(qǐng)求添加到 Dispatcher,根據(jù)是否緩存從不同的源獲取數(shù)據(jù),又根據(jù)同步還是異步,分別執(zhí)行 execute() 返回 response 或者是添加到請(qǐng)求隊(duì)列,通過回調(diào)獲取 response
了解調(diào)用過程后,我們?cè)俜治銎渌?OkHttp 特性就有了方向
如 OkHttp 的失敗重連及重定向機(jī)制,可以從 RetryAndFollowUpInterceptor 入手,這個(gè)攔截器就是用來實(shí)現(xiàn)該功能的
如 緩存策略,我們可以從 CacheInterceptor 入手,其中用到了CacheStrategy
類,可以確定我們是用緩存數(shù)據(jù)、網(wǎng)絡(luò)數(shù)據(jù)還是兩個(gè)皆有,我們可以看這個(gè)類入手來了解 OkHttp 的緩存機(jī)制
等等
結(jié)
初研究 Android 網(wǎng)絡(luò)請(qǐng)求 與 OkHttp,記與此。
參考:
http://www.itdecent.cn/p/2fa728c8b366
OkHttp-Wiki
OkHttp-Wiki-譯(簡(jiǎn)書)
http://frodoking.github.io/2015/03/12/android-okhttp/
http://www.itdecent.cn/p/5c98999bc34f