編輯:關於Android編程
學會了OkHttp3的用法後,我們當然有必要來了解下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來完成的。
Dispatcher主要用於控制並發的請求,它主要維護了以下變量:
/** 最大並發請求數*/ private int maxRequests = 64; /** 每個主機最大請求數*/ private int maxRequestsPerHost = 5; /** 消費者線程池 */ private ExecutorService executorService; /** 將要運行的異步請求隊列 */ private final Deque readyAsyncCalls = new ArrayDeque<>(); /**正在運行的異步請求隊列 */ private final Deque runningAsyncCalls = new ArrayDeque<>(); /** 正在運行的同步請求隊列 */ private final DequerunningSyncCalls = 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有兩個構造函數,可以使用自己設定線程池,如果沒有設定線程池則會在請求網絡前自己創建線程池,這個線程池類似於CachedThreadPool比較適合執行大量的耗時比較少的任務。
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它是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 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,很明顯這是在請求網絡。
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方法。
我們先來看看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來計算是否是最新數據,如果是則緩存有效。
最後我們再回到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的請求流程圖:
眾所周知,一般情況下我們使用android中的monkeyrunner進行自動化測試時,使用的是python語言來寫測試腳本。不過,最近發現可以用java調用monkey
RecyclerView是一種列表容器, 發布很久了, 才想起來寫點什麼.RecyclerView相比於ListView, 在回收重用時更具有靈活性, 也就是低耦合, 並
Earthquake(地震顯示器) 項目 詳解 環境: Android Studio 0.5.2, Gradle 1.11, kindle f
天氣說變就變,馬上又變冷了,還好空氣不錯,陽光也不錯,早起上班的車上的人也不多,公司來的同事和昨天一樣一樣的,可能明天會多一些吧,那就再來學習android吧。學了兩個a