這篇文章主要講 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