在以前的okhttp源碼解析(執行流程)的文章中,咱們已經對okhttp
發起請求的執行流程作了探究。這篇文章將對okhttp
中的攔截器和設計模式作一下分析,廢話少說,開幹!前端
咱們在探究攔截器以前,首先要知道攔截器是什麼做用,套用okhttp
官網的一句話:java
Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls.
android
將這句話翻譯一下:攔截器很是強大的機制,它能夠監視、重寫、和重連請求。官網也給咱們提供了一個列子,是對請求可響應以日誌的形式展現。git
class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } 複製代碼
okhttp
官網給出了一張圖: github
Application Interceptors
、
Network Interceptors
,看一下官網對這兩個攔截器的解釋。
Application Interceptors(應用攔截器):
Don’t need to worry about intermediate responses like redirects and retries.
不用擔憂響應和重定向中間的響應
Are always invoked once, even if the HTTP response is served from the cache.
常常只須要調用一次,使用HTTP響應是經過緩存提供
Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
準從應用層最初的目的,與OkHttp的注入頭部無關,如If-None-Match
Permitted to short-circuit and not call Chain.proceed().
容許短路並且不調用Chain.proceed()
Permitted to retry and make multiple calls to Chain.proceed().
容許重試和屢次調用Chain.proceed()
web
Network Interceptors(網絡攔截器):
Able to operate on intermediate responses like redirects and retries.
容許像重定向和重試同樣操做中間響應
Not invoked for cached responses that short-circuit the network.
網絡發生短路時不調用緩存響應
Observe the data just as it will be transmitted over the network.
在數據被傳遞到網絡時觀察數據
Access to the Connection that carries the request.
有權得到裝載請求的鏈接
算法
咱們從官網給出的示例中咱們看到當須要自定義攔截器時,就須要實現Interceptor
接口。segmentfault
public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; @Nullable Connection connection(); Call call(); int connectTimeoutMillis(); Chain withConnectTimeout(int timeout, TimeUnit unit); int readTimeoutMillis(); Chain withReadTimeout(int timeout, TimeUnit unit); int writeTimeoutMillis(); Chain withWriteTimeout(int timeout, TimeUnit unit); } } 複製代碼
在這個接口中有一個方法intercept
,因此咱們在對其餘攔截器進行探究時,能夠直接查看intercept
方法。設計模式
/** * This interceptor recovers from failures and follows redirects as necessary. It may throw an * {@link IOException} if the call was canceled. */ public final class RetryAndFollowUpInterceptor implements Interceptor { /** * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox, * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5. */ private static final int MAX_FOLLOW_UPS = 20; // 一、定義了失敗重連的次數 ... @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); // 二、建立一個StreamAllocation對象(這個對象會在後面進行探究,別急) StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; int followUpCount = 0; Response priorResponse = null; while (true) { // 三、開啓無限循環 if (canceled) { // 四、檢查請求是否已經取消,若是取消,經過streamAllocation釋放請求 streamAllocation.release(); throw new IOException("Canceled"); } Response response; boolean releaseConnection = true; try { // 五、執行攔截器鏈,獲取響應 response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getFirstConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // 六、關聯前一個響應, if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } Request followUp; try { // 七、這裏會根據狀態碼和請求方法判斷是否須要重定向 followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { streamAllocation.release(); throw e; } // 八、若是不須要重定向,釋放請求,返回響應 if (followUp == null) { streamAllocation.release(); return response; } closeQuietly(response.body()); // 九、判斷重定向次數是否超出範圍 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()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } 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; } } } 複製代碼
從註釋能夠看出這個攔截器主要負責失敗重連和重定向,關鍵性的註釋已經在上面代碼中給出,咱們看一下RetryAndFollowUpInterceptor
攔截操做中作了哪幾比較重要的操做。緩存
一、建立一個
StreamAllocation
對象。
二、開啓無限循環,調用鏈接器鏈中的proceed
方法,獲取響應。
三、獲取響應以後,若是出現路由異常、IO
異常,就繼續循環獲取響應(失敗重連)。
四、成功獲取響應,釋放當前鏈接。以後調用followUpRequest
方法,查看是否須要重定向。
五、若是不須要重定向,直接返回響應,同時釋放當前鏈接。
六、若是須要重定向,判斷是否超太重定向最大次數。超過,釋放當前請求並拋出異常;沒有超過,繼續請求。
上述操做中,最主要的是調用followUpRequest方法查看獲取重定向信息。
private Request followUpRequest(Response userResponse, Route route) throws IOException { if (userResponse == null) throw new IllegalStateException(); int responseCode = userResponse.code(); final String method = userResponse.request().method(); switch (responseCode) { case HTTP_PROXY_AUTH: // 407 須要代理身份驗證 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); case HTTP_UNAUTHORIZED: // 401 沒有受權 return client.authenticator().authenticate(route, userResponse); case HTTP_PERM_REDIRECT: // 308 永久重定向 case HTTP_TEMP_REDIRECT: // 307 臨時重定向 // "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 case HTTP_MULT_CHOICE: // 300 case HTTP_MOVED_PERM: // 301 case HTTP_MOVED_TEMP: // 302 case HTTP_SEE_OTHER: // 303 // Does the client allow redirects? if (!client.followRedirects()) return null; String location = userResponse.header("Location"); if (location == null) return null; HttpUrl url = userResponse.request().url().resolve(location); // Don't follow redirects to unsupported protocols. if (url == null) return null; // If configured, don't follow redirects between SSL and non-SSL. 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(); case HTTP_CLIENT_TIMEOUT: // 408 請求超時 // 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 (!client.retryOnConnectionFailure()) { // The application layer has directed us not to retry the request. return null; } if (userResponse.request().body() instanceof UnrepeatableRequestBody) { return null; } if (userResponse.priorResponse() != null && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) { // We attempted to retry and got another timeout. Give up. return null; } if (retryAfter(userResponse, 0) > 0) { return null; } return userResponse.request(); case HTTP_UNAVAILABLE: // 503 沒法獲取服務 if (userResponse.priorResponse() != null && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) { // We attempted to retry and got another timeout. Give up. return null; } if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) { // specifically received an instruction to retry without delay return userResponse.request(); } return null; default: return null; } } 複製代碼
followUpRequest
根據狀態碼作了如下操做:
一、若是是
407
或者401
,經過建立okhttpClient
時傳入的proxyAuthenticator
、authenticator
去獲取鑑權處理,返回處理後的結果。
二、若是是307
或者308
重定向,若是請求方式不是GET
而且也不是HEAD
,直接返回null
。
三、若是是300
、301
、302
、303
,而且okhttpClient
是容許重定向,就從當前請求頭中獲取Location
,而後從新建立一個Request
再次請求。
四、若是是408
請求超時,會分別判斷可否嘗試鏈接成功、響應體是不是不可重複、以前請求是否超時、是否有延遲時間,若是這些判斷都經過,將會再次請求。
五、若是是503
沒法獲取服務,檢查上一次請求是否能獲取服務,若是能夠獲取,將會從新請求。
到這裏RetryAndFollowUpInterceptor
攔截器已經分析完畢。有關StreamAllocation的講解,各位小夥伴能夠參照OKHttp源碼解析(九):OKHTTP鏈接中三個"核心"RealConnection、ConnectionPool、StreamAllocation。
/** * Bridges from application code to network code. First it builds a network request from a user * request. Then it proceeds to call the network. Finally it builds a user response from the network * response. */ public final class BridgeInterceptor implements Interceptor { private final CookieJar cookieJar; public BridgeInterceptor(CookieJar cookieJar) { this.cookieJar = cookieJar; } @Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); 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) { // 添加Host Header requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { // 添加連接信息Header requestBuilder.header("Connection", "Keep-Alive"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. // 若是在請求投中添加了"Accept-Encoding: gzip"字段,當請求回來時咱們還要解壓縮傳輸流 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()) { // 添加cookie信息 requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { // 添加User-Agent信息 requestBuilder.header("User-Agent", Version.userAgent()); } //獲取原始響應 Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); // 建立Response.Builder對象 Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); // 根據transparentGzip進行判斷,若是transparentGzip爲true表示Accept-Encoding支持gzip壓縮。 // 判斷響應的Content-Encoding是否爲gzip,確認服務返回的響應體是通過gzip壓縮的。 // 判斷響應體是否爲空 if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { // 將響響應的body轉成GzipSource 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
做爲應用和HTTP
之間的橋樑,負責將用戶建立的請求轉成發送到服務器的請求,把服務器的響應轉成用戶可以識別的響應。
BridgeInterceptor
中重要的執行步驟已經在上述源碼的註釋中給出,咱們來總結一下它具體都作了哪些事情。
一、經過添加請求頭,將用戶請求轉成可以進行網絡訪問的請求。
二、執行攔截器鏈中的下一個鏈接器去請求網絡。
三、獲取請求返回的響應,將響應轉成可用的Response。
想要了解okhttp
中的緩存,首先咱們應該瞭解一下Http
相關的緩存機制。這裏有關Http
緩存相關的知識再也不作過多講解,小夥伴有興趣的能夠參考前端也要懂Http緩存機制和http協議緩存機制。
老規矩,咱們首先仍是看一下攔截器內部的intercept
方法都是作了哪些事情。
/** Serves requests from the cache and writes responses to the cache. */ public final class CacheInterceptor implements Interceptor { final InternalCache cache; public CacheInterceptor(InternalCache cache) { this.cache = cache; } @Override public Response intercept(Chain chain) throws IOException { //看一下是否有緩存,若是有直接取出 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); // 一、獲取CacheStrategy(緩存策略對象) CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; ... } ... } 複製代碼
在CacheInterceptor
中咱們先看這麼多代碼,其他的等下分析。在註釋1處,咱們看到會獲取一個CacheStrategy
對象,這個對象是okhttp
的緩存策略對象,在這一步中會調用CacheStrategy.Factory.get()
方法,咱們看一下這裏面究竟作了哪些
/** * Given a request and cached response, this figures out whether to use the network, the cache, or * both. * * <p>Selecting a cache strategy may add conditions to the request (like the "If-Modified-Since" * header for conditional GETs) or warnings to the cached response (if the cached data is * potentially stale). */ public final class CacheStrategy { // 網絡請求對象;若是爲null則表示不使用網絡。 public final @Nullable Request networkRequest; // 須要返回或者須要驗證的緩存響應;若是爲null,則表示該請求不使用緩存。 public final @Nullable Response cacheResponse; //構造函數會將上面兩個字段傳入 CacheStrategy(Request networkRequest, Response cacheResponse) { this.networkRequest = networkRequest; this.cacheResponse = cacheResponse; } ... public static class Factory { ... public Factory(long nowMillis, Request request, Response cacheResponse){ this.nowMillis = nowMillis; this.request = request; this.cacheResponse = cacheResponse; if (cacheResponse != null) { this.sentRequestMillis = cacheResponse.sentRequestAtMillis(); this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis(); Headers headers = cacheResponse.headers(); //設置響應頭部 for (int i = 0, size = headers.size(); i < size; i++) { String fieldName = headers.name(i); String value = headers.value(i); if ("Date".equalsIgnoreCase(fieldName)) { servedDate = HttpDate.parse(value); servedDateString = value; } else if ("Expires".equalsIgnoreCase(fieldName)) { expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { lastModified = HttpDate.parse(value); lastModifiedString = value; } else if ("ETag".equalsIgnoreCase(fieldName)) { etag = value; } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HttpHeaders.parseSeconds(value, -1); } } } } ... // 使用緩存的響應來返回與之相對的策略。 public CacheStrategy get() { //獲取緩存策略 CacheStrategy candidate = getCandidate(); //若是網絡請求不爲null,而且請求裏面的CacheControl字段設置的爲只使用緩存 if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) { // 若是知足上面的條件說明:被禁止使用網絡請求,同時緩存不足 // 這時返回一個網絡請求和緩存都爲null的策略 return new CacheStrategy(null, null); } return candidate; } ... // 假設請求可用時,返回的策略 private CacheStrategy getCandidate() { // 若是沒有緩存,返回網絡請求策略 if (cacheResponse == null) { return new CacheStrategy(request, null); } // 若是請求是Https,同時網絡請求握手結果缺失 if (request.isHttps() && cacheResponse.handshake() == null) { // 返回網絡請求策略 return new CacheStrategy(request, null); } // 這一步是爲了檢查響應可否被緩存 // 若是可以被緩存,則不須要這一步檢查 // 若是不能被緩存,則不能被看成響應返回,這時使用網絡請求策略 if (!isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } // 拿到請求頭中的CacheControl CacheControl requestCaching = request.cacheControl(); // 判斷條件1:沒有緩存,返回網絡請求策略 // 判斷條件2:請求頭中存在If-Modified-Since或者If-None-Match字段,說明緩存過時,此時返回網絡請求策略 if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } // 獲取響應中的CacheControl CacheControl responseCaching = cacheResponse.cacheControl(); // 獲取響應的當前時間 long ageMillis = cacheResponseAge(); // 上一次響應刷新的時間 long freshMillis = computeFreshnessLifetime(); // 若是請求的CacheControl有緩存的最大有效時間(超過這個時間將會被視爲緩存過時) if (requestCaching.maxAgeSeconds() != -1) { // 選擇刷新時間和緩存最大有效時間二者之間最小值 freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } long minFreshMillis = 0; // 若是請求中有最小刷新時間的限制 // min-fresh:表示客戶端但願在指定的時間內獲取最新的響應 if (requestCaching.minFreshSeconds() != -1) { // 更新最小刷新時間的限制 minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } // 最大驗證時間 long maxStaleMillis = 0; // 響應中不是必須驗證而且請求裏面有最大驗證時間 // max-stale:表示客戶端願意接收一個已通過期的資源,能夠設置一個時間,表示響應不能超過的過期時間 if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { // 更新最大驗證時間 maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } // 判斷條件1:響應有緩存 // 判斷條件2:緩存已通過期 // 會在響應頭中添加Warning信息 if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder(); if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); } long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); } // 使用響應的緩存策略 return new CacheStrategy(null, builder.build()); } // 緩存過時 // 在響應頭部添加If-None-Match或者If-Modified-Since String conditionName; String conditionValue; if (etag != null) { conditionName = "If-None-Match"; conditionValue = etag; } else if (lastModified != null) { conditionName = "If-Modified-Since"; conditionValue = lastModifiedString; } else if (servedDate != null) { conditionName = "If-Modified-Since"; conditionValue = servedDateString; } else { // 使用請求緩存策略 return new CacheStrategy(request, null); // No condition! Make a regular request. } Headers.Builder conditionalRequestHeaders = request.headers().newBuilder(); // 添加請求頭 Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue); // 使用請求和緩存策略 Request conditionalRequest = request.newBuilder() .headers(conditionalRequestHeaders.build()) .build(); return new CacheStrategy(conditionalRequest, cacheResponse); } } ... } 複製代碼
上面就是CacheStrategy
的執行過程,咱們發現構建緩存策略時,經過傳入networkRequest
和cacheResponse
,同時控制這兩個對象是否爲null
來控制緩存策略。咱們從上面的代碼能夠看到一共分爲四種狀況。
networkRequest | cacheResponse | 使用策略 |
---|---|---|
null | null | 不使用網絡請求策略和緩存策略 |
非null | 非null | 同時使用網絡請求策略和緩存策略 |
非null | null | 網絡請求策略 |
null | 非null | 緩存策略 |
咱們已經知道了緩存策略,咱們將目光再次投向CacheInterceptor
中去。
public final class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { //獲取緩存 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; //獲取緩存策略 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //策略中的請求對象 Request networkRequest = strategy.networkRequest; //策略中的響應對象 Response cacheResponse = strategy.cacheResponse; //若是緩存不爲空,跟蹤緩存 if (cache != null) { cache.trackResponse(strategy); } ... //不使用網絡請求策略和緩存策略,直接返回響應碼爲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) { //服務器響應碼爲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(); //更新緩存 cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } //代碼執行到這裏,說明緩存中沒有數據 Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); //若是建立OkhttpClient對象時,設置了緩存 if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { //將請求結果緩存到本地 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
中的代碼,咱們能夠將以前的表格更新一下。
networkRequest | cacheResponse | 使用策略 | 對應結果 |
---|---|---|---|
null | null | 不使用網絡請求策略和緩存策略 | 返回狀態碼爲503的錯誤響應 |
非null | 非null | 同時使用網絡請求策略和緩存策略 | 須要請求驗證是否使用緩存 |
非null | null | 網絡請求策略 | 進行網絡請求 |
null | 非null | 緩存策略 | 不使用網絡請求,直接使用緩存 |
看到這裏以後咱們須要看一下okhttp
中的緩存算法,在分析的過程當中咱們一直都在圍繞CacheInterceptor
中的cache
對象進行,這個對象控制了緩存的讀取和寫入,同時這個對象是InternalCache
類,咱們看一下它是怎麼執行的。
/** * OkHttp's internal cache interface. Applications shouldn't implement this: instead use {@link * okhttp3.Cache}. */ public interface InternalCache { Response get(Request request) throws IOException; CacheRequest put(Response response) throws IOException; void remove(Request request) throws IOException; void update(Response cached, Response network); void trackConditionalCacheHit(); void trackResponse(CacheStrategy cacheStrategy); } 複製代碼
從這個類咱們也能夠看到這是okhttp
的緩存接口,直接去使用okhttp3.Cache
類。中這個類中咱們看到裏面有關緩存的讀取、寫入和清除等操做,咱們去看一下okhttp3.Cache
。
public final class Cache implements Closeable, Flushable { final InternalCache internalCache = new InternalCache() { @Override public Response get(Request request) throws IOException { return Cache.this.get(request); } @Override public CacheRequest put(Response response) throws IOException { return Cache.this.put(response); } @Override public void remove(Request request) throws IOException { Cache.this.remove(request); } @Override public void update(Response cached, Response network) { Cache.this.update(cached, network); } @Override public void trackConditionalCacheHit() { Cache.this.trackConditionalCacheHit(); } @Override public void trackResponse(CacheStrategy cacheStrategy) { Cache.this.trackResponse(cacheStrategy); } }; ... Cache(File directory, long maxSize, FileSystem fileSystem) { this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); } ... @Nullable Response get(Request request) { String key = key(request.url()); DiskLruCache.Snapshot snapshot; Entry entry; try { snapshot = cache.get(key); if (snapshot == null) { return null; } } catch (IOException e) { // Give up because the cache cannot be read. return null; } try { entry = new Entry(snapshot.getSource(ENTRY_METADATA)); } catch (IOException e) { Util.closeQuietly(snapshot); return null; } Response response = entry.response(snapshot); if (!entry.matches(request, response)) { Util.closeQuietly(response.body()); return null; } return response; } @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; } if (!requestMethod.equals("GET")) { // Don't cache non-GET responses. We're technically allowed to cache // HEAD requests and some POST requests, but the complexity of doing // so is high and the benefit is low. return null; } if (HttpHeaders.hasVaryAll(response)) { return null; } Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { editor = cache.edit(key(response.request().url())); if (editor == null) { return null; } entry.writeTo(editor); return new CacheRequestImpl(editor); } catch (IOException e) { abortQuietly(editor); return null; } } ... } 複製代碼
咱們看到Cache
中建立了一個InternalCache
對象,當執行get
操做時又會調用Cache
本身的get
方法。同時,在Cache
的構造方法中建立了一個DiskLruCache
對象,Cache
中全部對緩存的操做都是DiskLruCache
對象進行,因此okhttp
中的緩存算法是經過DiskLruCache
進行的。
因爲篇幅關係,這裏就對DiskLruCache
進行過多探究,有興趣的小夥伴能夠參照Android開源框架源碼鑑賞:LruCache與DiskLruCache。
總結一下緩存攔截器的執行步驟吧:
一、從本地磁盤獲取緩存數據
二、經過CacheStrategy
中工廠類的get
方法獲取緩存策略
三、若是不使用網絡請求同時也不使用緩存策略,返回響應碼爲504
的錯誤。
四、使用網絡請求不使用緩存策略,執行攔截鏈中的下一個攔截器,獲取響應並返回
五、若是使用緩存策略可是不使用網絡請求策略,直接使用緩存數據
六、既使用網絡請求同時也使用緩存策略,會根據服務器返回狀態碼進行判斷:若是狀態碼爲304
,說明緩存有效,直接從緩存中獲取數據,同時刷新緩存;若是狀態碼爲200
,說明緩存已經不能使用,直接使用網絡請求數據同時刷新緩存。
七、建立OkhttpClient
對象時若是指定了本地緩存路徑,則會將緩存存入到本地磁盤中。
ConnectInterceptor
這個攔截器爲網絡鏈接攔截,看一下它的做用。
/** Opens a connection to the target server and proceeds to the next interceptor. */ public final class ConnectInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); // 一、獲取RealInterceptorChain中的StreamAllocation對象。 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); // 三、經過streamAllocation獲取RealConnection RealConnection connection = streamAllocation.connection(); // 四、執行下一個攔截器 return realChain.proceed(request, streamAllocation, httpCodec, connection); } } 複製代碼
這個攔截器主要用於對服務建立鏈接,同時去執行下一個攔截器。它主要的執行步驟分爲4步:
一、獲取
RealInterceptorChain
傳遞過來的StreamAllocation
對象。
二、經過調用streamAllocation.newStream
,獲取HttpCodec
對象,這個對象主要做用是加密請求和解密響應。
三、經過調用streamAllocation.connection
,獲取RealConnection
對象,這個對象是用於實際的IO
傳輸。
四、執行下一個攔截器。
這裏咱們還要着重看一下步驟2和步驟3處,先看一下步驟2。
// StreamAllocation -> newStream public HttpCodec newStream( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { int connectTimeout = chain.connectTimeoutMillis(); int readTimeout = chain.readTimeoutMillis(); int writeTimeout = chain.writeTimeoutMillis(); int pingIntervalMillis = client.pingIntervalMillis(); boolean connectionRetryEnabled = client.retryOnConnectionFailure(); try { RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); // 標記1 HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); // 標記2 ... } ... } 複製代碼
在StreamAllocation.newStream
方法,這個方法中獲取了請求、讀取、寫入超時時間,同時還獲取了client
中的配置。最重要的調用了findHealthyConnection
方法,從字面上看,這個方法時用來獲取一個健壯的鏈接。
// StreamAllocation -> findHealthyConnection private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException { while (true) { RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); // 若是是一個全新的鏈接,跳過檢查 synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } // 肯定鏈接是否可用,若是不可用,移出鏈接池而且從新查找可用鏈接 if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; } } 複製代碼
這個方法中經過一個無限循環調用findConnection
方法,看一下吧。
/** * Returns a connection to host a new stream. This prefers the existing connection if it exists, * then the pool, finally building a new connection. */ // 返回RealConnection對象,若是當前存在,直接返回;反之會從鏈接池中查找,若是尚未的話會返回一個全新的鏈接。 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { boolean foundPooledConnection = false; RealConnection result = null; Route selectedRoute = null; Connection releasedConnection; Socket toClose; synchronized (connectionPool) { if (released) throw new IllegalStateException("released"); if (codec != null) throw new IllegalStateException("codec != null"); if (canceled) throw new IOException("Canceled"); // 嘗試獲取一個已經存在的鏈接,這裏須要注意,由於已經存在的鏈接有可能已經被限制建立新流。 releasedConnection = this.connection; toClose = releaseIfNoNewStreams(); //若是已經存在鏈接 if (this.connection != null) { result = this.connection; releasedConnection = null; } ... // 若是沒有已經存在的鏈接。 if (result == null) { // 嘗試從鏈接池中獲取 Internal.instance.get(connectionPool, address, this, null); // 從鏈接池中獲取到鏈接對象 if (connection != null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; } } } closeQuietly(toClose); if (releasedConnection != null) { eventListener.connectionReleased(call, releasedConnection); } if (foundPooledConnection) { eventListener.connectionAcquired(call, result); } // 若是發現已經存在、或者從鏈接池中獲取一個鏈接,直接返回 if (result != null) { return result; } ... // 實在找不到,咱們就建立一個 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.get(connectionPool, address, this, route); if (connection != null) { foundPooledConnection = true; result = connection; this.route = route; break; } } } if (!foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); } // 若是緩存中沒找到鏈接,直接建立一個新的 route = selectedRoute; refusedStreamCount = 0; result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } // If we found a pooled connection on the 2nd time around, we're done. if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; } // Do TCP + TLS handshakes. This is a blocking operation. // http三次握手操做 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; // 將新鏈接放入鏈接池中 Internal.instance.put(connectionPool, result); ... } ... return result; } 複製代碼
這一步獲是爲了獲取一個鏈接,在獲取鏈接以後又調用了newCodec
方法,也就是上面的標記2
處。
// RealConnection -> newCodec 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); } } 複製代碼
這裏查看HTTP
版本,由於HTTP1.1
和HTTP2
之間存在差別。
最後咱們看一下獲取鏈接以後,內部是怎樣作網絡鏈接的。
// RealConnection -> connect public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) { ... while (true) { try { ... establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener); ... } } ... } // RealConnection -> establishProtocol private void establishProtocol(ConnectionSpecSelector connectionSpecSelector, int pingIntervalMillis, Call call, EventListener eventListener) throws IOException { ... eventListener.secureConnectStart(call); connectTls(connectionSpecSelector); eventListener.secureConnectEnd(call, handshake); ... } // RealConnection -> connectTls private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException { Address address = route.address(); SSLSocketFactory sslSocketFactory = address.sslSocketFactory(); boolean success = false; SSLSocket sslSocket = null; try { // Create the wrapper over the connected socket. sslSocket = (SSLSocket) sslSocketFactory.createSocket( rawSocket, address.url().host(), address.url().port(), true /* autoClose */); // Configure the socket's ciphers, TLS versions, and extensions. ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); if (connectionSpec.supportsTlsExtensions()) { Platform.get().configureTlsExtensions( sslSocket, address.url().host(), address.protocols()); } // Force handshake. This can throw! sslSocket.startHandshake(); // block for session establishment SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); // Verify that the socket's certificates are acceptable for the target host. if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) { X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:" + "\n certificate: " + CertificatePinner.pin(cert) + "\n DN: " + cert.getSubjectDN().getName() + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); } // Success! Save the handshake and the ALPN protocol. String maybeProtocol = connectionSpec.supportsTlsExtensions() ? Platform.get().getSelectedProtocol(sslSocket) : null; socket = sslSocket; source = Okio.buffer(Okio.source(socket)); sink = Okio.buffer(Okio.sink(socket)); handshake = unverifiedHandshake; protocol = maybeProtocol != null ? Protocol.get(maybeProtocol) : Protocol.HTTP_1_1; success = true; } catch (AssertionError e) { if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); throw e; } finally { if (sslSocket != null) { Platform.get().afterHandshake(sslSocket); } if (!success) { closeQuietly(sslSocket); } } } 複製代碼
建立鏈接以後會用它進行網絡鏈接,最後會調用RealConnection.connectTls
方法,從這個方法中咱們能夠得出一個結論:okhttp
是基於原生Socket
和OKio
。
/** This is the last interceptor in the chain. It makes a network call to the server. */ // 最後一個攔截器,鏈接服務 public final class CallServerInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { 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(); realChain.eventListener().requestHeadersStart(realChain.call()); // 寫入請求頭 httpCodec.writeRequestHeaders(request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); Response.Builder responseBuilder = null; // 判斷請求方法和請求體 if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { if ("100-continue".equalsIgnoreCase(request.header("Expect"))) // 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. httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. realChain.eventListener().requestBodyStart(realChain.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(realChain.call(), requestBodyOut.successfulCount); } 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(); if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); // 獲取響應頭 responseBuilder = httpCodec.readResponseHeaders(false); } Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .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); response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } realChain.eventListener() .responseHeadersEnd(realChain.call(), response); // 獲取響應體 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; } 複製代碼
這個攔截器是攔截器鏈的最後一個,其主要是鏈接服務獲取響應,將響應返回到上一層攔截器中。
咱們從文章開頭知道,攔截器分爲兩種:一種是應用攔截器,另一種是網絡攔截器。那麼咱們平時開發的過程當中,如何自定義攔截器呢?建立出攔截器以後若是插入到攔截器鏈中呢?
這裏建立攔截器咱們仍是使用官網給出的例子。
class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); Log.i("LoggingInterceptor", String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); Log.i("LoggingInterceptor",String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } 複製代碼
關於添加攔截器須要咱們注意一下,由於網絡攔截器和應用攔截器插入的位置不一樣,而且插入的方法也不一樣。插入應用攔截器時,咱們須要調用addInterceptor
方法;插入網絡攔截器時,咱們須要調用addNetworkInterceptor
方法。
OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new LoggingInterceptor()) .build(); 複製代碼
OkHttpClient okHttpClient = new OkHttpClient.Builder() .addNetworkInterceptor(new LoggingInterceptor()) .build(); 複製代碼
至此,咱們已經將okhttp
中的攔截器探究完畢,能夠用一張圖來做爲總結。
在探究okhttp
中的設計模式時,這裏會找出對應設計模式在okhttp
中的代碼,有關對應的設計模式不作深刻研究,會給出相關文章供小夥伴探究。
這個模式是咱們在okhttp
中第一個遇到的設計模式,在建立OkHttpClient
和Request
都用到了這個模式,固然在建立其餘對象時也用到了這個設計模式。下面是建立OkHttpClient
時的代碼。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; ... } public static final class Builder { ... } } 複製代碼
有關工廠方法設計模式在okhttp
中的應用不是不少,一個是OkHttpClient
類實現的WebSocket.Factory
接口,還有一個就是Call
接口。
public interface WebSocket { ... interface Factory { WebSocket newWebSocket(Request request, WebSocketListener listener); } } 複製代碼
public interface Call extends Cloneable { ... interface Factory { Call newCall(Request request); } } 複製代碼
觀察者設計模式在okhttp
中主要有兩個地方被用到,分別是EventListener
和WebSocketListener
,這兩個都是對生命週期的監聽。
public abstract class EventListener { ... public void callStart(Call call) public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) ... } 複製代碼
public abstract class WebSocketListener { ... public void onOpen(WebSocket webSocket, Response response) public void onMessage(WebSocket webSocket, String text) ... } 複製代碼
單例設計模式因該是咱們最爲熟知的設計模式,在Platform
類中能夠看到其身影。單例設計模式的寫法有不少,可是咱們注意一下爲何Platfrom
會使用這種寫法。
public class Platform { private static final Platform PLATFORM = findPlatform(); ... public static Platform get() { return PLATFORM; } private static Platform findPlatform() { Platform android = AndroidPlatform.buildIfSupported(); if (android != null) { return android; } if (isConscryptPreferred()) { Platform conscrypt = ConscryptPlatform.buildIfSupported(); if (conscrypt != null) { return conscrypt; } } Platform jdk9 = Jdk9Platform.buildIfSupported(); if (jdk9 != null) { return jdk9; } Platform jdkWithJettyBoot = JdkWithJettyBootPlatform.buildIfSupported(); if (jdkWithJettyBoot != null) { return jdkWithJettyBoot; } // Probably an Oracle JDK like OpenJDK. return new Platform(); } ... } 複製代碼
Java設計模式—單例設計模式(Singleton Pattern)徹底解析
策略設計模式在okhttp
中的CookieJar
中可以找到。
public interface CookieJar { CookieJar NO_COOKIES = new CookieJar() { @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { } @Override public List<Cookie> loadForRequest(HttpUrl url) { return Collections.emptyList(); } }; ... } 複製代碼
責任鏈設計模式,這個模式屬於okhttp
中的核心設計模式,具體的就再也不贅述。可是,咱們能夠想一下這個設計模式在Android
其餘的機制中是否有用到過。
到這裏關於okhttp
的攔截器和設計模式就算探究完了,在這個過程當中我發現,本身對設計模式這一塊還不是很熟,因此在寫okhttp
的設計模式相關的知識點時不是很詳細,但願各位不要見怪,設計模式這塊知識也是後面要着重研究的方向之一。仍是那句話,文章有什麼不妥之處,請各位大佬及時指出,本人將不勝感激。
okhttp官網
OKHttp源碼解析(九):OKHTTP鏈接中三個"核心"RealConnection、ConnectionPool、StreamAllocation
前端也要懂Http緩存機制
Android開源框架源碼鑑賞:LruCache與DiskLruCache
賣熱乾麪的啓發 ---Builder 模式
設計模式系列之「工廠方法模式」
Java設計模式—單例設計模式(Singleton Pattern)徹底解析
LOL設計模式之「策略模式」
個人Java設計模式-責任鏈模式