摘要:異步請求當(dāng)正在運行的異步請求隊列中的數(shù)量小于并且正在運行的請求主機(jī)數(shù)小于時則把請求加載到中并在線程池中執(zhí)行,否則就再入到中進(jìn)行緩存等待。通常情況下攔截器用來添加,移除或者轉(zhuǎn)換請求或者響應(yīng)的頭部信息。
前言
學(xué)會了OkHttp3的用法后,我們當(dāng)然有必要來了解下OkHttp3的源碼,當(dāng)然現(xiàn)在網(wǎng)上的文章很多,我仍舊希望我這一系列文章篇是最簡潔易懂的。
1.從請求處理開始分析首先OKHttp3如何使用這里就不在贅述了,不明白的同學(xué)可以查看Android網(wǎng)絡(luò)編程(五)OkHttp2.x用法全解析、
Android網(wǎng)絡(luò)編程(六)OkHttp3用法全解析這兩篇文章。當(dāng)我們要請求網(wǎng)絡(luò)的時候我們需要用OkHttpClient.newCall(request)進(jìn)行execute或者enqueue操作,當(dāng)我們調(diào)用newCall時:
@Override public Call newCall(Request request) { return new RealCall(this, request); }
實際返回的是一個RealCall類,我們調(diào)用enqueue異步請求網(wǎng)絡(luò)實際上是調(diào)用了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任務(wù)調(diào)度 主要的變量Dispatcher主要用于控制并發(fā)的請求,它主要維護(hù)了以下變量:
/** 最大并發(fā)請求數(shù)*/ private int maxRequests = 64; /** 每個主機(jī)最大請求數(shù)*/ private int maxRequestsPerHost = 5; /** 消費者線程池 */ private ExecutorService executorService; /** 將要運行的異步請求隊列 */ private final Deque構(gòu)造函數(shù)readyAsyncCalls = new ArrayDeque<>(); /**正在運行的異步請求隊列 */ private final Deque runningAsyncCalls = new ArrayDeque<>(); /** 正在運行的同步請求隊列 */ private final Deque 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(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }
Dispatcher有兩個構(gòu)造函數(shù),可以使用自己設(shè)定線程池,如果沒有設(shè)定線程池則會在請求網(wǎng)絡(luò)前自己創(chuàng)建線程池,這個線程池類似于CachedThreadPool比較適合執(zhí)行大量的耗時比較少的任務(wù)。不了解線程池的同學(xué)可以查看Android多線程(一)線程池這篇文章。其中用到了SynchronousQueue,不了解它的同學(xué)可以查看Java并發(fā)編程(六)阻塞隊列這篇文章。
異步請求synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
當(dāng)正在運行的異步請求隊列中的數(shù)量小于64并且正在運行的請求主機(jī)數(shù)小于5時則把請求加載到runningAsyncCalls中并在線程池中執(zhí)行,否則就再入到readyAsyncCalls中進(jìn)行緩存等待。
AsyncCall線程池中傳進(jìn)來的參數(shù)就是AsyncCall它是RealCall的內(nèi)部類,內(nèi)部也實現(xiàn)了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); } }
首先我們來看看最后一行, 無論這個請求的結(jié)果如何都會執(zhí)行client.dispatcher().finished(this);
synchronized void finished(AsyncCall call) { if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn"t running!"); promoteCalls(); }
finished方法將此次請求從runningAsyncCalls移除后還執(zhí)行了promoteCalls方法:
private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iteratori = 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. } }
可以看到最關(guān)鍵的點就是會從readyAsyncCalls取出下一個請求,并加入runningAsyncCalls中并交由線程池處理。好了讓我們再回到上面的AsyncCall的execute方法,我們會發(fā)getResponseWithInterceptorChain方法返回了Response,很明顯這是在請求網(wǎng)絡(luò)。
3.Interceptor攔截器private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException { Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket); return chain.proceed(originalRequest); }
getResponseWithInterceptorChain方法,創(chuàng)建了ApplicationInterceptorChain,它是一個攔截器鏈,這個類也是RealCall的內(nèi)部類,接下來執(zhí)行了它的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方法每次從攔截器列表中取出攔截器,當(dāng)存在多個攔截器時都會在第七行阻塞,并等待下一個攔截器的調(diào)用返回。下面分別以 攔截器鏈中有1個、2個攔截器的場景加以模擬:
攔截器主要用來觀察,修改以及可能短路的請求輸出和響應(yīng)的回來。通常情況下攔截器用來添加,移除或者轉(zhuǎn)換請求或者響應(yīng)的頭部信息。比如將域名替換為ip地址,將請求頭中添加host屬性,也可以添加我們應(yīng)用中的一些公共參數(shù),比如設(shè)備id、版本號等等。 不了解攔截器的可以查看Okhttp-wiki 之 Interceptors 攔截器這篇文章。
回到代碼上來,我們看最后一行 return getResponse(request, forWebSocket),如果沒有更多的攔截器的話,就會執(zhí)行網(wǎng)絡(luò)請求,來看看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方法比較長我省略了一些代碼,可以看到創(chuàng)建了HttpEngine類并且調(diào)用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在初始化的時候會去讀取緩存目錄中關(guān)于曾經(jīng)請求過的所有信息。 InternalCache responseCache = Internal.instance.internalCache(client); //cacheCandidate為上次與服務(wù)器交互緩存的Response Response cacheCandidate = responseCache != null ? responseCache.get(request) : null; long now = System.currentTimeMillis(); //創(chuàng)建CacheStrategy.Factory對象,進(jìn)行緩存配置 cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(); //網(wǎng)絡(luò)請求 networkRequest = cacheStrategy.networkRequest; //緩存的響應(yīng) cacheResponse = cacheStrategy.cacheResponse; if (responseCache != null) { //記錄當(dāng)前請求是網(wǎng)絡(luò)發(fā)起還是緩存發(fā)起 responseCache.trackResponse(cacheStrategy); } if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn"t applicable. Close it. } //不進(jìn)行網(wǎng)絡(luò)請求并且緩存不存在或者過期則返回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; } // 不進(jìn)行網(wǎng)絡(luò)請求,而且緩存可以使用,直接返回緩存 if (networkRequest == null) { userResponse = cacheResponse.newBuilder() .request(userRequest) .priorResponse(stripBody(priorResponse)) .cacheResponse(stripBody(cacheResponse)) .build(); userResponse = unzip(userResponse); return; } //需要訪問網(wǎng)絡(luò)時 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()); } } }
上面的代碼顯然是在發(fā)送請求,但是最主要的是做了緩存的策略。cacheCandidate是上次與服務(wù)器交互緩存的Response,這里的緩存都是基于Map,key是請求中url的md5,value是在文件中查詢到的緩存,頁面置換基于LRU算法,我們現(xiàn)在只需要知道它是一個可以讀取緩存Header的Response即可。根據(jù)cacheStrategy的處理得到了networkRequest和cacheResponse這兩個值,根據(jù)這兩個值的數(shù)據(jù)是否為null來進(jìn)行進(jìn)一步的處理,當(dāng)networkRequest和cacheResponse都為null的情況也就是不進(jìn)行網(wǎng)絡(luò)請求并且緩存不存在或者過期,這時候則返回504錯誤;當(dāng)networkRequest 為null時也就是不進(jìn)行網(wǎng)絡(luò)請求,而且緩存可以使用時則直接返回緩存;其他的情況則請求網(wǎng)絡(luò)。
接下來我們查看readResponse方法:
public void readResponse() throws IOException { ...省略 else{ //讀取網(wǎng)絡(luò)響應(yīng) networkResponse = readNetworkResponse(); } //將響應(yīng)頭部存入Cookie中 receiveHeaders(networkResponse.headers()); // If we have a cache response too, then we"re doing a conditional get. if (cacheResponse != null) { //檢查緩存是否可用,如果可用。那么就用當(dāng)前緩存的Response,關(guān)閉網(wǎng)絡(luò)連接,釋放連接。 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)); } }
這個方法發(fā)起刷新請求頭部和請求體,解析HTTP響應(yīng)頭部。如果有緩存并且可用則用緩存的數(shù)據(jù)并更新緩存,否則就用網(wǎng)絡(luò)請求返回的數(shù)據(jù)。
我們再來看看validate(cacheResponse, networkResponse)方法是如何判斷緩存是否可用的:
private static boolean validate(Response cached, Response network) { //如果服務(wù)器返回304則緩存有效 if (network.code() == HTTP_NOT_MODIFIED) { return true; } //通過緩存和網(wǎng)絡(luò)請求響應(yīng)中的Last-Modified來計算是否是最新數(shù)據(jù),如果是則緩存有效 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; }
如緩存果過期或者強(qiáng)制放棄緩存,在此情況下,緩存策略全部交給服務(wù)器判斷,客戶端只用發(fā)送條件get請求即可,如果緩存是有效的,則返回304 Not Modifiled,否則直接返回body。條件get請求有兩種方式一種是Last-Modified-Date,一種是 ETag。這里采用了Last-Modified-Date,通過緩存和網(wǎng)絡(luò)請求響應(yīng)中的Last-Modified來計算是否是最新數(shù)據(jù),如果是則緩存有效。
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行當(dāng)發(fā)生IOException或者RouteException時會執(zhí)行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); }
最后一行可以看到就是重新創(chuàng)建了HttpEngine并返回,用來完成重連。
到這里OkHttp請求網(wǎng)絡(luò)的流程基本上講完了,下面是關(guān)于OKHttp的請求流程圖:
參考資料:
http://www.jianshu.com/p/aad5aacd79bf
http://www.jianshu.com/p/64e256c1dbbf
http://www.cnblogs.com/LuLei1990/p/5534791.html
http://frodoking.github.io/2015/03/12/android-okhttp/
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/70587.html
摘要:創(chuàng)建增加返回值為的支持這里的加上之前定義的參數(shù)形成完整的請求地址用于指定返回的參數(shù)數(shù)據(jù)類型,這里我們支持和類型。完整的代碼如下增加返回值為的支持請求參數(shù)上文講了訪問網(wǎng)絡(luò)的基本方法,接下來我們來了解下常用的請求參數(shù)。 前言 Retrofit是Square公司開發(fā)的一款針對Android網(wǎng)絡(luò)請求的框架,Retrofit2底層基于OkHttp實現(xiàn)的,而OkHttp現(xiàn)在已經(jīng)得到Google官方...
摘要:使用前準(zhǔn)備配置添加網(wǎng)絡(luò)權(quán)限異步請求慣例,請求百度可以省略,默認(rèn)是請求請求成功與版本并沒有什么不同,比較郁悶的是回調(diào)仍然不在線程。 前言 上一篇介紹了OkHttp2.x的用法,這一篇文章我們來對照OkHttp2.x版本來看看,OkHttp3使用起來有那些變化。當(dāng)然,看這篇文章前建議看一下前一篇文章Android網(wǎng)絡(luò)編程(五)OkHttp2.x用法全解析。 1.使用前準(zhǔn)備 Android ...
摘要:需要注意的是回調(diào)并不是在線程。也可以通過來同時取消多個請求。在開始創(chuàng)建的時候配置好,在請求網(wǎng)絡(luò)的時候用將請求的結(jié)果回調(diào)給線程。最后調(diào)用這個的方法請求成功使用起來簡單多了,而且請求結(jié)果回調(diào)是在線程的。 前言 講完了Volley,我們接下來看看目前比較火的網(wǎng)絡(luò)框架OkHttp, 它處理了很多網(wǎng)絡(luò)疑難雜癥:會從很多常用的連接問題中自動恢復(fù)。如果您的服務(wù)器配置了多個IP地址,當(dāng)?shù)谝粋€IP連接失...
閱讀 3944·2021-11-16 11:44
閱讀 3123·2021-11-12 10:36
閱讀 3381·2021-10-08 10:04
閱讀 1266·2021-09-03 10:29
閱讀 406·2019-08-30 13:50
閱讀 2615·2019-08-29 17:14
閱讀 1742·2019-08-29 15:32
閱讀 1087·2019-08-29 11:27