OkHttp
做爲優秀的網絡請求框架,已經獲得了廣大Android開發者的承認。對於它的使用方法,你們也是很是的熟悉。例如同步同步請求、異步請求等,均可以使用很簡潔的邏輯來實現。因爲OkHttp
已經封裝了繁瑣複雜的請求邏輯,開發者只須要使用其提供的API就能輕鬆的實現網絡請求,這使得開發者能將更多的精力放到業務開發上,提升了工做效率。html
可是,做爲一位有追求的Android開發者,不能一味的衣來伸手飯來張口。雖然不至於要本身動手,豐衣足食,可是咱們也要作到知其然更知其因此然,從優秀的基礎框架中學習其設計思想、架構思想,這對咱們的成長也是有很是大的幫助的。設計模式
下面咱們就以OkHttp
爲例,從源碼的角度對其總體流程進行分析,以便能從中學習到其設計思路與工做原理。緩存
下面是OkHttp發起請求與數據響應的流程圖(參考拆輪子系列:拆 OkHttp這篇文章畫的)。bash
總體來講,是OkHttpClient
經過newCall
方法,進而觸發RealCall的execute
(同步)、enquene
(異步)方法,通過一系列的interceptors
(攔截器),最後通過IO操做發起請求獲得Response
(響應數據)。cookie
下面針對上述OkHttp
的總體工做流程,從源碼的角度分析其中的原理,咱們首先從同步請求提及。網絡
OkHttp
的同步請求示例以下所示。架構
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
複製代碼
OkHttpClient
有個內部類Builder
,它的build
方法能夠生成一個OkHttpClient
實例。但OkHttpClient
提供了一個構造函數,讓咱們可使用默認的配置來建立OkHttpClient
實例。框架
OkHttpClient
實例的newCall
方法接收一個Request
對象,該對象由Request
的內部類Builder
構造出來。異步
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
複製代碼
newCall
方法中經過RealCall
又調用了newRealCall
方法,並返回RealCall
對象。也就是說,實際上執行的是RealCall
的execute
方法。async
public Response execute() throws IOException {
synchronized (this) { // 1
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain(); // 2
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
複製代碼
一、每個RealCall
對象的execute
方法只能執行一次,屢次執行會拋出IllegalStateException
異常。
二、經過執行getResponseWithInterceptorChain
方法同步返回了Response
對象。從上面的總體架構可知,getResponseWithInterceptorChain
方法是OkHttp發起網絡請求的重點部分,咱們接着往下面看。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
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);
}
}
複製代碼
getResponseWithInterceptorChain
方法中的往List中加了不少攔截器,最後經過構造了RealInterceptorChain
對象,並執行它的proceed
方法返回一個Response
對象。該方法的工做流程以下圖所示。
這就是所謂的責任鏈模式,每個節點負責本身的一部分工做,最後組裝成一個完整的請求對象發送網絡請求並返回Response
。對於其中的每個節點(攔截器),咱們經過源碼進一步分析其中的細節。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...省略
//生成下一個節點
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);
...省略
return response;
}
複製代碼
RealInterceptorChain
的proceed
方法主要作了兩個操做:
一、生成了下一個節點對象next
,類型爲RealInterceptorChain
。
二、調用了當前攔截器的intercept
方法,並將next
對象傳遞給該攔截器。從上述流程圖中,咱們認定當前攔截器是RetryAndFollowUpInterceptor
。
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) { //這裏死循環的意思是會一直重試,直到遇到return或者拋出異常後纔會跳出
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
...
continue;
} catch (IOException e) {
...
continue;
} finally {
...
}
...
Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) { //不須要重試,直接返回Response對象
streamAllocation.release();
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) { //重試次數加1,重試次數大於最大次數,釋放資源
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//繼續重試,直到重試次數大於最大重試次數
request = followUp;
priorResponse = response;
}
}
複製代碼
RetryAndFollowUpInterceptor
,從它的名字也能看出來,其主要目的是爲失敗重試和重定向而工做的。因此它的intercept
方法主要利用了while
循環進行屢次的請求重試,只有當重試次數大於最大重試次數時纔會跳出while
循環。
RetryAndFollowUpInterceptor
的intercept
方法將沿着責任鏈,從而執行到了BridgeInterceptor
的intercept
方法。
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder(); //構造了一個新的Request.Builder對象
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");
}
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();
}
複製代碼
BridgeInterceptor
的攔截方法要作的事情並很少,主要目的是判斷網絡請求Request
對象header
對象的字段爲空時,構造一個默認對象,並且在網絡數據返回後,將返回的Response
對象進一步的封裝並返回。
BridgeInterceptor
的intercept
方法又將網絡請求繼續分發,它的下一個攔截器則是CacheInterceptor
。從CacheInterceptor
的名字與其註釋可知,該攔截器的主要功能是獲取緩存的Response
以及將網絡請求返回Response
存儲到緩存中。
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
...省略
//沒有網絡,則使用緩存
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());
}
}
// 有緩存,若是返回的Response和緩存比對後沒有改變,則返回緩存
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) { //緩存改變了,則從新寫入新的Response到緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
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;
}
複製代碼
從上面的interceptor
方法中能夠看出,該方法主要作了如下事情。
一、判斷是否須要返回緩存。(通常無網絡時會返回緩存)
二、有網絡的狀況下,Request
請求正常發送,判斷返回的Response
內容是否有更新。沒有更新,則返回緩存內容;有更新,則返回新的Response
,並將新的Response
內容寫入到緩存中。
CacheInterceptor
攔截器的下一個責任鏈節點是ConnectInterceptor
攔截器。
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
ConnectInterceptor
攔截器的intercept
方法主要負責創建鏈接,也就是建立了HttpCodec
對象。HttpCodec
是一個接口類,有兩個實現類,分別爲Http1Codec
和Http2Codec
。Http1Codec
使用的是Http1.0
版本的協議發送網絡請求,而Http2Codec
使用的是Http2.0
版本的協議發送網絡請求。從HttpCodec
接口方法和其實現邏輯來看,其中主要封裝了Java的IO
操做,經過stream
字節流參與網絡請求的發送與接收過程。
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
...省略
return response;
}
複製代碼
CallServerInterceptor
做爲最後一個攔截器,其主要利用了HttpCodec
的readResponseHeaders
方法獲取Response
數據,並將Response
返回。
至此咱們分析完了OkHttp
中關於同步強求的總體流程。其中特別重要的是OkHttp
中的攔截器分層原理,也就是所謂的責任鏈設計模式。OkHttp
的請求會將通過攔截器一層層的分發,直到有攔截器將Response
進行返回。而返回的Response
也會傳遞到以前的每個攔截器,每個攔截器對該Response
進行加工封裝,最後造成一個統一的Response
對象返回。
OkHttp
的異步請求示例以下所示。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//請求失敗的回調
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//請求成功的回調
}
});
複製代碼
異步請求調用了okHttpClient.newCall
方法,從上面的同步請求分析能夠知道,okHttpClient.newCall
返回一個RealCall
對象,也就是說異步請求其實調用的是RealCall的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));
}
複製代碼
client.dispatcher
返回一個Dispatcher
對象。該對象的功能如其名字同樣,它會將請求進行管理並分發,其中的enqueue
方法以下所示。
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
複製代碼
enqueue
方法將AsyncCall對象加入了readyAsyncCalls
隊列,而後執行promoteAndExecute
方法。
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
複製代碼
promoteAndExecute
方法執行時,會比較readyAsyncCalls
隊列中的請求對象個數是否大於maxRequests
的值,若是readyAsyncCalls
隊列的請求對象個數小於maxRequests
,則將這些請求對象加入到executableCalls
列表中,而後遍歷每個AsyncCall
對象,執行它的executeOn
方法。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
複製代碼
executeOn
方法執行的邏輯是:經過ExecutorService
(線程池)執行每個AsyncCall
請求對象,因此相應的AsyncCall
對象的run
方法會被執行,而run
方法調用了execute
方法。
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
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) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
複製代碼
在execute
方法中,經過getResponseWithInterceptorChain
方法返回Response
對象。這裏的getResponseWithInterceptorChain
方法執行過程在同步請求時已經分析完了,這裏再也不重複說明。
至此,異步請求流程也已經分析完畢。和同步請求流程相對比,異步請求流程比同步流程多了一步也就是將請求對象進行分發並放到線程池中去執行,至於攔截器分層、發起網絡請求、數據返回流程等都是同樣的。
OkHttp做爲一個優秀的網絡請求庫,其主要運用責任鏈模式、分層思想、單一職責思想等設計模式與思想,經過每一層攔截器對請求Request和返回Response進行封裝。隱藏了繁瑣複雜的網絡請求流程,提供簡潔的API供開發者調用。經過源碼解析,咱們瞭解並學習了其中的設計原理與總體流程,作到了知其然也知其因此然。