- 一 請求與響應流程
- 1.1 請求的封裝
- 1.2 請求的發送
- 1.3 請求的調度
- 二 攔截器
- 2.1 RetryAndFollowUpInterceptor
- 2.2 BridgeInterceptor
- 2.3 CacheInterceptor
- 2.4 ConnectInterceptor
- 2.5 CallServerInterceptor
- 三 連接機制
- 3.1 建立連接
- 3.2 連接池
- 四 緩存機制
- 4.1 緩存策略
- 4.2 緩存管理
在Android刀耕火種的哪個年代,我們做網絡請求通常會選用HttpURLConnection或者Apache HTTP Client,這兩者均支持HTTPS、流的上傳和下載、配置超時和連接池等特性,但隨着業務場景的負責化以及 對流量消耗的優化需求,Okhttp應運而生,自誕生起,口碑就一直很好。
但是,大家都說好,好在哪里?既然這么好,它的設計理念和實現思路有哪些值得我們學習的地方?
今天就帶着這些問題,一探究竟。
An HTTP+HTTP/2 client for Android and Java applications.
官方網站: https://github.com/square/okhttp
源碼版本:3.9.1
在正式分析源碼之前,我們先來看個簡單的小例子,從例子入手,逐步分析Okhttp的實現。
:point_right: 舉例
OkHttpClient okHttpClient = new OkHttpClient.Builder() .build(); Request request = new Request.Builder() .url(url) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } });
在上面的例子中,我們構建了一個客戶端OkHttpClient和一個請求Request,然后調用newCall()方法將請求發送了出去。從這個小例子中,我們可以發現 OkHttpClient相當於是個上下文或者說是大管家,它接到我們給的任務以后,將具體的工作分發到各個子系統中去完成。
Okhttp的子系統層級結構圖如下所示:
:point_right: 點擊圖片查看大圖
- 網絡配置層:利用Builder模式配置各種參數,例如:超時時間、攔截器等,這些參數都會由Okhttp分發給各個需要的子系統。
- 重定向層:負責重定向。
- Header拼接層:負責把用戶構造的請求轉換為發送給服務器的請求,把服務器返回的響應轉換為對用戶友好的響應。
- HTTP緩存層:負責讀取緩存以及更新緩存。
- 連接層:連接層是一個比較復雜的層級,它實現了網絡協議、內部的攔截器、安全性認證,連接與連接池等功能,但這一層還沒有發起真正的連接,它只是做了連接器一些參數的處理。
- 數據響應層:負責從服務器讀取響應的數據。
在整個Okhttp的系統中,我們還要理解以下幾個關鍵角色:
- OkHttpClient:通信的客戶端,用來統一管理發起請求與解析響應。
- Call:Call是一個接口,它是HTTP請求的抽象描述,具體實現類是RealCall,它由CallFactory創建。
- Request:請求,封裝請求的具體信息,例如:url、header等。
- RequestBody:請求體,用來提交流、表單等請求信息。
- Response:HTTP請求的響應,獲取響應信息,例如:響應header等。
- ResponseBody:HTTP請求的響應體,被讀取一次以后就會關閉,所以我們重復調用responseBody.string()獲取請求結果是會報錯的。
- Interceptor:Interceptor是請求攔截器,負責攔截並處理請求,它將網絡請求、緩存、透明壓縮等功能都統一起來,每個功能都是一個Interceptor,所有的Interceptor最 終連接成一個Interceptor.Chain。典型的責任鏈模式實現。
- StreamAllocation:用來控制Connections與Streas的資源分配與釋放。
- RouteSelector:選擇路線與自動重連。
- RouteDatabase:記錄連接失敗的Route黑名單。
我們首先來分析連接的請求與響應流程,這樣我們就可以對整個Okhttp系統有一個整體的認識。
一 請求與響應流程
Okhttp的整個請求與響應的流程就是Dispatcher不斷從Request Queue里取出請求(Call),根據是否已經存存緩存,從內存緩存或者服務器獲取請求的數據,請求分為同步和異步兩種,同步請求通過
調用Call.exectute()方法直接返回當前請求的Response,異步請求調用Call.enqueue()方法將請求(AsyncCall)添加到請求隊列中去,並通過回調(Callback)獲取服務器返回的結果。
一圖勝千言,我們來看一下整個的流程圖,如下所示:
:point_right: 點擊圖片查看大圖
讀者仔細看一下這個流程圖,是不是很像計算機網絡的OSI七層模型,Okhttp正式采用這種思路,利用攔截器Interceptor將整套框架縱向分層,簡化了設計邏輯,提升了框架擴展性。
通過上面的流程圖,我們可以知道在整個請求與響應流程中,以下幾點是我們需要重點關注的:
- Dispatcher是如何進行請求調度的?
- 各個攔截器是如何實現的?
- 連接與連接池是如何建立和維護的?
帶着以上問題,我們去源碼中一探究竟。
我們先來看一下具體的函數調用鏈,請求與響應的序列圖如下所示:
:point_right: 點擊圖片查看大圖
上述序列圖可以幫助我們理解整個請求與響應流程的具體細節,我們首先來看一下一個請求和如何被封裝並發出的。
1.1 請求的封裝
請求是由Okhttp發出,真正的請求都被封裝了在了接口Call的實現類RealCall中,如下所示:
Call接口如下所示:
public interface Call extends Cloneable { //返回當前請求 Request request(); //同步請求方法,此方法會阻塞當前線程知道請求結果放回 Response execute() throws IOException; //異步請求方法,此方法會將請求添加到隊列中,然后等待請求返回 void enqueue(Callback responseCallback); //取消請求 void cancel(); //請求是否在執行,當execute()或者enqueue(Callback responseCallback)執行后該方法返回true boolean isExecuted(); //請求是否被取消 boolean isCanceled(); //創建一個新的一模一樣的請求 Call clone(); interface Factory { Call newCall(Request request); } }
RealCall的構造方法如下所示:
final class RealCall implements Call { private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { //我們構建的OkHttpClient,用來傳遞參數 this.client = client; this.originalRequest = originalRequest; //是不是WebSocket請求,WebSocket是用來建立長連接的,后面我們會說。 this.forWebSocket = forWebSocket; //構建RetryAndFollowUpInterceptor攔截器 this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); } }
RealCall實現了Call接口,它封裝了請求的調用,這個構造函數的邏輯也很簡單:賦值外部傳入的OkHttpClient、Request與forWebSocket,並
創建了重試與重定向攔截器RetryAndFollowUpInterceptor。
1.2 請求的發送
RealCall將請求分為兩種:
- 同步請求
- 異步請求
異步請求只是比同步請求多了個Callback,分別調用的方法如下所示:
異步請求
final class RealCall implements Call { @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); } }
同步請求
final class RealCall implements Call { @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } } }
從上面實現可以看出,不管是同步請求還是異步請求都是Dispatcher在處理:
- 同步請求:直接執行,並返回請求結果
- 異步請求:構造一個AsyncCall,並將自己加入處理隊列中。
AsyncCall本質上是一個Runable,Dispatcher會調度ExecutorService來執行這些Runable。
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } String host() { return originalRequest.url().host(); } Request request() { return originalRequest; } RealCall get() { return RealCall.this; } @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,只不過異步請求多了個線程調度,異步 執行的過程。
我們先來來看看Dispatcher里的實現。
1.3 請求的調度
public final class Dispatcher { private int maxRequests = 64; private int maxRequestsPerHost = 5; /** 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<>(); /** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); } synchronized void enqueue(AsyncCall call) { //正在運行的異步請求不得超過64,同一個host下的異步請求不得超過5個 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } }
Dispatcher是一個任務調度器,它內部維護了三個雙端隊列:
- readyAsyncCalls:准備運行的異步請求
- runningAsyncCalls:正在運行的異步請求
- runningSyncCalls:正在運行的同步請求
記得異步請求與同步騎牛,並利用ExecutorService來調度執行AsyncCall。
同步請求就直接把請求添加到正在運行的同步請求隊列runningSyncCalls中,異步請求會做個判斷:
如果正在運行的異步請求不超過64,而且同一個host下的異步請求不得超過5個則將請求添加到正在運行的同步請求隊列中runningAsyncCalls並開始 執行請求,否則就添加到readyAsyncCalls繼續等待。
講完Dispatcher里的實現,我們繼續來看getResponseWithInterceptorChain()的實現,這個方法才是真正發起請求並處理請求的地方。
1.4 請求的處理
final class RealCall implements Call { Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //這里可以看出,我們自定義的Interceptor會被優先執行 interceptors.addAll(client.interceptors()); //添加重試和重定向爛機器 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); return chain.proceed(originalRequest); } }
短短幾行代碼,完成了對請求的所有處理過程,Interceptor將網絡請求、緩存、透明壓縮等功能統一了起來,它的實現采用責任鏈模式,各司其職, 每個功能都是一個Interceptor,上一級處理完成以后傳遞給下一級,它們最后連接成了一個Interceptor.Chain。它們的功能如下:
- RetryAndFollowUpInterceptor:負責重定向。
- BridgeInterceptor:負責把用戶構造的請求轉換為發送給服務器的請求,把服務器返回的響應轉換為對用戶友好的響應。
- CacheInterceptor:負責讀取緩存以及更新緩存。
- ConnectInterceptor:負責與服務器建立連接。
- CallServerInterceptor:負責從服務器讀取響應的數據。
位置決定功能,位置靠前的先執行,最后一個則復制與服務器通訊,請求從RetryAndFollowUpInterceptor開始層層傳遞到CallServerInterceptor,每一層 都對請求做相應的處理,處理的結構再從CallServerInterceptor層層返回給RetryAndFollowUpInterceptor,最紅請求的發起者獲得了服務器返回的結果。
以上便是Okhttp整個請求與響應的具體流程,可以發現攔截器才是Okhttp核心功能所在,我們來逐一分析每個攔截器的實現。
二 攔截器
從上面的流程可以看出,各個環節都是由相應的攔截器進行處理,所有的攔截器(包括我們自定義的)都實現了Interceptor接口,如下所示:
public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; //返回Request執行后返回的連接 @Nullable Connection connection(); } }
Okhttp內置的攔截器如下所示:
- RetryAndFollowUpInterceptor:負責失敗重試以及重定向。
- BridgeInterceptor:負責把用戶構造的請求轉換為發送給服務器的請求,把服務器返回的響應轉換為對用戶友好的響應。
- CacheInterceptor:負責讀取緩存以及更新緩存。
- ConnectInterceptor:負責與服務器建立連接。
- CallServerInterceptor:負責從服務器讀取響應的數據。
我們繼續來看看RealInterceptorChain里是怎么一級級處理的。
public final class RealInterceptorChain implements Interceptor.Chain { 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; } }
這個方法比較有意思,在調用proceed方法之后,會繼續構建一個新的RealInterceptorChain對象,調用下一個interceptor來繼續請求,直到所有interceptor都處理完畢,將 得到的response返回。
每個攔截器的方法都遵循這樣的規則:
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //1 Request階段,該攔截器在Request階段負責做的事情 //2 調用RealInterceptorChain.proceed(),其實是在遞歸調用下一個攔截器的intercept()方法 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); //3 Response階段,完成了該攔截器在Response階段負責做的事情,然后返回到上一層的攔截器。 return response; } }
從上面的描述可知,Request是按照interpretor的順序正向處理,而Response是逆向處理的。這參考了OSI七層模型的原理。上面我們也提到過。CallServerInterceptor相當於最底層的物理層, 請求從上到逐層包裝下發,響應從下到上再逐層包裝返回。很漂亮的設計。
interceptor的執行順序:RetryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor -> ConnectInterceptor -> CallServerInterceptor。
2.1 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor負責失敗重試以及重定向。
public final class RetryAndFollowUpInterceptor implements Interceptor { private static final int MAX_FOLLOW_UPS = 20; @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //1. 構建一個StreamAllocation對象,StreamAllocation相當於是個管理類,維護了 //Connections、Streams和Calls之間的管理,該類初始化一個Socket連接對象,獲取輸入/輸出流對象。 streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(request.url()), callStackTrace); //重定向次數 int followUpCount = 0; Response priorResponse = null; while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response = null; boolean releaseConnection = true; try { //2. 繼續執行下一個Interceptor,即BridgeInterceptor response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { //3. 拋出異常,則檢測連接是否還可以繼續。 if (!recover(e.getLastConnectException(), false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // 和服務端建立連接失敗 boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { //檢測到其他未知異常,則釋放連接和資源 if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } //構建響應體,這個響應體的body為空。 if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } //4。根據響應碼處理請求,返回Request不為空時則進行重定向處理。 Request followUp = followUpRequest(response); if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); //重定向的次數不能超過20次 if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(followUp.url()), callStackTrace); } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; } } }
我們先來說說StreamAllocation這個類的作用,這個類協調了三個實體類的關系:
- Connections:連接到遠程服務器的物理套接字,這個套接字連接可能比較慢,所以它有一套取消機制。
- Streams:定義了邏輯上的HTTP請求/響應對,每個連接都定義了它們可以攜帶的最大並發流,HTTP/1.x每次只可以攜帶一個,HTTP/2每次可以攜帶多個。
- Calls:定義了流的邏輯序列,這個序列通常是一個初始請求以及它的重定向請求,對於同一個連接,我們通常將所有流都放在一個調用中,以此來統一它們的行為。
我們再來看看整個方法的流程:
- 構建一個StreamAllocation對象,StreamAllocation相當於是個管理類,維護了Connections、Streams和Calls之間的管理,該類初始化一個Socket連接對象,獲取輸入/輸出流對象。
- 繼續執行下一個Interceptor,即BridgeInterceptor
-
拋出異常,則檢測連接是否還可以繼續,以下情況不會重試:
-
客戶端配置出錯不再重試
- 出錯后,request body不能再次發送
- 發生以下Exception也無法恢復連接:
- ProtocolException:協議異常
- InterruptedIOException:中斷異常
- SSLHandshakeException:SSL握手異常
- SSLPeerUnverifiedException:SSL握手未授權異常
- 沒有更多線路可以選擇 4。根據響應碼處理請求,返回Request不為空時則進行重定向處理,重定向的次數不能超過20次。
最后是根據響應碼來處理請求頭,由followUpRequest()方法完成,具體如下所示:
public final class RetryAndFollowUpInterceptor implements Interceptor { private Request followUpRequest(Response userResponse) throws IOException { if (userResponse == null) throw new IllegalStateException(); Connection connection = streamAllocation.connection(); Route route = connection != null ? connection.route() : null; int responseCode = userResponse.code(); final String method = userResponse.request().method(); switch (responseCode) { //407,代理認證 case HTTP_PROXY_AUTH: Proxy selectedProxy = route != null ? route.proxy() : client.proxy(); if (selectedProxy.type() != Proxy.Type.HTTP) { throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"); } return client.proxyAuthenticator().authenticate(route, userResponse); //401,未經認證 case HTTP_UNAUTHORIZED: return client.authenticator().authenticate(route, userResponse); //307,308 case HTTP_PERM_REDIRECT: case HTTP_TEMP_REDIRECT: // "If the 307 or 308 status code is received in response to a request other than GET // or HEAD, the user agent MUST NOT automatically redirect the request" if (!method.equals("GET") && !method.equals("HEAD")) { return null; } // fall-through //300,301,302,303 case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: //客戶端在配置中是否允許重定向 if (!client.followRedirects()) return null; String location = userResponse.header("Location"); if (location == null) return null; HttpUrl url = userResponse.request().url().resolve(location); // url為null,不允許重定向 if (url == null) return null; //查詢是否存在http與https之間的重定向 boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme()); if (!sameScheme && !client.followSslRedirects()) return null; // Most redirects don't include a request body. Request.Builder requestBuilder = userResponse.request().newBuilder(); if (HttpMethod.permitsRequestBody(method)) { final boolean maintainBody = HttpMethod.redirectsWithBody(method); if (HttpMethod.redirectsToGet(method)) { requestBuilder.method("GET", null); } else { RequestBody requestBody = maintainBody ? userResponse.request().body() : null; requestBuilder.method(method, requestBody); } if (!maintainBody) { requestBuilder.removeHeader("Transfer-Encoding"); requestBuilder.removeHeader("Content-Length"); requestBuilder.removeHeader("Content-Type"); } } // When redirecting across hosts, drop all authentication headers. This // is potentially annoying to the application layer since they have no // way to retain them. if (!sameConnection(userResponse, url)) { requestBuilder.removeHeader("Authorization"); } return requestBuilder.url(url).build(); //408,超時 case HTTP_CLIENT_TIMEOUT: // 408's are rare in practice, but some servers like HAProxy use this response code. The // spec says that we may repeat the request without modifications. Modern browsers also // repeat the request (even non-idempotent ones.) if (userResponse.request().body() instanceof UnrepeatableRequestBody) { return null; } return userResponse.request(); default: return null; } } }
重定向會涉及到一些網絡編程的知識,這里如果沒有完成理解,你只要知道RetryAndFollowUpInterceptor的作用就是處理了一些連接異常以及重定向就可以了。我們接着來看看下一個BridgeInterceptor。
2.2 BridgeInterceptor
BridgeInterceptor就跟它的名字那樣,它是一個連接橋,它負責把用戶構造的請求轉換為發送給服務器的請求,把服務器返回的響應轉換為對用戶友好的響應。
轉換的過程就是添加一些服務端需要的header信息。
public final class BridgeInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); if (body != null) { //1 進行Header的包裝 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"); } //這里有個坑:如果你在請求的時候主動添加了"Accept-Encoding: gzip" ,transparentGzip=false,那你就要自己解壓,如果 // 你沒有吹解壓,或導致response.string()亂碼。 // 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"); } //創建OkhttpClient配置的cookieJar 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()); } Response networkResponse = chain.proceed(requestBuilder.build()); //解析服務器返回的Header,如果沒有這事cookie,則不進行解析 HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); //判斷服務器是否支持gzip壓縮,如果支持,則將壓縮提交給Okio庫來處理 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); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); } }
就跟它的名字描述的那樣,它是一個橋梁,負責把用戶構造的請求轉換為發送給服務器的請求,把服務器返回的響應轉換為對用戶友好的響應。 在Request階段配置用戶信息,並添加一些請求頭。在Response階段,進行gzip解壓。
這個方法主要是針對Header做了一些處理,這里主要提一下"Accept-Encoding", "gzip",關於它有以下幾點需要注意:
- 開發者沒有添加Accept-Encoding時,自動添加Accept-Encoding: gzip
- 自動添加Accept-Encoding,會對request,response進行自動解壓
- 手動添加Accept-Encoding,不負責解壓縮
- 自動解壓時移除Content-Length,所以上層Java代碼想要contentLength時為-1
- 自動解壓時移除 Content-Encoding
- 自動解壓時,如果是分塊傳輸編碼,Transfer-Encoding: chunked不受影響。
BridgeInterceptor主要就是針對Header做了一些處理,我們接着來看CacheInterceptor。
2.3 CacheInterceptor
我們知道為了節省流量和提高響應速度,Okhttp是有自己的一套緩存機制的,CacheInterceptor就是用來負責讀取緩存以及更新緩存的。
public final class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { //1. 讀取候選緩存,具體如何讀取的我們下面會講。 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); //2. 創建緩存策略,強制緩存、對比緩存等,關於緩存策略我們下面也會講。 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) { closeQuietly(cacheCandidate.body()); } //3. 根據策略,不使用網絡,又沒有緩存的直接報錯,並返回錯誤碼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(); } //4. 根據策略,不使用網絡,有緩存的直接返回。 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { //5. 前面兩個都沒有返回,繼續執行下一個Interceptor,即ConnectInterceptor。 networkResponse = chain.proceed(networkRequest); } finally { //如果發生IO異常,則釋放掉緩存 if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } //6. 接收到網絡結果,如果響應code式304,則使用緩存,返回緩存結果。 if (cacheResponse != null) { 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(); cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } //7. 讀取網絡結果。 Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); //8. 對數據進行緩存。 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. } } } //9. 返回網絡讀取的結果。 return response; } }
整個方法的流程如下所示:
- 讀取候選緩存,具體如何讀取的我們下面會講。
- 創建緩存策略,強制緩存、對比緩存等,關於緩存策略我們下面也會講。
- 根據策略,不使用網絡,又沒有緩存的直接報錯,並返回錯誤碼504。
- 根據策略,不使用網絡,有緩存的直接返回。
- 前面兩個都沒有返回,繼續執行下一個Interceptor,即ConnectInterceptor。
- 接收到網絡結果,如果響應code式304,則使用緩存,返回緩存結果。
- 讀取網絡結果。
- 對數據進行緩存。
- 返回網絡讀取的結果。
我們再接着來看ConnectInterceptor。
2.4 ConnectInterceptor
在RetryAndFollowUpInterceptor里初始化了一個StreamAllocation對象,我們說在這個StreamAllocation對象里初始化了一個Socket對象用來做連接,但是並沒有 真正的連接,等到處理完hader和緩存信息之后,才調用ConnectInterceptor來進行真正的連接
public final class ConnectInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); boolean doExtensiveHealthChecks = !request.method().equals("GET"); //創建輸出流 HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks); //建立連接 RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); } }
ConnectInterceptor在Request階段建立連接,處理方式也很簡單,創建了兩個對象:
- HttpCodec:用來編碼HTTP requests和解碼HTTP responses
- RealConnection:連接對象,負責發起與服務器的連接。
這里事實上包含了連接、連接池等一整套的Okhttp的連接機制,我們放在下面單獨講,先來繼續看最后一個Interceptor:CallServerInterceptor。
2.5 CallServerInterceptor
CallServerInterceptor負責從服務器讀取響應的數據。
public final class CallServerInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { //這些對象在前面的Interceptor都已經創建完畢 RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); //1. 寫入請求頭 httpCodec.writeRequestHeaders(request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return what // we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); responseBuilder = httpCodec.readResponseHeaders(true); } //2 寫入請求體 if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } else if (!connection.isMultiplexed()) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from // being reused. Otherwise we're still obligated to transmit the request body to leave the // connection in a consistent state. streamAllocation.noNewStreams(); } } httpCodec.finishRequest(); //3 讀取響應頭 if (responseBuilder == null) { responseBuilder = httpCodec.readResponseHeaders(false); } Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); //4 讀取響應體 int code = response.code(); if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; } }
我們通過ConnectInterceptor已經連接到服務器了,接下來我們就是寫入請求數據以及讀出返回數據了。整個流程:
- 寫入請求頭
- 寫入請求體
- 讀取響應頭
- 讀取響應體
這篇文章就到這里,后續的文章我們會來分析Okhttp的緩存機制、連接機制、編輯嗎機制等實現。
三 連接機制
連接的創建是在StreamAllocation對象統籌下完成的,我們前面也說過它早在RetryAndFollowUpInterceptor就被創建了,StreamAllocation對象 主要用來管理兩個關鍵角色:
- RealConnection:真正建立連接的對象,利用Socket建立連接。
- ConnectionPool:連接池,用來管理和復用連接。
在里初始化了一個StreamAllocation對象,我們說在這個StreamAllocation對象里初始化了一個Socket對象用來做連接,但是並沒有
3.1 創建連接
我們在前面的ConnectInterceptor分析中已經說過,onnectInterceptor用來完成連接。而真正的連接在RealConnect中實現,連接由連接池ConnectPool來管理,連接池最多保 持5個地址的連接keep-alive,每個keep-alive時長為5分鍾,並有異步線程清理無效的連接。
主要由以下兩個方法完成:
- HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
- RealConnection connection = streamAllocation.connection();
我們來具體的看一看。
StreamAllocation.newStream()最終調動findConnect()方法來建立連接。
public final class StreamAllocation { /** * Returns a connection to host a new stream. This prefers the existing connection if it exists, * then the pool, finally building a new connection. */ private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) throws IOException { Route selectedRoute; synchronized (connectionPool) { if (released) throw new IllegalStateException("released"); if (codec != null) throw new IllegalStateException("codec != null"); if (canceled) throw new IOException("Canceled"); //1 查看是否有完好的連接 RealConnection allocatedConnection = this.connection; if (allocatedConnection != null && !allocatedConnection.noNewStreams) { return allocatedConnection; } //2 連接池中是否用可用的連接,有則使用 Internal.instance.get(connectionPool, address, this, null); if (connection != null) { return connection; } selectedRoute = route; } //線程的選擇,多IP操作 if (selectedRoute == null) { selectedRoute = routeSelector.next(); } //3 如果沒有可用連接,則自己創建一個 RealConnection result; synchronized (connectionPool) { if (canceled) throw new IOException("Canceled"); // Now that we have an IP address, make another attempt at getting a connection from the pool. // This could match due to connection coalescing. Internal.instance.get(connectionPool, address, this, selectedRoute); if (connection != null) { route = selectedRoute; return connection; } // Create a connection and assign it to this allocation immediately. This makes it possible // for an asynchronous cancel() to interrupt the handshake we're about to do. route = selectedRoute; refusedStreamCount = 0; result = new RealConnection(connectionPool, selectedRoute); acquire(result); } //4 開始TCP以及TLS握手操作 result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled); routeDatabase().connected(result.route()); //5 將新創建的連接,放在連接池中 Socket socket = null; synchronized (connectionPool) { // Pool the connection. Internal.instance.put(connectionPool, result); // If another multiplexed connection to the same address was created concurrently, then // release this connection and acquire that one. if (result.isMultiplexed()) { socket = Internal.instance.deduplicate(connectionPool, address, this); result = connection; } } closeQuietly(socket); return result; } }
整個流程如下:
-
查找是否有完整的連接可用:
-
Socket沒有關閉
- 輸入流沒有關閉
- 輸出流沒有關閉
-
Http2連接沒有關閉
-
連接池中是否有可用的連接,如果有則可用。
- 如果沒有可用連接,則自己創建一個。
- 開始TCP連接以及TLS握手操作。
- 將新創建的連接加入連接池。
上述方法完成后會創建一個RealConnection對象,然后調用該方法的connect()方法建立連接,我們再來看看RealConnection.connect()方法的實現。
public final class RealConnection extends Http2Connection.Listener implements Connection { public void connect( int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) { if (protocol != null) throw new IllegalStateException("already connected"); //線路選擇 RouteException routeException = null; List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs(); ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); if (route.address().sslSocketFactory() == null) { if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication not enabled for client")); } String host = route.address().url().host(); if (!Platform.get().isCleartextTrafficPermitted(host)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication to " + host + " not permitted by network security policy")); } } //開始連接 while (true) { try { //如果是通道模式,則建立通道連接 if (route.requiresTunnel()) { connectTunnel(connectTimeout, readTimeout, writeTimeout); } //否則進行Socket連接,一般都是屬於這種情況 else { connectSocket(connectTimeout, readTimeout); } //建立https連接 establishProtocol(connectionSpecSelector); break; } catch (IOException e) { closeQuietly(socket); closeQuietly(rawSocket); socket = null; rawSocket = null; source = null; sink = null; handshake = null; protocol = null; http2Connection = null; if (routeException == null) { routeException = new RouteException(e); } else { routeException.addConnectException(e); } if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) { throw routeException; } } } if (http2Connection != null) { synchronized (connectionPool) { allocationLimit = http2Connection.maxConcurrentStreams(); } } } /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */ private void connectSocket(int connectTimeout, int readTimeout) throws IOException { Proxy proxy = route.proxy(); Address address = route.address(); //根據代理類型的不同處理Socket rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP ? address.socketFactory().createSocket() : new Socket(proxy); rawSocket.setSoTimeout(readTimeout); try { //建立Socket連接 Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout); } catch (ConnectException e) { ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress()); ce.initCause(e); throw ce; } // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0 // More details: // https://github.com/square/okhttp/issues/3245 // https://android-review.googlesource.com/#/c/271775/ try { //獲取輸入/輸出流 source = Okio.buffer(Okio.source(rawSocket)); sink = Okio.buffer(Okio.sink(rawSocket)); } catch (NullPointerException npe) { if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) { throw new IOException(npe); } } } }
最終調用Java里的套接字Socket里的connect()方法。
3.2 連接池
我們知道在負責的網絡環境下,頻繁的進行建立Sokcet連接(TCP三次握手)和斷開Socket(TCP四次分手)是非常消耗網絡資源和浪費時間的,HTTP中的keepalive連接對於 降低延遲和提升速度有非常重要的作用。
復用連接就需要對連接進行管理,這里就引入了連接池的概念。
Okhttp支持5個並發KeepAlive,默認鏈路生命為5分鍾(鏈路空閑后,保持存活的時間),連接池有ConectionPool實現,對連接進行回收和管理。
ConectionPool在內部維護了一個線程池,來清理連接,如下所示:
public final class ConnectionPool { private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); //清理連接,在線程池executor里調用。 private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { //執行清理,並返回下次需要清理的時間。 long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { //在timeout時間內釋放鎖 ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } } }; }
ConectionPool在內部維護了一個線程池,來清理連,清理任務由cleanup()方法完成,它是一個阻塞操作,首先執行清理,並返回下次需要清理的間隔時間,調用調用wait()
方法釋放鎖。等時間到了以后,再次進行清理,並返回下一次需要清理的時間,循環往復。
我們來看一看cleanup()方法的具體實現。
undefined
整個方法的流程如下所示:
- 查詢此連接內部的StreanAllocation的引用數量。
- 標記空閑連接。
- 如果空閑連接超過5個或者keepalive時間大於5分鍾,則將該連接清理掉。
- 返回此連接的到期時間,供下次進行清理。
- 全部都是活躍連接,5分鍾時候再進行清理。
- 沒有任何連接,跳出循環。
- 關閉連接,返回時間0,立即再次進行清理。
在RealConnection里有個StreamAllocation虛引用列表,每創建一個StreamAllocation,就會把它添加進該列表中,如果留關閉以后就將StreamAllocation 對象從該列表中移除,正是利用利用這種引用計數的方式判定一個連接是否為空閑連接,
undefined
查找引用計數由pruneAndGetAllocationCount()方法實現,具體實現如下所示:
undefined
四 緩存機制
4.1 緩存策略
在分析Okhttp的緩存機制之前,我們先來回顧一下HTTP與緩存相關的理論知識,這是實現Okhttp機制的基礎。
HTTP的緩存機制也是依賴於請求和響應header里的參數類實現的,最終響應式從緩存中去,還是從服務端重新拉取,HTTP的緩存機制的流程如下所示:
:point_right: 點擊圖片查看大圖
HTTP的緩存可以分為兩種:
- 強制緩存:需要服務端參與判斷是否繼續使用緩存,當客戶端第一次請求數據是,服務端返回了緩存的過期時間(Expires與Cache-Control),沒有過期就可以繼續使用緩存,否則則不適用,無需再向服務端詢問。
- 對比緩存:需要服務端參與判斷是否繼續使用緩存,當客戶端第一次請求數據時,服務端會將緩存標識(Last-Modified/If-Modified-Since與Etag/If-None-Match)與數據一起返回給客戶端,客戶端將兩者都備份到緩存中 ,再次請求數據時,客戶端將上次備份的緩存 標識發送給服務端,服務端根據緩存標識進行判斷,如果返回304,則表示通知客戶端可以繼續使用緩存。
強制緩存優先於對比緩存。
上面提到強制緩存使用的的兩個標識:
- Expires:Expires的值為服務端返回的到期時間,即下一次請求時,請求時間小於服務端返回的到期時間,直接使用緩存數據。到期時間是服務端生成的,客戶端和服務端的時間可能有誤差。
- Cache-Control:Expires有個時間校驗的問題,所有HTTP1.1采用Cache-Control替代Expires。
Cache-Control的取值有以下幾種:
- private: 客戶端可以緩存。
- public: 客戶端和代理服務器都可緩存。
- max-age=xxx: 緩存的內容將在 xxx 秒后失效
- no-cache: 需要使用對比緩存來驗證緩存數據。
- no-store: 所有內容都不會緩存,強制緩存,對比緩存都不會觸發。
我們再來看看對比緩存的兩個標識:
Last-Modified/If-Modified-Since
Last-Modified 表示資源上次修改的時間。
當客戶端發送第一次請求時,服務端返回資源上次修改的時間:
undefined 客戶端再次發送,會在header里攜帶If-Modified-Since。將上次服務端返回的資源時間上傳給服務端。
undefined 服務端接收到客戶端發來的資源修改時間,與自己當前的資源修改時間進行對比,如果自己的資源修改時間大於客戶端發來的資源修改時間,則說明資源做過修改, 則返回200表示需要重新請求資源,否則返回304表示資源沒有被修改,可以繼續使用緩存。
上面是一種時間戳標記資源是否修改的方法,還有一種資源標識碼ETag的方式來標記是否修改,如果標識碼發生改變,則說明資源已經被修改,ETag優先級高於Last-Modified。
Etag/If-None-Match
ETag是資源文件的一種標識碼,當客戶端發送第一次請求時,服務端會返回當前資源的標識碼:
undefined 客戶端再次發送,會在header里攜帶上次服務端返回的資源標識碼:
undefined 服務端接收到客戶端發來的資源標識碼,則會與自己當前的資源嗎進行比較,如果不同,則說明資源已經被修改,則返回200,如果相同則說明資源沒有被修改,返回 304,客戶端可以繼續使用緩存。
以上便是HTTP緩存策略的相關理論知識,我們來看看具體實現。
Okhttp的緩存策略就是根據上述流程圖實現的,具體的實現類是CacheStrategy,CacheStrategy的構造函數里有兩個參數:
undefined 這兩個參數參數的含義如下:
- networkRequest:網絡請求。
- cacheResponse:緩存響應,基於DiskLruCache實現的文件緩存,可以是請求中url的md5,value是文件中查詢到的緩存,這個我們下面會說。
CacheStrategy就是利用這兩個參數生成最終的策略,有點像map操作,將networkRequest與cacheResponse這兩個值輸入,處理之后再將這兩個值輸出,們的組合結果如下所示:
- 如果networkRequest為null,cacheResponse為null:only-if-cached(表明不進行網絡請求,且緩存不存在或者過期,一定會返回503錯誤)。
- 如果networkRequest為null,cacheResponse為non-null:不進行網絡請求,而且緩存可以使用,直接返回緩存,不用請求網絡。
- 如果networkRequest為non-null,cacheResponse為null:需要進行網絡請求,而且緩存不存在或者過期,直接訪問網絡。
- 如果networkRequest為non-null,cacheResponse為non-null:Header中含有ETag/Last-Modified標簽,需要在條件請求下使用,還是需要訪問網絡。
那么這四種情況是如何判定的,我們來看一下。
CacheStrategy是利用Factory模式進行構造的,CacheStrategy.Factory對象構建以后,調用它的get()方法即可獲得具體的CacheStrategy,CacheStrategy.Factory.get()方法內部
調用的是CacheStrategy.Factory.getCandidate()方法,它是核心的實現。
如下所示:
undefined
整個函數的邏輯就是按照上面那個HTTP緩存判定流程圖來實現,具體流程如下所示:
- 如果緩存沒有命中,就直接進行網絡請求。
- 如果TLS握手信息丟失,則返回直接進行連接。
- 根據response狀態碼,Expired時間和是否有no-cache標簽就行判斷是否進行直接訪問。
- 如果請求header里有"no-cache"或者右條件GET請求(header里帶有ETag/Since標簽),則直接連接。
- 如果緩存在過期時間內則可以直接使用,則直接返回上次緩存。
- 如果緩存過期,且有ETag等信息,則發送If-None-Match、If-Modified-Since、If-Modified-Since等條件請求交給服務端判斷處理
整個流程就是這樣,另外說一點,Okhttp的緩存是根據服務器header自動的完成的,整個流程也是根據RFC文檔寫死的,客戶端不必要進行手動控制。
理解了緩存策略,我們來看看緩存在磁盤上是如何被管理的。
4.2 緩存管理
這篇文章我們來分析Okhttp的緩存機制,緩存機制是基於DiskLruCache做的。Cache類封裝了緩存的實現,實現了InternalCache接口。
InternalCache接口如下所示:
InternalCache
undefined 我們接着來看看它的實現類。
Cache沒有直接實現InternalCache這個接口,而是在其內部實現了InternalCache的匿名內部類,內部類的方法調用Cache對應的方法,如下所示:
undefined ` 在Cache類里還定義一些內部類,這些類封裝了請求與響應信息。
- Cache.Entry:封裝了請求與響應等信息,包括url、varyHeaders、protocol、code、message、responseHeaders、handshake、sentRequestMillis與receivedResponseMillis。
- Cache.CacheResponseBody:繼承於ResponseBody,封裝了緩存快照snapshot,響應體bodySource,內容類型contentType,內容長度contentLength。
除了兩個類以外,Okhttp還封裝了一個文件系統類FileSystem類,這個類利用Okio這個庫對Java的FIle操作進行了一層封裝,簡化了IO操作。理解了這些剩下的就是DiskLruCahe里的插入緩存 、獲取緩存和刪除緩存的操作。
好了,到這里關於Okhttp的全部內容就都講完了,可以說Okhttp是設計非常優良的一個庫,有很多值得我們學習的地方
來自:https://blog.souche.com/untitled-7/