最近在看 Okhttp 的源碼。不得不說源碼設計的很巧妙,從中能學到不少。其實網上關於 Okhttp 的文章已經不少了,本身也看了不少。可是俗話說得好,好記性不如爛筆頭,當你動手的時候,你會發現你在看的時候沒有注意到的不少細節。緩存
本次要分析的 Okhttp 版本是 3.8.1,在 gradle 中引用以下:安全
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation 'com.squareup.okio:okio:1.7.0'
之因此選擇分析3.8.1,是由於最新版是採用 Kotlin 寫的,由於本人 Kotlin 實力不容許,因此只能分析 Java 版本。服務器
一、發起一個異步 GET 請求,代碼具體以下:cookie
String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .get()//默認就是GET請求,能夠不寫 .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: "); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "onResponse: " + response.body().string()); } });
二、發起一個同步 GET 請求,代碼具體以下:網絡
String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .build(); final Call call = okHttpClient.newCall(request); new Thread(new Runnable() { @Override public void run() { try { Response response = call.execute(); Log.d(TAG, "run: " + response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).start();
能夠看到兩個請求基本大同小異,總結下過程以下:app
先建立 OkHttpClient 實例;less
構造 Request 實例,傳入 url 等相關參數;dom
經過前兩步中的實例對象構建 Call 對象;異步
異步請求經過 Call#enqueue(Callback) 方法來提交異步請求,同步請求經過 Call#execute() 直接獲取 Reponse ;socket
經過示例,你們簡單瞭解 Okhttp 中的一些對象,下面開始梳理整個請求邏輯。先從 OkHttpClient 開始。
OkHttpClient
當咱們發起請求的時候,須要先構造 okHttpClient 對象,代碼具體以下:
public OkHttpClient() { this(new Builder()); }
能夠發現是使用了 builder 建造者模式;來看看裏面的內容
public Builder() { dispatcher = new Dispatcher(); // 調度器 protocols = DEFAULT_PROTOCOLS; // 協議 connectionSpecs = DEFAULT_CONNECTION_SPECS; //傳輸層版本和鏈接協議 eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); cookieJar = CookieJar.NO_COOKIES; //cookie socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; //證書鏈 proxyAuthenticator = Authenticator.NONE; //代理身份驗證 authenticator = Authenticator.NONE; //本地身份驗證 connectionPool = new ConnectionPool(); //連接池 複用鏈接 dns = Dns.SYSTEM; //域名 followSslRedirects = true; followRedirects = true; //本地重定向 retryOnConnectionFailure = true; connectTimeout = 10_000; readTimeout = 10_000; //讀取超時 writeTimeout = 10_000; //寫入超時 pingInterval = 0; }
Request
Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tag = builder.tag != null ? builder.tag : this; }
HTTP請求任務封裝。能夠說咱們能用到的操縱基本上都定義在這個接口裏面了,因此也能夠說這個類是 Okhttp 類的核心類了。咱們能夠經過 Call 對象來操做請求了。而 Call 接口內部提供了 Factory 工廠方法模式 (將對象的建立延遲到工廠類的子類去進行,從而實現動態配置),下面是 Call 接口的具體內容:
public interface Call extends Cloneable { /** Returns the original request that initiated this call. */ Request request(); /** * Invokes the request immediately, and blocks until the response can be processed or is in * error. * * <p>To avoid leaking resources callers should close the {@link Response} which in turn will * close the underlying {@link ResponseBody}. * * <pre>@{code * * // ensure the response (and underlying response body) is closed * try (Response response = client.newCall(request).execute()) { * ... * } * * }</pre> * * <p>The caller may read the response body with the response's {@link Response#body} method. To * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the * Response. * * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does * not necessarily indicate application-layer success: {@code response} may still indicate an * unhappy HTTP response code like 404 or 500. * * @throws IOException if the request could not be executed due to cancellation, a connectivity * problem or timeout. Because networks can fail during an exchange, it is possible that the * remote server accepted the request before the failure. * @throws IllegalStateException when the call has already been executed. */ Response execute() throws IOException; /** * Schedules the request to be executed at some point in the future. * * <p>The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually * immediately unless there are several other requests currently being executed. * * <p>This client will later call back {@code responseCallback} with either an HTTP response or a * failure exception. * * @throws IllegalStateException when the call has already been executed. */ void enqueue(Callback responseCallback); /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */ void cancel(); /** * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain * #enqueue(Callback) enqueued}. It is an error to execute a call more than once. */ boolean isExecuted(); boolean isCanceled(); /** * Create a new, identical call to this one which can be enqueued or executed even if this call * has already been. */ Call clone(); interface Factory { Call newCall(Request request); } }
RealCall 繼承自 Call,是真正發起請求的的實體類。RealCall 主要方法:
同步請求 :client.newCall(request).execute();
異步請求: client.newCall(request).enqueue();
下面咱們來看看裏面具體的內容:
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { final EventListener.Factory eventListenerFactory = client.eventListenerFactory(); this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // TODO(jwilson): this is unsafe publication and not threadsafe. this.eventListener = eventListenerFactory.create(this); }
能夠發現,其內部持有了 client,原始請求,以及請求事件回調 Listener 等。咱們看下請求的回調 Listener 的具體內容:
public void fetchStart(Call call) { } public void dnsStart(Call call, String domainName) { } public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList, Throwable throwable) { } public void connectStart(Call call, InetAddress address, int port) { } public void secureConnectStart(Call call) { } public void secureConnectEnd(Call call, Handshake handshake, Throwable throwable) { } public void connectEnd(Call call, InetAddress address, int port, String protocol, Throwable throwable) { } public void requestHeadersStart(Call call) { } public void requestHeadersEnd(Call call, Throwable throwable) { } public void requestBodyStart(Call call) { } public void requestBodyEnd(Call call, Throwable throwable) { } public void responseHeadersStart(Call call) { } public void responseHeadersEnd(Call call, Throwable throwable) { } public void responseBodyStart(Call call) { } public void responseBodyEnd(Call call, Throwable throwable) { } public void fetchEnd(Call call, Throwable throwable) { }
能夠看到 OkHttp 的回調作得很是細緻,有各類各樣的回調,無論你想不想用,都幫你考慮到了呢。這樣咱們能夠監聽具體的回調,而後作一些操做。
接下去就要開始講異步請求的具體步驟呢。先從異步請求講起,這也是咱們最經常使用的。
// RealCall @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
能夠看到上述代碼作了幾件事:
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. @Override public RealCall clone() { return RealCall.newRealCall(client, originalRequest, forWebSocket); }
心細的讀者可能發現一個問題了,那就是這裏 enqueue 明明是一個封裝了 responseCallback 的 AsyncCall ,怎麼就會變成加入隊列執行請求了呢?這個下面我會進行解釋。
Dispatcher 是 Okhttp 的調度器,用來管理控制請求的隊列。內部經過線程池來確保隊列的有序運行。先看下 enqueue 方法的具體內容:
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
能夠看到內部存在兩個隊列,一個是正在運行的隊列 runningAsyncCalls,另外一個是 readyAsyncCalls 隊列。若是當前運行數小於最大運行數,而且當前請求的host小於最大請求個數,那麼就會直接加入運行隊列,並運行。若是超了,就會加入準備隊列。
/** Ready async calls in the order they'll be run. */ private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
實際上還有一個同步隊列,沒有給同步隊列作限制,只要一加入就開始執行請求。
當請求隊列完成請求後須要進行移除,看下 finished 的代碼邏輯:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } }
能夠看到,這是使用了泛型,不用關心具體傳入的隊列是哪個,直接就能夠移除。promoteCalls 爲 true 表明是異步請求隊列,還得從 readyAsyncCalls 隊列裏面取出一個隊列添加到 runningAsyncCalls 隊列裏面去執行請求。
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. } }
經過上述代碼,關於調度器的功能做用就基本理清了。
AsyncCall 是 RealCall 裏面的內部類,繼承自 NamedRunnable,是自定義的Runnable,能夠爲線程設置 name。內部代碼具體以下:
public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); }
能夠發現,在 run 方法內部調用了execute 方法,這個方法就是真正的發起請求的邏輯。下面咱們看下 AsyncCall 中的該方法得具體內容:
@Override protected void execute() { boolean signalledCallback = false; 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) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } } }
獲取響應數據最終是是經過 getResponseWithInterceptorChain() 來獲取的。而後經過回調將 Response 返回給用戶。
下面看下 getResponseWithInterceptorChain 方法內部的具體邏輯:
//Realcall 核心代碼 開始真正的執行網絡請求 Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. // 責任鏈 List<Interceptor> interceptors = new ArrayList<>(); // 在配置okhttpClient 時設置的intercept 由用戶本身設置 interceptors.addAll(client.interceptors()); // 負責處理失敗後的重試與重定向 interceptors.add(retryAndFollowUpInterceptor); // 負責把用戶構造的請求轉換爲發送到服務器的請求 、把服務器返回的響應轉換爲用戶友好的響應 處理 配置請求頭等信息 // 從應用程序代碼到網絡代碼的橋樑。首先,它根據用戶請求構建網絡請求。而後它繼續呼叫網絡。最後,它根據網絡響應構建用戶響應。 interceptors.add(new BridgeInterceptor(client.cookieJar())); // 處理 緩存配置 根據條件(存在響應緩存並被設置爲不變的或者響應在有效期內)返回緩存響應 // 設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改) // 可配置用戶本身設置的緩存攔截器 interceptors.add(new CacheInterceptor(client.internalCache())); // 鏈接服務器 負責和服務器創建鏈接 這裏纔是真正的請求網絡 interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { // 配置okhttpClient 時設置的networkInterceptors // 返回觀察單個網絡請求和響應的不可變攔截器列表。 interceptors.addAll(client.networkInterceptors()); } // 執行流操做(寫出請求體、得到響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據 // 進行http請求報文的封裝與請求報文的解析 interceptors.add(new CallServerInterceptor(forWebSocket)); // 建立責任鏈 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); //TODO 執行責任鏈 return chain.proceed(originalRequest); }
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request) { this.interceptors = interceptors; this.connection = connection; this.streamAllocation = streamAllocation; this.httpCodec = httpCodec; this.index = index; this.request = request; } // 責任鏈處理 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; // If we already have a stream, confirm that the incoming request will use it. 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 we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must call proceed() exactly once"); } // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } return response; }
首先是看構造函數,內部持有了當前責任鏈的全部攔截器list,還包括 RealConnection,index (當前正在處理攔截器索引)等。
接下去看 proceed 方法裏的邏輯,歸來起來就是以下:
首先是經過 index 和 calls 來作了一些安全判斷,避免重複處理,。
將索引號 index +1,新建立一個 chain。
根據目前的 index 獲取攔截器,而後將新的 chain 傳入到獲取攔截器中。
攔截器作完本身的操做後,會調用新建立的 chain 的 proceed 方法,交由下一個攔截器來處理。
當數據返回後,從後往前,攔截器會依次對數據作一些處理,最終用戶得到請求的數據。
經過上述往復循環,最終全部的攔截器都會走兩遍,一次是對請求體作操做,一次是對返回體作操做,最終用戶得到處理後的數據。
下面來看一個具體的攔截器 。
CacheInterceptor 代碼比較長,咱們一步一步的來進行分析。
首先咱們先分析上部分代碼當沒有網絡的狀況下是如何處理獲取緩存的。
@Override public Response intercept(Chain chain) throws IOException { // 獲取request對應緩存的Response 若是用戶沒有配置緩存攔截器 cacheCandidate == null Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; // 執行響應緩存策略 long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); // 若是networkRequest == null 則說明不使用網絡請求 Request networkRequest = strategy.networkRequest; // 獲取緩存中(CacheStrategy)的Response Response cacheResponse = strategy.cacheResponse; if (cache != null) { cache.trackResponse(strategy); } // 緩存無效 關閉資源 if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail. // networkRequest == null 不實用網路請求 且沒有緩存 cacheResponse == null 返回失敗 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 we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } }
上述的代碼,主要作了幾件事:
若是用戶本身配置了緩存攔截器,cacheCandidate = cache.Response 獲取用戶本身存儲的 Response,不然 cacheCandidate = null,同時從 CacheStrategy 獲取cacheResponse 和 networkRequest;
若是 cacheCandidate != null 而 cacheResponse == null 說明緩存無效清楚 cacheCandidate 緩存。
若是 networkRequest == null 說明沒有網絡,cacheResponse == null 沒有緩存,返回失敗的信息,責任鏈此時也就終止,不會在往下繼續執行。
若是 networkRequest == null 說明沒有網絡,cacheResponse != null 有緩存,返回緩存的信息,責任鏈此時也就終止,不會在往下繼續執行。
上部分代碼,其實就是沒有網絡的時候的處理。那麼下部分代碼確定是,有網絡的時候處理:
// 執行下一個攔截器 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()); } } // 網絡請求 回來 更新緩存 // If we have a cache response too, then we're doing a conditional get. // 若是存在緩存 更新 if (cacheResponse != null) { // 304響應碼 自從上次請求後,請求須要響應的內容未發生改變 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(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } // 緩存Response 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; }
上面的代碼主要作了這幾件事:
執行下一個攔截器,也就是請求網絡
責任鏈執行完畢後,會返回最終響應數據,若是緩存存在更新緩存,若是緩存不存在加入到緩存中去。
這就跟前面講的對應上了,請求前作一些處理,好比判斷緩存是否存在,網絡是否可用等操做;數據回來以後,更新緩存,在傳給上一個攔截器去作處理。
這樣就體現出了責任鏈的好處了,當責任鏈執行完畢,若是攔截器想要拿到最終的數據作其餘的邏輯處理等,這樣就不用在作其餘的調用方法邏輯了,直接在當前的攔截器就能夠拿到最終的數據。這也是okhttp設計的最優雅最核心的功能。
到這裏,異步請求邏輯基本就梳理完了。
同步請求會直接調用 Call#ececute 方法,記住這個 execute 方法的返回實體是 Reponse,因此它直接返回了請求。
// RealCall // 同步執行請求 直接返回一個請求的結果 @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); // 調用監聽的開始方法 eventListener.callStart(this); try { // 添加到隊列中去 client.dispatcher().executed(this); // 獲取請求的返回數據 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { // 執行調度器的完成方法 移除隊列 client.dispatcher().finished(this); } }
主要作了幾件事:
synchronized (this) 避免重複執行,上面的文章部分有講。
client.dispatcher().executed(this),實際上調度器只是將 call 加入到了同步執行隊列中。
getResponseWithInterceptorChain() 最核心的代碼,至關於同步請求直接就開始運行,請求網絡獲得響應數據,返回給用戶
client.dispatcher().finished(this); 執行調度器的完成方法 移除隊列
能夠看出,在同步請求的方法中,涉及到 dispatcher 只是告知了執行狀態,開始執行了(調用 executed),執行完畢了(調用 finished)其餘的並無涉及到。dispatcher 更多的是服務異步請求。
以上就是對 Okhttp 請求流程的梳理,後面附一張盜的流程圖