前言
前兩篇的文章講解了Volley,HttpURLConnection,今天是對於OKHttp的分析,分析完成將會分析OKIO和retrofit,試圖通過這一系列分析,來對Android的網路庫的實現有足夠充分的瞭解,當然這裡的分析完全是脫離了對於專案的具體針對性實踐,因此在一些細節上會有說欠缺,這也將會是接下來原始碼分析的下一步,從專案中的應用和實踐優化作為出發點。
基礎使用
- 建立OKHttpClient和構造請求
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.header("User-Agent", "OkHttp Headers.java")
.url("http://www.baidu.com")
.build();
複製程式碼
- 發起非同步請求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
}
});
複製程式碼
- 發起同步請求
okhttp3.Response response = client.newCall(request).execute();
複製程式碼
OkHttp的使用首先通過OkHttp的生成器來根據我們自己的配置建立一個OKHttpClient,然後構造一個請求,設定請求的header,url,請求的body,OkHttp提供了同步和非同步兩種請求執行方式。這裡可以根據自己的需求選擇一個合適的方式。
實現分析
按照Android每週一輪子的寫作風格,基礎使用作為一個引子,幫助我們迅速的切入框架的執行流程,快速的理清整個呼叫鏈路,瞭解該框架的實現,此處我們還是延續這種方式。針對上面的使用流程,逐步分析。
- newCall
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製程式碼
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
複製程式碼
根據建立的請求,構造一個RealCall例項,同時為該RealCall物件設定事件監聽器。在EventListener
中,定義了許多的函式,可以監控到網路請求的整個生命週期,包括DNS開始查詢,DNS查詢結束,連線的開始建立,連線建立失敗等等。
- enqueue
在連線建立之後,在非同步請求中,將會呼叫enqueue方法。
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製程式碼
在該方法中,會呼叫OkHttpClient的Dispatcher的enqueue
方法。在建立OkHttpClient的時候,但開發者未設定Dispatcher時,會預設建立一個Dispatcher。這裡按照預設的實現程式碼進行分析。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
複製程式碼
加入到Dispatchr之中之後,對於請求進行判斷,判斷是否超過了最大請求數目,是否超過了單個host共享下的請求數目,如果超過了則將其加入到準備執行佇列之中。如果請求數目過多的時候,將其放置在一個準備佇列之中。對於ArrayDeque的資料結構解釋文末。網路請求執行,是通過執行緒池來實現的,對於執行緒池的建立和具體的執行問題,將在文末具體分析。
在Dispatcher中用來管理請求的資料結構,分別為正在執行的非同步請求佇列,正在準備的非同步請求佇列,正在執行的同步請求佇列。
Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
複製程式碼
- execute
對於請求的具體執行過程,在AsyncCall的execute方法中。
final class AsyncCall extends NamedRunnable {
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
.....
} catch (IOException e) {
......
} finally {
client.dispatcher().finished(this);
}
}
}
複製程式碼
這裡首先獲得響應結果,在獲得響應結果的時候,可能會出現一些異常情況,這裡會catch到異常,會回撥事件監聽器的一些回撥函式。對於獲取請求響應結果的核心呼叫就是getResponseWithInterceptorChain
通過層層責任鏈的執行來獲得最終的請求結果。
- getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
複製程式碼
構建一個Interceptor列表,在其中新增使用者設定的攔截器,框架自身的快取,網路連線等等聯結器,然後根據這一系列的攔截器構建出一個Interceptor.Chain例項物件,之後呼叫其processed方法。
- 責任鏈的執行(proceed)
proceed的方法是網路請求執行的核心
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//如果已經有存在的流,確定進入的請求可以使用它
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 呼叫該鏈中的下一個攔截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//確認攔截器是否呼叫了chain的proceed方法。
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// 判斷響應是否為空,為空丟擲異常
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
//判斷響應體是否為空,丟擲異常返回內容體為空
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
複製程式碼
責任鏈的處理函式執行的操作大致為對於一些狀態進行判斷,然後取其中的攔截器構造一個攔截鏈例項,然後執行攔截器的攔截方法,在其攔截方法中還會繼續呼叫建立的新的RealChain方法的proceed方法,通過這種遞迴的方式來將資料進行層層包裝處理,最終將資料丟回。對於其中的每一個攔截器在完成整個網路請求的過程發揮了關鍵的作用。接下來從第一個攔截器開始逐層次進行分析。
RetryAndFollowUpInterceptor
這個攔截器可以將其從失敗和有必要的重定向中恢復。多少重定向和授權需要嘗試,Chrome會跟隨21次重定向,火狐,curl,wget會跟隨20次,Safari會跟隨16次,Http1.0推薦5次,這裡則為20次。
通過一個While true死迴圈來進行重試操作,在這裡建立StreamAllocation,然後建立新的攔截器鏈例項,然後呼叫其processed方法,等待返回結果回來。該攔截器在最外層,接下來的攔截器返回的響應結果,最終都會返回到這裡被處理。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
.....
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
//從失敗的路由中恢復
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
//跟隨次數判斷
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
複製程式碼
這裡通過異常捕捉的方式,根據相應的異常出錯,採取相應的恢復方式,同時記錄相應的出錯狀態,但達到閥值之後,停止進行拉起重試操作。此次網路請求失敗。
BridgeInterceptor
應用程式碼和網路程式碼之間的橋樑,用來根據使用者的一個請求構建一個網路請求,最後,根據網路響應來構建一個使用者響應。根據設定的一些頭部引數進行相應的處理。比如GZIP壓縮問題等等,根據傳遞引數構造RequestHeader,RequestBody。
public Response intercept(Chain chain) throws IOException {
//請求體的構建
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//Gzip 壓縮轉換
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//獲取響應結果,對響應結果進行包裝
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
複製程式碼
作為應用層和網路層的橋樑,其主要目的是在請求到達網路層時,對網路請求進行包裝和在網路請求的響應結果回來時,對響應結果進行包裝,轉到使用者層。
CacheInterceptor
用來檢測快取中是否有資料,有檢測無變化返回,否則網路請求後存放。
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//獲取請求的cache策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
}
// 如果我們既無法進行網路請求又無快取,返回504錯誤
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 如果我們不需要網路請求,執行完成返回結果
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//執行責任鏈,根據網路請求獲取響應結果
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 獲取請求響應後,根據cache狀態,對cache內容進行相應的處理
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//構造一個響應體
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
複製程式碼
ConnectInterceptor
根據傳遞的資料,尋找並建立一個健康的連線,同時也會根據設定的一些超時時間等資訊設定給響應的連線。再尋找該連線時候如果沒有與目標主機建立連線,其將會與目標主機建立連線。
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
複製程式碼
這裡的核心是在於Transmitter類,其作為網路和應用的一箇中間層,在建立RealCall的時候,就建立了該物件,接下來,我們看一下其newExchange方法做了什麼?
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
if (exchange != null) {
throw new IllegalStateException("cannot make a new request because the previous response "
+ "is still open: please call response.close()");
}
}
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
複製程式碼
Exchange是一個可以攜帶新的請求和響應的,首先通過ExchangeFinder來去查詢。
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
複製程式碼
在ExchangeFinder中的核心實現是呼叫了findHealthyConnection。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
複製程式碼
該方法會通過一個死迴圈來不斷的呼叫findConnecton來找到最終一個合適的連線。findConnection是連線複用與連線建立的核心實現。
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
複製程式碼
會首先從連線池中進行查詢,進行host的比對,如果有比對成功的,怎返回當前的連線,如果則會嘗試其它的route的ip,最終還是沒有找到的話,則會嘗試重新建立一個新的連線。
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
nextRouteToTry = selectedRoute;
} else {
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
複製程式碼
然後將建立的新的連線加入到連線池進行復用,Connection建立好之後,會呼叫其connect方法來進行連線的建立,首先判斷是否需要隧道,如果不需要則直接建立Socket連線。Socket連線建立好之後,呼叫establishProtocol,上面socket只是完成了TCP層面的連線建立,後續進行協議層的處理,同時每一個步驟都通過eventListner進行相應的事件回撥。
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
}
複製程式碼
Socket連線建立之後,其後續的都可以通過相應的包裝向socket中進行資料的寫入,對於Socket通過Okio進行包裝。
CallServerInterceptor
這個攔截是攔截鏈中的最後一個,資料的寫入過程,也就是發起網路請求和Server進行互動的過程,然後返回請求資料,這個時候再是層層的返回撥用棧,將資料倒回去,然後進行層層的處理。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//寫入請求體的頭部
exchange.writeRequestHeaders(request);
//寫入請求體
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
//從請求體中讀出資料
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
return response;
}
複製程式碼
這個部分我們核心需要關注的就是我們的請求資料是如何寫入到Socket的,伺服器返回的資料是如何被我們讀出來的。在連線的部分,我們已經知道建立一個連線之後,會通過Okio的包裝一個InputStream還有一個OutputStream,也就是Okio的Source和Sink。上述是一個縮減程式碼,省略了相關一些邏輯的實現,這裡主要表現對於請求頭的寫入,請求體的寫入,然後從返回的資料中獲取資料。其操作本質上是對於Socket包裝層的一個寫入流和讀出流的包裝。
相關類
- Transmitter
處在OkHttp應用層和網路層的中間,可以用來進行流的取消,可以取消流,但是不會影響共享連線池中的其它流。其中維護了ConnectionPool,Call,RealConnection。
- ConnectionPool
為了減少延遲,通過connectionPool來實現對於相同地址的連線的重用。其內部通過ArrayDeque維護了多個RealConnection。
- Dispatcher
每一個排程器內部都有一個執行緒池來進行任務的執行排程。
- RealCall
實際的請求執行
- ExchangeCodec
對於Http請求的包裝和對於Http請求返回結果的包裝。其中包含了一個Socket連線用來進行http1的資料傳輸,同時也包含了請求,同時封裝了對於Socket的讀寫操作。
- RealConnection
在建立Socket連線的時候,會通過OKIO來包裝Socket的,具備一個Sink,Source,
小結
本篇文章算不上對於原始碼的深度剖析,大概還是停留在表層的程式碼邏輯呼叫,對於其中用到的設計模式,各種技術,和其相比於其它網路庫的優勢所在,這裡暫時都沒有做分析,由於本週時間比較近,所以對於效能,優勢特徵,將會在接下來的一篇深度展開分析。同時下一篇也將會作為對OkIO分析的一個引子。