Okhttp原始碼分析

版本號:3.13.1
一.基本使用
//1.建立OkHttpClient物件 val okHttpClient = OkHttpClient.Builder().readTimeout(5,TimeUnit.SECONDS).build() //2.建立Request物件 val request = Request.Builder().url("www.baidu.com").build() //3.通過OkHttpClient將Request封裝成Call物件 val call = okHttpClient.newCall(request) //通過Call執行請求 //同步請求 val response = call.execute() Log.d("okhttp",response.body().toString()) //非同步請求 call.enqueue(object :Callback{ override fun onFailure(call: Call, e: IOException) { } override fun onResponse(call: Call, response: Response) { Log.d("okhttp",response.body().toString()) } })
Call可以理解為 Request 和 Response 之間的橋樑,Http請求過程中可能有 重定向和重試 等操作,你的一個簡單請求可能會產生多個請求和響應。 OkHttp 使用 Call 這一概念對此來建模: 不論為了滿足你的請求任務,中間做了多少次請求和響應,都算作一個Call 。
二.原始碼分析
1.建立物件原始碼分析
不管是 同步請求 還是 非同步請求 ,都必須先建立 OkHttpClient 和 Request 物件,上面使用 Build模式 建立的,下面分別看一下各自的原始碼:
OkHttpClient.Builder().
public Builder() { //任務分發器 dispatcher = new Dispatcher(); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); if (proxySelector == null) { proxySelector = new NullProxySelector(); } cookieJar = CookieJar.NO_COOKIES; 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; callTimeout = 0; //java7以後在數字中可以使用下劃線,只是增加閱讀性,沒其他作用 connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; }
Request.Builder()
public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); }
build()方法,都是在該方法中建立各自的物件,在構造方法中將當前的 build物件 傳入,然後把對應的屬性值賦。下面以 Request 為例:
public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); } //Builder模式 Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tags = Util.immutableMap(builder.tags); }
不管同步請求還是非同步請求,都是呼叫Call的方法執行的,下面看一下Call物件的建立 okHttpClient.newCall(request)
:
@Override public Call newCall(Request request) { //Call是一個介面,RealCall是它的實現類 return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. //建立RealCall物件,將client和request傳入 RealCall call = new RealCall(client, originalRequest, forWebSocket); //設定監聽器 call.eventListener = client.eventListenerFactory().create(call); return call; } private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; //重定向攔截器 this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client); this.timeout = new AsyncTimeout() { @Override protected void timedOut() { cancel(); } }; this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS); }
從上面程式碼中可以看到 Call 的實現類為 RealCall ,它持有 client 和 request 。
上面建立物件的原始碼已經分析完了,下面就看一下具體請求的方法。
2.同步請求:call.execute()
@Override public Response execute() throws IOException { synchronized (this) { //同一個請求執行執行一遍,否則跑出異常 if (executed) throw new IllegalStateException("Already Executed"); executed = true; } ...... //當執行的請求開始的時候,回撥監聽中的方法 eventListener.callStart(this); try { //真正的請求是dispatcher.executed client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; }...... finally { //執行完成以後從對列中移除請求 client.dispatcher().finished(this); } } public Dispatcher dispatcher() { return dispatcher; } //dispatcher.executed synchronized void executed(RealCall call) { //將call加入到同步請求對列中 runningSyncCalls.add(call); } public final class Dispatcher { ...... //非同步就緒對列 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //非同步執行對列 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //同步執行對列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); ...... }
同步請求呼叫 realCall.executed
方法,在該方法中呼叫 dispatcher.executed
將 realCall 新增到同步執行對列中 runningSyncCalls
然後呼叫 getResponseWithInterceptorChain
獲取響應報文。

3.非同步請求: call.enqueue
@Override public void enqueue(Callback responseCallback) { synchronized (this) { //當前的call(建立的call)只能執行一次 if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); //封裝成了AsyncCall,它就是一個Runable client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
void enqueue(AsyncCall call) { synchronized (this) { //新增到就緒對列 readyAsyncCalls.add(call); ...... } } 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(); //如果請求大於最大的請求數 maxRequests = 64,不執行 if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. //請求的host不能大於maxRequestsPerHost = 5 if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); asyncCall.callsPerHost().incrementAndGet(); //沒有大於最大請求數,新增到執行對列中 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; }
非同步請求的時候,通過 dispatcher.enqueue
方法將call(封裝成了Runable(AsyncCall,它是RealCall的的內部類))新增到就緒對列中,然後迴圈就緒對列,如果現在執行的任務數沒有超過最大的請求數(64)就新增到執行對列中,然後執行 asyncCall.executeOn(executorService());
。
public synchronized ExecutorService executorService() { if (executorService == null) { //最大的執行緒數為 Integer.MAX_VALUE,上面已經限制最大的請求數為64所以這裡的數量不會超過64 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } 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! } } }
executorService.execute(this);
就是執行 AsyncCall 中的 run()
方法
AsyncCall繼承 NamedRunnable ,沒有重寫 run()
方法,直接呼叫父類的,在父類的 run()
方法中呼叫了一個 execute();
方法,該方法是一個抽象方法,需要子類實現,所以實際執行的是 AsyncCall.execute()
public abstract class NamedRunnable implements Runnable { ...... @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(); }
下面就看一個 AsyncCall.execute() ,真正執行任務的方法:
//該方法在子執行緒中執行 @Override 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); } }...... finally { //呼叫finished方法,將該請求從請求對列中移除 client.dispatcher().finished(this); } } }

從上面的程式碼分析我們可以得出: 非同步請求流程 : call.enqueue
-> realCall.enqueue
-> dispatcher.enqueue(AsyncCall call)
(AsyncCall本質就是一個Runable)在 dispatcher.enqueue
方法中將 call 新增到就緒對列中,然後外遍歷就緒對列,如果現在執行的任務沒有超過最大請求數(64)就會把它加入到執行對列中 runningAsyncCalls ,然後呼叫 asyncCall.executeOn(executorService())
(executorService()方法就是建立一個執行緒池),在 executeOn
方法中會執行 asyncCall ,就是呼叫它的 execute
方法。在該方法中真正的去執行任務,該方法是在子執行緒中執行的。
通過上面的分析我們可以得出大致的請求流程圖如下:

請求流程圖
不管是 同步請求 還是 非同步請求 ,都呼叫了 dispatcher 對應的方法,它裡面維護了三個任務對列和一個執行緒池(用來執行非同步請求), dispatcher 維護著請求任務的新增和移除。
三.Okhttp中的攔截器
攔截器是 Okhttp 提供的一種強大的機制,它可以實現網路監聽、請求以及響應重寫、請求失敗重試等功能。

Okhttp的 攔截器 分為兩種:一種是 應用攔截器 (就是我們自定義的攔截器),另一種就是 網路攔截器 (是Okhttp內部提供給我們的攔截器,真正的網路請求就是通過這些網路攔截器來實現的)。
從上面的程式碼分析得出:不管是 同步請求 還是 非同步請求 ,最終都是通過 getResponseWithInterceptorChain()
方法來獲取 Response 的,該方法就是構建一個 攔截器鏈 。下面看一下該方法的程式碼:
//RealCall中的方法 Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList<>(); //新增自定義的攔截器 interceptors.addAll(client.interceptors()); //新增okhttp提供給我們的網路攔截器 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); }
建立好 攔截器鏈 以後呼叫了該物件的 chain.proceed(originalRequest)
方法。該方法原始碼如下:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { ...... //又建立了一個攔截器鏈物件,注意此時傳入的index = index + 1 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); //順序獲取攔截器,然後呼叫攔截器的intercept方法 Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); ...... return response; }
在 chain.proceed
方法中呼叫了 interceptor.intercept(next);
方法,並將新建立的攔截器鏈物件傳入,此時index為index + 1,這樣就構成了依次呼叫 攔截器 集合的所用攔截器的 intercept
方法。在該方法中完成對應的功能以後,呼叫下一個攔截器的 intercept
方法,並將處理後的Response返回給上一個 攔截器 。

攔截器處理流程圖
1.RetryAndFollowUpInterceptor(重定向攔截器)
該攔截器的主要作用就是:負責請求的重定向操作以及請求失敗後的重試機制。
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); //1.建立StreamAllocation 物件,該物件用於分配請求過程中的流 StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; //重連次數 int followUpCount = 0; Response priorResponse = null; while (true) { ...... Response response; boolean releaseConnection = true; try { //2.呼叫RealInterceptorChain的proceed方法進行網路請求 response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; }...... // 疊加先前的響應 if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } Request followUp; try { //根據響應判斷是否需要重新請求 followUp = followUpRequest(response, streamAllocation.route()); }...... if (followUp == null) { //不需要重新請求,直接返回response,結束while迴圈 streamAllocation.release(true); return response; } ...... //需要重新請求,先判斷重新請求的次數是否超過設定的最大值,MAX_FOLLOW_UPS = 20 if (++followUpCount > MAX_FOLLOW_UPS) { //超過最大的重新請求次數,丟擲異常 streamAllocation.release(true); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } ...... //重新請求 if (!sameConnection(response, followUp.url())) { streamAllocation.release(false); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; }...... request = followUp; priorResponse = response; } }
從 RetryAndFollowUpInterceptor.intercept
方法得出主要做了以下幾件事:
- 1.建立 StreamAllocation 物件。
- 2.呼叫 RealInterceptorChain 的
proceed
方法進行網路請求,該方法就會呼叫下一個 攔截器 的intercept
方法,依次呼叫,獲取對應的 Response 。
intercept
方法有些類似 遞迴 呼叫,這裡是不同攔截器物件的 intercept
方法,這樣就從上到下形成了一個鏈。
- 3.根據異常結果或者響應結果判斷是否要進行重新請求。
2.BridgeInterceptor(橋接攔截器)
該攔截器的作用主要就是處理請求和響應
在 RetryAndFollowUpInterceptor 攔截器中建立 StreamAllocation 物件以後,就會呼叫 chain.proceed
方法進行網路請求,其實就是呼叫下一個攔截器的 intercept
方法, RetryAndFollowUpInterceptor 的下一個攔截就是 BridgeInterceptor ,下面看一下它的 intercept
程式碼:
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); //1.為請求新增一些頭資訊 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"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. 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()); } //2.傳送網路請求 Response networkResponse = chain.proceed(requestBuilder.build()); //3.解壓響應資料,支援`gzip`,所以需要解壓 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.intercept
方法得出主要做了以下幾件事:
- 1.將使用者構建的 Request 轉化為能夠進行網路訪問的請求(新增一些頭資訊,如:
Connection
、Accept-Encoding
、Host
等)。 - 2.將設定好的 Request 傳送網路請求(呼叫
chan.proceed
)。 - 3.將請求返回的 Response 轉化為使用者可用的 Response (可能使用 gzip 壓縮,需要解壓)。
3.CacheInterceptor(快取攔截器)
該攔截器的作用主要就是處理資料的快取
在 BridgeInterceptor.intercept
方法中構建好 Request 後就傳送請求,就會呼叫 CacheInterceptor.intercept
方法,該方法的程式碼為:
@Override public Response intercept(Chain chain) throws IOException { //如果設定了快取就獲取快取 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); //獲取快取策略,裡面維護著一個networkRequest和cacheResponse CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache != null) { //如果有快取,跟新一下快取的各項指標,主要是快取命中率 cache.trackResponse(strategy); } if (cacheCandidate != null && cacheResponse == null) { //有快取,但是對應的Response 為null即快取不符合要求,關閉該快取 closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 如果此時網路不可用,同時網路不可用,丟擲一個504的錯誤 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 (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()); } } if (cacheResponse != null) { //如果網路請求返回的狀態碼為 HTTP_NOT_MODIFIED = 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(); ...... } 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; }@Override public Response intercept(Chain chain) throws IOException { //如果設定了快取就獲取快取 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); //獲取快取策略,裡面維護著一個networkRequest和cacheResponse CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache != null) { //如果有快取,跟新一下快取的各項指標,主要是快取命中率 cache.trackResponse(strategy); } if (cacheCandidate != null && cacheResponse == null) { //有快取,但是對應的Response 為null即快取不符合要求,關閉該快取 closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 如果此時網路不可用,同時快取不可用,丟擲一個504的錯誤 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 (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()); } } if (cacheResponse != null) { //如果網路請求返回的狀態碼為 HTTP_NOT_MODIFIED = 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(); ...... } 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; }
從上面程式碼可以得出 CacheInterceptor 的處理邏輯為:
- 1.如果網路不可用,同時沒有快取,會返回一個 504 的響應碼。
- 2.如果網路不可用,但是有快取,直接返回快取的資料。
- 3.如果網路可用,傳送網路請求(呼叫
chain.proceed
)。- 如果有快取並且網路請求返回的狀態碼為 HTTP_NOT_MODIFIED = 304 ,就從快取中獲取資料。
- 如果沒有快取,就返回網路請求的資料。此時如果設定了快取,就將網路請求後的資料新增到快取中(
cache.put(response)
)
快取的管理使用的 Cache 類中響應的方法( get
、 put
),我們先看一下怎麼使用快取:
val okHttpClient = OkHttpClient.Builder() .readTimeout(5,TimeUnit.SECONDS) .cache(Cache(File("cache"),24 * 1024 * 1024))//通過配置Cache物件 .build()
下面我們就看一下 Cache 這個類的原始碼:
@Nullable CacheRequest put(Response response) { String requestMethod = response.request().method(); if (HttpMethod.invalidatesCache(response.request().method())) { try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } //只快取GET請求 if (!requestMethod.equals("GET")) { return null; } //Vary的內容會作為當前快取資料是否可以作為請求結果返回給客戶端的判斷 //Vary詳解:https://blog.csdn.net/qq_29405933/article/details/84315254 if (HttpHeaders.hasVaryAll(response)) { return null; } //快取資料的實體物件 Entry entry = new Entry(response); //使用磁碟快取DiskLruCache來實現快取功能 DiskLruCache.Editor editor = null; try { editor = cache.edit(key(response.request().url())); if (editor == null) { return null; } entry.writeTo(editor); return new CacheRequestImpl(editor); }...... }
從上述程式碼可以看出 Okhttp 中的快取使用的是 DiskLruCache 。
4.ConnectInterceptor(連線攔截器)
該攔截器的作用主要就是建立與伺服器的連線(Socket)
如果沒有快取,但是網路可用的情況下就會呼叫 ConnectInterceptor.intercept
方法,下面看一下該方法的程式碼:
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); //獲取StreamAllocation物件,該物件是在RetryAndFollowUpInterceptor中例項化的 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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); //建立用於網路IO的RealConnection物件 RealConnection connection = streamAllocation.connection(); //呼叫後面攔截的intercept方法並傳遞對應的引數,發起網路請求 return realChain.proceed(request, streamAllocation, httpCodec, connection); }
下面重點看一下 streamAllocation.newStream
方法:
public HttpCodec newStream( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { ...... try { //獲取一個RealConnection物件 RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); //通過RealConnection物件建立HttpCodec物件 HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); synchronized (connectionPool) { codec = resultCodec; return resultCodec; } } catch (IOException e) { throw new RouteException(e); } }
findHealthyConnection
方法會呼叫 findConnection
方法來獲取一個 RealConnection 物件,程式碼如下:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { ...... synchronized (connectionPool) { ...... if (this.connection != null) { //如果能複用就複用,this代表的是StreamAllocation物件 result = this.connection; releasedConnection = null; } ...... } ...... 不能複用從連線池中找 synchronized (connectionPool) { if (canceled) throw new IOException("Canceled"); if (newRouteSelection) { List<Route> routes = routeSelection.getAll(); for (int i = 0, size = routes.size(); i < size; i++) { Route route = routes.get(i); Internal.instance.acquire(connectionPool, address, this, route); if (connection != null) { foundPooledConnection = true; result = connection; this.route = route; break; } } } if (!foundPooledConnection) { //連線池中沒有就new一個 if (selectedRoute == null) { selectedRoute = routeSelection.next(); } route = selectedRoute; refusedStreamCount = 0; result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } ...... 開啟Socket連線 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; //connection將加入連線池中 Internal.instance.put(connectionPool, result); ...... } ....... return result; }
findConnection
方法做的主要就是:
- 1.如果 StreamAllocation 中的
connection
能複用就複用,不同複用的話就從連線池 connectionPool 中獲取,如果 連線池 中沒有就 new 一個,然後加入連線池中。 - 最終呼叫 RealConnection 的
connect
方法開啟一個 socket連結 。
獲取 resultConnection 物件後然後呼叫 resultConnection.newCodec
來獲取HttpCodec 物件
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation) throws SocketException { if (http2Connection != null) { return new Http2Codec(client, chain, streamAllocation, http2Connection); } else { socket.setSoTimeout(chain.readTimeoutMillis()); source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS); sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS); return new Http1Codec(client, streamAllocation, source, sink); } }
建立好 RealConnection 和 HttpCodec 物件以後,呼叫下一個 攔截器 的 intercept
方法。下面我們最後一個攔截器: CallServerInterceptor
5.CallServerInterceptor(發起請求攔截器)
該攔截器的主要作用就是真正的向伺服器寫入請求資料和讀取響應資料
該攔截器為 Okhttp 攔截器鏈的最後一個攔截器,該攔截器是真正向伺服器傳送請求和處理響應的地方。下面看一下它的 intercept
方法程式碼:
@Override public Response intercept(Chain chain) throws IOException { final RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); final HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); ...... //向socket中寫入header資料 httpCodec.writeRequestHeaders(request); ...... Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { //如果請求頭中有配置“Expect: 100-continue” ,直接讀取響應頭資訊 httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(call); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { if (request.body() instanceof DuplexRequestBody) { httpCodec.flushRequest(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, -1L)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); //向socket寫入請求體 request.body().writeTo(bufferedRequestBody); } else { // Write the request body if the "Expect: 100-continue" expectation was met. realChain.eventListener().requestBodyStart(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(call, requestBodyOut.successfulCount); } } ...... } if (!(request.body() instanceof DuplexRequestBody)) { //結束請求 httpCodec.finishRequest(); } //讀取響應資訊 if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(call); responseBuilder = httpCodec.readResponseHeaders(false); } responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()); Internal.instance.initCodec(responseBuilder, httpCodec); Response response = responseBuilder.build(); int code = response.code(); if (code == 100) { // server sent a 100-continue even though we did not request one. // try again to read the actual response responseBuilder = httpCodec.readResponseHeaders(false); responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()); Internal.instance.initCodec(responseBuilder, httpCodec); response = responseBuilder.build(); code = response.code(); } realChain.eventListener().responseHeadersEnd(call, response); if (forWebSocket && code == 101) { response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { //回去響應體資料 response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } ...... return response; }
從上面程式碼可以看出 CallServerInterceptor 主要就是向 Socket 中寫入 請求資訊 ,然後讀取 響應資訊 ,最後構建 Response 物件並返回給上一個 攔截器 。
總結
至此 Okhttp 的關鍵程式碼已經分析完畢,我們可以得出 Okhttp 的一次請求過程大概是:
- 1.將請求封裝成 Call物件 。
- 2.通過 Dispatcher 對請求進行分發。
- 3.呼叫 RealCall物件的
getResponseWithInterceptorChain
方法獲取 Response - 4.
getResponseWithInterceptorChain
方法中會依次呼叫攔截器: RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor 的intercept
方法,完成對於請求資訊的封裝,傳送和讀取。