OkHttp3源碼和設(shè)計(jì)模式-1

《打車APP實(shí)戰(zhàn)》課程中乖仇,我們使用 OkHttp3 簡(jiǎn)單搭建了一個(gè)網(wǎng)絡(luò)框架廉油, 實(shí)踐了 OkHttp3 的用法势誊。不過(guò)課程本身的重點(diǎn)是 MVP 架構(gòu)的實(shí)踐剑梳,所以沒(méi)有進(jìn)一步 OkHttp3 底層的實(shí)現(xiàn)細(xì)節(jié)唆貌。本文來(lái)探究一下 OkHttp3 的源碼和其中的設(shè)計(jì)思想。

關(guān)于 OkHttp3 的源碼分析的文章挺多阻荒,不過(guò)大多還是在為了源碼而源碼挠锥。個(gè)人覺(jué)得如果讀源碼不去分析源碼背后的設(shè)計(jì)模式或設(shè)計(jì)思想众羡,那么讀源碼的意義不大侨赡。 同時(shí),如果熟悉的設(shè)計(jì)模式越多粱侣,那么讀某個(gè)框架的源碼的時(shí)候就越容易羊壹,兩者是相輔相成的,這也是許多大牛認(rèn)為多讀源碼能提高編程能力的原因齐婴。

整體架構(gòu)

整體架構(gòu)

為了方面后面的理解油猫,我這里簡(jiǎn)單畫了個(gè)架構(gòu)圖,圖中畫出了 OkHttp3 核心的功能模塊柠偶。為了方便整體理解情妖,這里分了三個(gè)層次: 客戶層睬关、執(zhí)行層和連接層。
首先毡证,客戶層的OkHttpClient 电爹,使用過(guò) OkHttp 網(wǎng)絡(luò)庫(kù)的同學(xué)應(yīng)該都熟悉,在發(fā)送網(wǎng)絡(luò)請(qǐng)求料睛,執(zhí)行層決定怎么處理請(qǐng)求丐箩,比如同步還是異步,同步請(qǐng)求的話直接在當(dāng)前線程完成請(qǐng)求恤煞, 請(qǐng)求要經(jīng)過(guò)多層攔截器處理屎勘; 如果是異步處理,需要 Dispatcher 執(zhí)行分發(fā)策略居扒, 線程池管理執(zhí)行任務(wù)概漱; 又比如,一個(gè)請(qǐng)求下來(lái)苔货,要不要走緩存犀概,如果不走緩存,進(jìn)行網(wǎng)絡(luò)請(qǐng)求夜惭。最后執(zhí)行層將從連接層進(jìn)行網(wǎng)絡(luò) IO 獲取數(shù)據(jù)姻灶。

OkHttpClient

使用過(guò) OkHttp 網(wǎng)絡(luò)庫(kù)的同學(xué)應(yīng)該都熟悉 OkHttpClient , 許多第三方框架都會(huì)提供一個(gè)類似的類作為客戶訪問(wèn)的一個(gè)入口诈茧。 關(guān)于 OkHttpClient 代碼注釋上就說(shuō)的很清楚:

   /**
 * Factory for {@linkplain Call calls}, which can be used to send 
   HTTP requests and read their
 * responses.
 *
 * <h3>OkHttpClients should be shared</h3>
 *
 * <p>OkHttp performs best when you create a single {@code 
 OkHttpClient} instance and reuse it for
 * all of your HTTP calls. This is because each client holds its own 
connection pool and thread
 * pools. Reusing connections and threads reduces latency and 
saves memory. Conversely, creating a
 * client for each request wastes resources on idle pools.
 *
 * <p>Use {@code new OkHttpClient()} to create a shared instance 
with the default settings:
 * <pre>   {@code
 *
 *   // The singleton HTTP client.
 *   public final OkHttpClient client = new OkHttpClient();
 * }</pre>
 *
 * <p>Or use {@code new OkHttpClient.Builder()} to create a shared 
  instance with custom settings:
 * <pre>   {@code
 *
 *   // The singleton HTTP client.
 *   public final OkHttpClient client = new OkHttpClient.Builder()
 *       .addInterceptor(new HttpLoggingInterceptor())
 *       .cache(new Cache(cacheDir, cacheSize))
 *       .build();
 * }</pre>
 *
 ....  省略
*/

簡(jiǎn)單提煉:
1产喉、OkHttpClient, 可以通過(guò) new OkHttpClient() 或 new OkHttpClient.Builder() 來(lái)創(chuàng)建對(duì)象敢会, 但是---特別注意曾沈, OkHttpClient() 對(duì)象最好是共享的, 建議使用單例模式創(chuàng)建鸥昏。 因?yàn)槊總€(gè) OkHttpClient 對(duì)象都管理自己獨(dú)有的線程池和連接池塞俱。 這一點(diǎn)很多同學(xué),甚至在我經(jīng)歷的團(tuán)隊(duì)中就有人踩過(guò)坑吏垮, 每一個(gè)請(qǐng)求都創(chuàng)建一個(gè) OkHttpClient 導(dǎo)致內(nèi)存爆掉障涯。

2、 從上面的整體框架圖膳汪,其實(shí)執(zhí)行層有很多屬性功能是需要OkHttpClient 來(lái)制定唯蝶,例如緩存、線程池遗嗽、攔截器等粘我。如果你是設(shè)計(jì)者你會(huì)怎樣設(shè)計(jì) OkHttpClient ? 建造者模式痹换,OkHttpClient 比較復(fù)雜征字, 太多屬性都弹, 而且客戶的組合需求多樣化, 這種情況下就考慮使用建造者模式匙姜。 new OkHttpClien() 創(chuàng)建對(duì)象缔杉, 內(nèi)部默認(rèn)指定了很多屬性:

 public OkHttpClient() {
   this(new Builder());
}

在看看 new Builder() 的默認(rèn)實(shí)現(xiàn):

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  eventListenerFactory = EventListener.factory(EventListener.NONE);
  proxySelector = ProxySelector.getDefault();
  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;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
  pingInterval = 0;
}

默認(rèn)指定 Dispatcher (管理線程池)、鏈接池搁料、超時(shí)時(shí)間等或详。

3、 內(nèi)部對(duì)于線程池郭计、鏈接池管理有默認(rèn)的管理策略霸琴,例如空閑時(shí)候的線程池、連接池會(huì)在一定時(shí)間自動(dòng)釋放昭伸,但如果你想主動(dòng)去釋放也可以通過(guò)客戶層去釋放梧乘。(很少)

執(zhí)行層

 Response response = mOkHttpClient.newCall(request).execute();

這是應(yīng)用程序中發(fā)起網(wǎng)絡(luò)請(qǐng)求最頂端的調(diào)用,newCall(request) 方法返回 RealCall 對(duì)象庐杨。RealCall 封裝了一個(gè) request 代表一個(gè)請(qǐng)求調(diào)用任務(wù)选调,RealCall 有兩個(gè)重要的方法 execute() 和 enqueue(Callback responseCallback)。 execute() 是直接在當(dāng)前線程執(zhí)行請(qǐng)求灵份,enqueue(Callback responseCallback) 是將當(dāng)前任務(wù)加到任務(wù)隊(duì)列中仁堪,執(zhí)行異步請(qǐng)求。

同步請(qǐng)求

 @Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    // client.dispatcher().executed(this) 內(nèi)部只是記錄下執(zhí)行狀態(tài)填渠,
    client.dispatcher().executed(this);
    // 真正執(zhí)行發(fā)生在這里
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    // 后面再解釋
    client.dispatcher().finished(this);
  }
}

執(zhí)行方法關(guān)鍵在 getResponseWithInterceptorChain() 這個(gè)方法中弦聂, 關(guān)于 client.dispatcher().executed(this) 和 client.dispatcher().finished(this); 這里先忽略 ,后面再看氛什。

請(qǐng)求過(guò)程要從執(zhí)行層說(shuō)到連接層莺葫,涉及到 getResponseWithInterceptorChain 方法中組織的各個(gè)攔截器的執(zhí)行過(guò)程,內(nèi)容比較多枪眉,后面章節(jié)在說(shuō)捺檬。先說(shuō)說(shuō) RealCall 中 enqueue(Callback responseCallback) 方法涉及的異步請(qǐng)求和線程池。

Dispatcher 和線程池

 @Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
}
 captureCallStackTrace();
 client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

調(diào)用了 dispatcher 的 enqueue()方法
dispatcher 結(jié)合線程池完成了所有異步請(qǐng)求任務(wù)的調(diào)配贸铜。

synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

Dispatcher調(diào)度

dispatcher 主要維護(hù)了三兩個(gè)隊(duì)列 readyAsyncCalls堡纬、runningAsyncCalls 和 runningSyncCalls,分別代表了準(zhǔn)備中隊(duì)列萨脑, 正在執(zhí)行的異步任務(wù)隊(duì)列和正在執(zhí)行的同步隊(duì)列隐轩, 重點(diǎn)關(guān)注下前面兩個(gè)饺饭。
現(xiàn)在我們可以回頭來(lái)看看前面 RealCall 方法 client.dispatcher().finished(this) 這個(gè)疑點(diǎn)了渤早。 在每個(gè)任務(wù)執(zhí)行完之后要回調(diào) client.dispatcher().finished(this) 方法, 主要是要將當(dāng)前任務(wù)從 runningAsyncCalls 或 runningSyncCalls 中移除瘫俊, 同時(shí)把 readyAsyncCalls 的任務(wù)調(diào)度到 runningAsyncCalls 中并執(zhí)行鹊杖。

線程池

public synchronized ExecutorService executorService() {
  if (executorService == null) {
  executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
 }
  return executorService;
 }

默認(rèn)實(shí)現(xiàn)是一個(gè)不限容量的線程池 悴灵, 線程空閑時(shí)存活時(shí)間為 60 秒。線程池實(shí)現(xiàn)了對(duì)象復(fù)用骂蓖,降低線程創(chuàng)建開(kāi)銷积瞒,從設(shè)計(jì)模式上來(lái)講,使用了享元模式登下。

責(zé)任鏈 (攔截器執(zhí)行過(guò)程)

  Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
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);
  }
}

要跟蹤 Okhttp3 的網(wǎng)絡(luò)請(qǐng)求任務(wù)執(zhí)行過(guò)程 茫孔,需要看懂以上代碼,看懂以上代碼必須理解設(shè)計(jì)模式-責(zé)任鏈被芳。在責(zé)任鏈模式里缰贝,很多對(duì)象由每一個(gè)對(duì)象對(duì)其下家的引用而連接起來(lái)形成一條鏈。請(qǐng)求在這個(gè)鏈上傳遞畔濒,直到鏈上的某一個(gè)對(duì)象決定處理此請(qǐng)求剩晴。發(fā)出這個(gè)請(qǐng)求的客戶端并不知道鏈上的哪一個(gè)對(duì)象最終處理這個(gè)請(qǐng)求,這使得系統(tǒng)可以在不影響客戶端的情況下動(dòng)態(tài)地重新組織和分配責(zé)任侵状。 網(wǎng)絡(luò)請(qǐng)求過(guò)程赞弥,是比較典型的復(fù)合責(zé)任鏈的場(chǎng)景,比如請(qǐng)求傳遞過(guò)程趣兄,我們需要做請(qǐng)求重試绽左, 需要執(zhí)行緩存策略, 需要建立連接等艇潭, 每一個(gè)處理節(jié)點(diǎn)可以由一個(gè)鏈上的對(duì)象來(lái)處理妇菱; 同時(shí)客戶端使用的時(shí)候可能也會(huì)在請(qǐng)求過(guò)程中做一些應(yīng)用層需要的事情,比如我要記錄網(wǎng)絡(luò)請(qǐng)求的耗時(shí)暴区、日志等闯团, 責(zé)任鏈還可以動(dòng)態(tài)的擴(kuò)展到客戶業(yè)務(wù)方。

攔截器

在 OkHttp3 的攔截器鏈中仙粱, 內(nèi)置了5個(gè)默認(rèn)的攔截器房交,分別用于重試、請(qǐng)求對(duì)象轉(zhuǎn)換伐割、緩存候味、鏈接、網(wǎng)絡(luò)讀寫隔心。
以上方法中先是添加了客戶端自定義的連接器白群,然后在分別添加內(nèi)置攔截器。

Okhttp3 攔截器類圖

攔截器類圖

現(xiàn)在我們把對(duì) OkHttp 網(wǎng)絡(luò)請(qǐng)求執(zhí)行過(guò)程的研究轉(zhuǎn)化對(duì)每個(gè)攔截器處理的研究硬霍。

retryAndFollowUpInterceptor 重試機(jī)制

重試流程

retryAndFollowUpInterceptor 處于內(nèi)置攔截器鏈的最頂端帜慢,在一個(gè)循環(huán)中執(zhí)行重試過(guò)程:
1、首先下游攔截器在處理網(wǎng)絡(luò)請(qǐng)求過(guò)程如拋出異常,則通過(guò)一定的機(jī)制判斷一下當(dāng)前鏈接是否可恢復(fù)的(例如粱玲,異常是不是致命的躬柬、有沒(méi)有更多的線路可以嘗試等),如果可恢復(fù)則重試抽减,否則跳出循環(huán)允青。
2、 如果沒(méi)什么異常則校驗(yàn)下返回狀態(tài)卵沉、代理鑒權(quán)颠锉、重定向等,如果需要重定向則繼續(xù)史汗,否則直接跳出循環(huán)返回結(jié)果木柬。
3、 如果重定向淹办,則要判斷下是否已經(jīng)達(dá)到最大可重定向次數(shù)眉枕, 達(dá)到則拋出異常,跳出循環(huán)怜森。

@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 創(chuàng)建連接池管理對(duì)象
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 {
  // 將請(qǐng)求處理傳遞下游攔截器處理
    response = ((RealInterceptorChain) chain).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.
     //  線路異常速挑,判斷滿足可恢復(fù)條件,滿足則繼續(xù)循環(huán)重試
    if (!recover(e.getLastConnectException(), false, request)) {
      throw e.getLastConnectException();
    }
    releaseConnection = false;
    continue;
  } catch (IOException e) {
    // An attempt to communicate with a server failed. The request may have been sent.

// IO異常副硅,判斷滿足可恢復(fù)條件姥宝,滿足則繼續(xù)循環(huán)重試
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}

  // Attach the prior response if it exists. Such responses never have a body.
  if (priorResponse != null) {
    response = response.newBuilder()
        .priorResponse(priorResponse.newBuilder()
                .body(null)
                .build())
        .build();
  }
 //  是否需要重定向
  Request followUp = followUpRequest(response);

  if (followUp == null) {
    if (!forWebSocket) {
      streamAllocation.release();
    }
    // 不需要重定向,正常返回結(jié)果
    return response;
  }

  closeQuietly(response.body());

  if (++followUpCount > MAX_FOLLOW_UPS) {
   // 達(dá)到次數(shù)限制
    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;
 }
}

BridgeInterceptor

  /**
  * 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.
 */

這個(gè)攔截器比較簡(jiǎn)單恐疲, 一個(gè)實(shí)現(xiàn)應(yīng)用層和網(wǎng)絡(luò)層直接的數(shù)據(jù)格式編碼的橋腊满。 第一: 把應(yīng)用層客戶端傳過(guò)來(lái)的請(qǐng)求對(duì)象轉(zhuǎn)換為 Http 網(wǎng)絡(luò)協(xié)議所需字段的請(qǐng)求對(duì)象。 第二培己, 把下游網(wǎng)絡(luò)請(qǐng)求結(jié)果轉(zhuǎn)換為應(yīng)用層客戶所需要的響應(yīng)對(duì)象碳蛋。 這個(gè)設(shè)計(jì)思想來(lái)自適配器設(shè)計(jì)模式,大家可以去體會(huì)一下省咨。

CacheInterceptor 數(shù)據(jù)策略(策略模式)

CacheInterceptor 實(shí)現(xiàn)了數(shù)據(jù)的選擇策略肃弟, 來(lái)自網(wǎng)絡(luò)還是來(lái)自本地? 這個(gè)場(chǎng)景也是比較契合策略模式場(chǎng)景零蓉, CacheInterceptor 需要一個(gè)策略提供者提供它一個(gè)策略(錦囊)笤受, CacheInterceptor 根據(jù)這個(gè)策略去選擇走網(wǎng)絡(luò)數(shù)據(jù)還是本地緩存。

緩存策略

緩存的策略過(guò)程:
1敌蜂、 請(qǐng)求頭包含 "If-Modified-Since" 或 "If-None-Match" 暫時(shí)不走緩存
2箩兽、 客戶端通過(guò) cacheControl 指定了無(wú)緩存,不走緩存
3章喉、客戶端通過(guò) cacheControl 指定了緩存汗贫,則看緩存過(guò)期時(shí)間身坐,符合要求走緩存。
4芳绩、 如果走了網(wǎng)絡(luò)請(qǐng)求,響應(yīng)狀態(tài)碼為 304(只有客戶端請(qǐng)求頭包含 "If-Modified-Since" 或 "If-None-Match" 撞反,服務(wù)器數(shù)據(jù)沒(méi)變化的話會(huì)返回304狀態(tài)碼妥色,不會(huì)返回響應(yīng)內(nèi)容), 表示客戶端繼續(xù)用緩存遏片。

@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
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()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
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 {
// 執(zhí)行網(wǎng)絡(luò)
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());
}
}

// 返回 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();

    // Update the cache after combining headers but before stripping the
    // Content-Encoding header (as performed by initContentStream()).
    cache.trackConditionalCacheHit();
    cache.update(cacheResponse, response);
    return response;
  } else {
    closeQuietly(cacheResponse.body());
  }
}
Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();
if (cache != null) {
  if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
    //  存儲(chǔ)緩存
    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;
}

緩存實(shí)現(xiàn)

OkHttp3 內(nèi)部緩存默認(rèn)實(shí)現(xiàn)是使用的 DiskLruCache嘹害, 這部分代碼有點(diǎn)繞:

interceptors.add(new CacheInterceptor(client.internalCache()));
初始化 CacheInterceptor 時(shí)候 client.internalCache() 這里獲取OkHttpClient的緩存。

InternalCache internalCache() {
  return cache != null ? cache.internalCache : internalCache;
}

注意到吮便, 這個(gè)方法是非公開(kāi)的笔呀。 客戶端只能通過(guò) OkhttpClient.Builder的 cache(cache) 定義緩存, cache 是一個(gè) Cache 對(duì)實(shí)例髓需。 在看看 Cache 的內(nèi)部實(shí)現(xiàn)许师, 內(nèi)部有一個(gè) InternalCache 的內(nèi)部類實(shí)現(xiàn)硫惕。 內(nèi)部調(diào)用時(shí)使用 InternalCache 實(shí)例提供接口,而存儲(chǔ)邏輯在 Cache 中實(shí)現(xiàn)溺忧。

緩存

Cache 為什么不直接實(shí)現(xiàn) InternalCache ,而通過(guò)持有 InternalCache 的一個(gè)內(nèi)部類對(duì)象來(lái)實(shí)現(xiàn)方法循狰? 是希望控制緩存實(shí)現(xiàn)咧擂, 不希望用戶外部去實(shí)現(xiàn)緩存逞盆,同時(shí)對(duì)內(nèi)保持一定的擴(kuò)展。

鏈接層

RealCall 封裝了請(qǐng)求過(guò)程松申, 組織了用戶和內(nèi)置攔截器云芦,其中內(nèi)置攔截器 retryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor 完執(zhí)行層的大部分邏輯 ,ConnectInterceptor -> CallServerInterceptor 兩個(gè)攔截器開(kāi)始邁向連接層最終完成網(wǎng)絡(luò)請(qǐng)求贸桶。 關(guān)于 ConnectInterceptor -> CallServerInterceptor 要結(jié)合連接層一起說(shuō)明舅逸,限于篇幅, 下一篇文章:《OkHttp3源碼和設(shè)計(jì)模式-2 》接著分析皇筛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堡赔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子设联,更是在濱河造成了極大的恐慌善已,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件离例,死亡現(xiàn)場(chǎng)離奇詭異换团,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)宫蛆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門艘包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)的猛,“玉大人,你說(shuō)我怎么就攤上這事想虎∝宰穑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵舌厨,是天一觀的道長(zhǎng)岂却。 經(jīng)常有香客問(wèn)我,道長(zhǎng)裙椭,這世上最難降的妖魔是什么躏哩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮揉燃,結(jié)果婚禮上扫尺,老公的妹妹穿的比我還像新娘。我一直安慰自己炊汤,他們只是感情好正驻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著抢腐,像睡著了一般拨拓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氓栈,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天渣磷,我揣著相機(jī)與錄音,去河邊找鬼授瘦。 笑死醋界,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的提完。 我是一名探鬼主播形纺,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼徒欣!你這毒婦竟也來(lái)了逐样?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤打肝,失蹤者是張志新(化名)和其女友劉穎脂新,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體粗梭,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡争便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了断医。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滞乙。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奏纪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斩启,到底是詐尸還是另有隱情序调,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布兔簇,位于F島的核電站发绢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏男韧。R本人自食惡果不足惜朴摊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一默垄、第九天 我趴在偏房一處隱蔽的房頂上張望此虑。 院中可真熱鬧,春花似錦口锭、人聲如沸朦前。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)韭寸。三九已至,卻和暖如春荆隘,著一層夾襖步出監(jiān)牢的瞬間恩伺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工椰拒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晶渠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓燃观,卻偏偏與公主長(zhǎng)得像褒脯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缆毁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容