前言
學會了OkHttp3的用法后,我們當然有必要來了解下OkHttp3的源碼,當然現在網上的文章很多,我仍舊希望我這一系列文章篇是最簡潔易懂的。
1.從請求處理開始分析
首先OKHttp3如何使用這里就不在贅述了,不明白的同學可以查看Android網絡編程(五)OkHttp2.x用法全解析、
Android網絡編程(六)OkHttp3用法全解析這兩篇文章。當我們要請求網絡的時候我們需要用OkHttpClient.newCall(request)進行execute或者enqueue操作,當我們調用newCall時:
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
實際返回的是一個RealCall類,我們調用enqueue異步請求網絡實際上是調用了RealCall的enqueue方法:
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
可以看到最終的請求是dispatcher來完成的。
2.Dispatcher任務調度
主要的變量
Dispatcher主要用于控制并發的請求,它主要維護了以下變量:
/** 最大并發請求數*/
private int maxRequests = 64;
/** 每個主機最大請求數*/
private int maxRequestsPerHost = 5;
/** 消費者線程池 */
private ExecutorService executorService;
/** 將要運行的異步請求隊列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在運行的異步請求隊列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在運行的同步請求隊列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
構造函數
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
Dispatcher有兩個構造函數,可以使用自己設定線程池,如果沒有設定線程池則會在請求網絡前自己創建線程池,這個線程池類似于CachedThreadPool比較適合執行大量的耗時比較少的任務。不了解線程池的同學可以查看Android多線程(一)線程池這篇文章。其中用到了SynchronousQueue,不了解它的同學可以查看Java并發編程(六)阻塞隊列這篇文章。
異步請求
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
當正在運行的異步請求隊列中的數量小于64并且正在運行的請求主機數小于5時則把請求加載到runningAsyncCalls中并在線程池中執行,否則就再入到readyAsyncCalls中進行緩存等待。
AsyncCall
線程池中傳進來的參數就是AsyncCall它是RealCall的內部類,內部也實現了execute方法:
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
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!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
首先我們來看看最后一行, 無論這個請求的結果如何都會執行client.dispatcher().finished(this);
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();
}
finished方法將此次請求從runningAsyncCalls移除后還執行了promoteCalls方法:
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
可以看到最關鍵的點就是會從readyAsyncCalls取出下一個請求,并加入runningAsyncCalls中并交由線程池處理。好了讓我們再回到上面的AsyncCall的execute方法,我們會發getResponseWithInterceptorChain方法返回了Response,很明顯這是在請求網絡。
3.Interceptor攔截器
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
getResponseWithInterceptorChain方法,創建了ApplicationInterceptorChain,它是一個攔截器鏈,這個類也是RealCall的內部類,接下來執行了它的proceed方法:
@Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
//從攔截器列表取出攔截器
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
proceed方法每次從攔截器列表中取出攔截器,當存在多個攔截器時都會在第七行阻塞,并等待下一個攔截器的調用返回。下面分別以 攔截器鏈中有1個、2個攔截器的場景加以模擬:
攔截器主要用來觀察,修改以及可能短路的請求輸出和響應的回來。通常情況下攔截器用來添加,移除或者轉換請求或者響應的頭部信息。比如將域名替換為ip地址,將請求頭中添加host屬性,也可以添加我們應用中的一些公共參數,比如設備id、版本號等等。 不了解攔截器的可以查看Okhttp-wiki 之 Interceptors 攔截器這篇文章。
回到代碼上來,我們看最后一行 return getResponse(request, forWebSocket),如果沒有更多的攔截器的話,就會執行網絡請求,來看看getResponse方法做了些什么(RealCall.java):
Response getResponse(Request request, boolean forWebSocket) throws IOException {
...省略
// Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
int followUpCount = 0;
while (true) {
if (canceled) {
engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
boolean releaseConnection = true;
try {
engine.sendRequest();
engine.readResponse();
releaseConnection = false;
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
...省略
}
}
getResponse方法比較長我省略了一些代碼,可以看到創建了HttpEngine類并且調用HttpEngine的sendRequest方法和readResponse方法。
4.緩存策略
我們先來看看sendRequest方法:
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
//請求頭部添加
Request request = networkRequest(userRequest);
//獲取client中的Cache,同時Cache在初始化的時候會去讀取緩存目錄中關于曾經請求過的所有信息。
InternalCache responseCache = Internal.instance.internalCache(client);
//cacheCandidate為上次與服務器交互緩存的Response
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
long now = System.currentTimeMillis();
//創建CacheStrategy.Factory對象,進行緩存配置
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
//網絡請求
networkRequest = cacheStrategy.networkRequest;
//緩存的響應
cacheResponse = cacheStrategy.cacheResponse;
if (responseCache != null) {
//記錄當前請求是網絡發起還是緩存發起
responseCache.trackResponse(cacheStrategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//不進行網絡請求并且緩存不存在或者過期則返回504錯誤
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
// 不進行網絡請求,而且緩存可以使用,直接返回緩存
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}
//需要訪問網絡時
boolean success = false;
try {
httpStream = connect();
httpStream.setHttpEngine(this);
if (writeRequestHeadersEagerly()) {
long contentLength = OkHeaders.contentLength(request);
if (bufferRequestBody) {
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if (contentLength != -1) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}
上面的代碼顯然是在發送請求,但是最主要的是做了緩存的策略。cacheCandidate是上次與服務器交互緩存的Response,這里的緩存都是基于Map,key是請求中url的md5,value是在文件中查詢到的緩存,頁面置換基于LRU算法,我們現在只需要知道它是一個可以讀取緩存Header的Response即可。根據cacheStrategy的處理得到了networkRequest和cacheResponse這兩個值,根據這兩個值的數據是否為null來進行進一步的處理,當networkRequest和cacheResponse都為null的情況也就是不進行網絡請求并且緩存不存在或者過期,這時候則返回504錯誤;當networkRequest 為null時也就是不進行網絡請求,而且緩存可以使用時則直接返回緩存;其他的情況則請求網絡。
接下來我們查看readResponse方法:
public void readResponse() throws IOException {
...省略
else{
//讀取網絡響應
networkResponse = readNetworkResponse();
}
//將響應頭部存入Cookie中
receiveHeaders(networkResponse.headers());
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
//檢查緩存是否可用,如果可用。那么就用當前緩存的Response,關閉網絡連接,釋放連接。
if (validate(cacheResponse, networkResponse)) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
// 更新緩存
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
}
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}
這個方法發起刷新請求頭部和請求體,解析HTTP響應頭部。如果有緩存并且可用則用緩存的數據并更新緩存,否則就用網絡請求返回的數據。
我們再來看看validate(cacheResponse, networkResponse)方法是如何判斷緩存是否可用的:
private static boolean validate(Response cached, Response network) {
//如果服務器返回304則緩存有效
if (network.code() == HTTP_NOT_MODIFIED) {
return true;
}
//通過緩存和網絡請求響應中的Last-Modified來計算是否是最新數據,如果是則緩存有效
Date lastModified = cached.headers().getDate("Last-Modified");
if (lastModified != null) {
Date networkLastModified = network.headers().getDate("Last-Modified");
if (networkLastModified != null
&& networkLastModified.getTime() < lastModified.getTime()) {
return true;
}
}
return false;
}
如緩存果過期或者強制放棄緩存,在此情況下,緩存策略全部交給服務器判斷,客戶端只用發送條件get請求即可,如果緩存是有效的,則返回304 Not Modifiled,否則直接返回body。條件get請求有兩種方式一種是Last-Modified-Date,一種是 ETag。這里采用了Last-Modified-Date,通過緩存和網絡請求響應中的Last-Modified來計算是否是最新數據,如果是則緩存有效。
5.失敗重連
最后我們再回到RealCall的getResponse方法:
Response getResponse(Request request, boolean forWebSocket) throws IOException {
...省略
boolean releaseConnection = true;
try {
engine.sendRequest();
engine.readResponse();
releaseConnection = false;
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e.getLastConnectException();
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
HttpEngine retryEngine = engine.recover(e, null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
StreamAllocation streamAllocation = engine.close();
streamAllocation.release();
}
}
...省略
engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
response);
}
}
查看代碼第11行和21行當發生IOException或者RouteException時會執行HttpEngine的recover方法:
public HttpEngine recover(IOException e, Sink requestBodyOut) {
if (!streamAllocation.recover(e, requestBodyOut)) {
return null;
}
if (!client.retryOnConnectionFailure()) {
return null;
}
StreamAllocation streamAllocation = close();
// For failure recovery, use the same route selector with a new connection.
return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
}
最后一行可以看到就是重新創建了HttpEngine并返回,用來完成重連。
到這里OkHttp請求網絡的流程基本上講完了,下面是關于OKHttp的請求流程圖:
參考資料:
http://www.lxweimin.com/p/aad5aacd79bf
http://www.lxweimin.com/p/64e256c1dbbf
http://www.cnblogs.com/LuLei1990/p/5534791.html
http://frodoking.github.io/2015/03/12/android-okhttp/