okhttp源碼分析(三)-CacheInterceptor過(guò)濾器

1.okhttp源碼分析(一)——基本流程(超詳細(xì))
2.okhttp源碼分析(二)——RetryAndFollowUpInterceptor過(guò)濾器
3.okhttp源碼分析(三)——CacheInterceptor過(guò)濾器
4.okhttp源碼分析(四)——ConnectInterceptor過(guò)濾器
5.okhttp源碼分析(五)——CallServerInterceptor過(guò)濾器

前言

前一篇博客分析了RetryAndFollowUpInterceptor過(guò)濾器,緊接著下一個(gè)過(guò)濾器應(yīng)該是BridgeInterceptor,但是這個(gè)過(guò)濾器的作用主要是在對(duì)Request和Resposne的封裝,源碼理解起來(lái)也比較好理解,所以就沒(méi)有分析這個(gè)過(guò)濾器。直接分析下一個(gè)過(guò)濾器CacheInterceptor,其實(shí)從名字就可以看出這個(gè)過(guò)濾器的主要作用就是緩存。

分析

1.宏觀流程

和上一篇博客的分析相同,按照我的理解,我將過(guò)濾器中最關(guān)鍵的方法刪減了一下,有助于從宏觀上大體對(duì)這個(gè)過(guò)濾器進(jìn)行理解。

@Override public Response intercept(Chain chain) throws IOException {
    //1
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    //2
    if (networkRequest == null && cacheResponse == null) {
      return new Response;
    }
    //3
    if (networkRequest == null) {
      return cacheResponse;
    }
    //4
      networkResponse = chain.proceed(networkRequest);
    //5
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
        return response;
      } 
    }
    //6
    Response response = networkResponse;
    //7
    cache.put(response);

    return response;
  }

好吧,刪了點(diǎn)還是比較多的,但是剩下的代碼已經(jīng)比較直白了,很好理解了。其實(shí)看過(guò)濾器看多了也基本上掌握了基本法,找最關(guān)鍵的那行代碼chain.proceed(networkRequest);,上面就是請(qǐng)求前過(guò)濾器做的事,下面就是請(qǐng)求后過(guò)濾器做的事。
總體上看:

//1
Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

1.嘗試通過(guò)這個(gè)Request拿緩存。

//2
    if (networkRequest == null && cacheResponse == null) {
      return new Response.code(504);
    }

2.如果不允許使用網(wǎng)絡(luò)并且緩存為空,新建一個(gè)504的Resposne返回。

//3
    if (networkRequest == null) {
      return cacheResponse;
    }

3.如果不允許使用網(wǎng)絡(luò),但是有緩存,返回緩存。

//4
      networkResponse = chain.proceed(networkRequest);

4.鏈?zhǔn)秸{(diào)用下一個(gè)過(guò)濾器。

//5
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
        return response;
      } 
    }

5.如果緩存不為空,但是網(wǎng)絡(luò)請(qǐng)求得來(lái)的返回碼是304(如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。)則使用緩存的響應(yīng)。

//6
    Response response = networkResponse;
    //7
    cache.put(response);

    return response;

6、7.使用網(wǎng)絡(luò)請(qǐng)求得到的Resposne,并且將這個(gè)Resposne緩存起來(lái)(前提當(dāng)然是能緩存)。
接下來(lái)就是腦袋都大的細(xì)節(jié)了,我也不敢說(shuō)分析的十分詳細(xì),只能就我的理解總體分析,學(xué)習(xí)。

2.過(guò)程細(xì)節(jié)

@Override public Response intercept(Chain chain) throws IOException {
    //默認(rèn)cache為null,可以配置cache,不為空嘗試獲取緩存中的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存
    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.
    //如果緩存策略中禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=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 we don't need the network, we're done.
    //不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //直接走后續(xù)過(guò)濾器
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //當(dāng)緩存響應(yīng)和網(wǎng)絡(luò)響應(yīng)同時(shí)存在的時(shí)候,選擇用哪個(gè)
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶
        // 只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。
        //則使用緩存的響應(yīng)
        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());
      }
    }
    //使用網(wǎng)絡(luò)響應(yīng)
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //所以默認(rèn)創(chuàng)建的OkHttpClient是沒(méi)有緩存的
    if (cache != null) {
      //將響應(yīng)緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        //緩存Resposne的Header信息
        CacheRequest cacheRequest = cache.put(response);
        //緩存body
        return cacheWritingResponse(cacheRequest, response);
      }
      //只能緩存GET....不然移除request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

首先看第一行代碼,一開(kāi)始以為很簡(jiǎn)單,但看了后才發(fā)現(xiàn)里面涉及的流程非常麻煩。

Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

從邏輯上看,這里還是比較好理解的,通過(guò)Request嘗試從緩存成功拿對(duì)應(yīng)的緩存Resposne,如果拿到了則賦值,沒(méi)有則為null。這里重點(diǎn)就要看怎么取緩存了。其實(shí)CacheInterceptor重點(diǎn)比較難以理解的就是:拿緩存,緩存策略,存緩存

public final class CacheInterceptor implements Interceptor {
  final InternalCache cache;

  public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
  }
}

//======================RealCall.java======================
interceptors.add(new CacheInterceptor(client.internalCache()));

首先可以看到這里的cache是InternalCache類型,而且是在構(gòu)造函數(shù)的時(shí)候調(diào)用的。并且通過(guò)RealCall也可以看到,構(gòu)造這個(gè)過(guò)濾器的時(shí)候傳入的是我們構(gòu)造的OkHttpClient中設(shè)置的interanlCache,而當(dāng)我們用默認(rèn)方式構(gòu)造OkHttpClient的時(shí)候是不會(huì)創(chuàng)建緩存的,也就是internalCache=null的

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);
}

不出意外,InternalCache是一個(gè)接口,OkHttp充分貫徹了面向接口編程。接著查找OkHttp中哪個(gè)實(shí)現(xiàn)了或者說(shuō)使用了這個(gè)接口,對(duì)應(yīng)找到了Cache這個(gè)類。

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);
    }
  };

@Nullable Response get(Request request) {
    String key = key(request.url());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      snapshot = cache.get(key);
      if (snapshot == null) {
        //沒(méi)拿到,返回null
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

    try {
      //創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }
    //得到緩存構(gòu)建得到的response
    Response response = entry.response(snapshot);

    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }

可以看到,Cache中實(shí)現(xiàn)了InternalCache這個(gè)接口,get()方法對(duì)應(yīng)調(diào)用的是Cache類中的get方法。所以現(xiàn)在就要看get方法了。

String key = key(request.url());

首先,通過(guò)這行代碼我們了解到,緩存的Key是和request的url直接相關(guān)的。這里通過(guò)url,得到了緩存的key。

final DiskLruCache cache;
=============================
DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      snapshot = cache.get(key);
      if (snapshot == null) {
        //沒(méi)拿到,返回null
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

下面剛開(kāi)始看到的時(shí)候?qū)Ω鞣N變量是很難理解的,這里就先不要管,隨著后面分析的深入,會(huì)理解這里的snapshot變量。可以看到這里得到key后,又會(huì)走cache.get()方法,好吧,又要再進(jìn)入看了。首先要明白,這里的cache對(duì)應(yīng)的類型是DiskLruCache。

public synchronized Snapshot get(String key) throws IOException {
    //總結(jié)來(lái)說(shuō)就是對(duì)journalFile文件的操作,有則刪除無(wú)用冗余的信息,構(gòu)建新文件,沒(méi)有則new一個(gè)新的
    initialize();
    //判斷是否關(guān)閉,如果緩存損壞了,會(huì)被關(guān)閉
    checkNotClosed();
    //檢查key是否滿足格式要求,正則表達(dá)式
    validateKey(key);
    //獲取key對(duì)應(yīng)的entry
    Entry entry = lruEntries.get(key);
    if (entry == null || !entry.readable) return null;
    //獲取entry里面的snapshot的值
    Snapshot snapshot = entry.snapshot();
    if (snapshot == null) return null;
    //有則計(jì)數(shù)器+1
    redundantOpCount++;
    //把這個(gè)內(nèi)容寫(xiě)入文檔中
    journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
    //判斷是否達(dá)清理?xiàng)l件
    if (journalRebuildRequired()) {

      executor.execute(cleanupRunnable);
    }

    return snapshot;
  }

進(jìn)入到DisLruCache內(nèi)部,首先執(zhí)行的是initialize()方法。

public synchronized void initialize() throws IOException {
    //斷言,當(dāng)持有自己鎖的時(shí)候。繼續(xù)執(zhí)行,沒(méi)有持有鎖,直接拋異常
    assert Thread.holdsLock(this);
    //如果初始化過(guò),則直接跳出
    if (initialized) {
      return; // Already initialized.
    }

    // If a bkp file exists, use it instead.
    //如果有journalFileBackup這個(gè)文件
    if (fileSystem.exists(journalFileBackup)) {
      // If journal file also exists just delete backup file.
      //如果有journalFile這個(gè)文件
      if (fileSystem.exists(journalFile)) {
        //刪除journalFileBackup這個(gè)文件
        fileSystem.delete(journalFileBackup);
      } else {
        //沒(méi)有journalFile這個(gè)文件,并且有journalFileBackup這個(gè)文件,則將journalFileBackup改名為journalFile
        fileSystem.rename(journalFileBackup, journalFile);
      }
    }
    //最后的結(jié)果只有兩種:1.什么都沒(méi)有2.有journalFile文件

    // Prefer to pick up where we left off.
    if (fileSystem.exists(journalFile)) {
      //如果有journalFile文件
      try {
        readJournal();
        processJournal();
        //標(biāo)記初始化完成
        initialized = true;
        return;
      } catch (IOException journalIsCorrupt) {
        Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
            + journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
      }

      // The cache is corrupted, attempt to delete the contents of the directory. This can throw and
      // we'll let that propagate out as it likely means there is a severe filesystem problem.
      try {
        //有緩存損壞導(dǎo)致異常,則刪除緩存目錄下所有文件
        delete();
      } finally {
        closed = false;
      }
    }
    //如果沒(méi)有則重新創(chuàng)建一個(gè)
    rebuildJournal();
    //標(biāo)記初始化完成,無(wú)論有沒(méi)有journal文件,initialized都會(huì)標(biāo)記為true,只執(zhí)行一遍
    initialized = true;
  }

總算不用再進(jìn)入看了,這里assert斷言保證這個(gè)方法是線程安全的。接著通過(guò)對(duì)initialized變量來(lái)判斷,如果初始化過(guò),則直接return。

//如果有journalFileBackup這個(gè)文件
    if (fileSystem.exists(journalFileBackup)) {
      // If journal file also exists just delete backup file.
      //如果有journalFile這個(gè)文件
      if (fileSystem.exists(journalFile)) {
        //刪除journalFileBackup這個(gè)文件
        fileSystem.delete(journalFileBackup);
      } else {
        //沒(méi)有journalFile這個(gè)文件,并且有journalFileBackup這個(gè)文件,則將journalFileBackup改名為journalFile
        fileSystem.rename(journalFileBackup, journalFile);
      }
    }

這里首先說(shuō)明一下journalFile指的是日志文件,是對(duì)緩存一系列操作的記錄,不影響緩存的執(zhí)行流程。
可以看到這里有兩個(gè)文件journalFile和journalFileBackup,從名字上可以確定,一個(gè)是備份文件,一個(gè)是記錄文件,隨著后面的分析,會(huì)發(fā)現(xiàn)緩存中充分利用的兩個(gè)文件,這種形式,一個(gè)用于保存,一個(gè)用于編輯操作。
這里的判斷就很好理解了,如果有journalFileBackup這個(gè)文件,并且有journalFile這個(gè)文件,則刪除journalFileBackup這個(gè)沒(méi)用的文件;如果沒(méi)有journalFile但是有journalFileBackup這個(gè)文件,則將journalFileBackup命名為journalFile。最終可以得出,最后用于保存的其實(shí)是journalFile文件。這里執(zhí)行完后最后的結(jié)果只有兩種:1.什么都沒(méi)有2.有journalFile文件

if (fileSystem.exists(journalFile)) {
      //如果有journalFile文件
      try {
        readJournal();
        processJournal();
        //標(biāo)記初始化完成
        initialized = true;
        return;
      } catch (IOException journalIsCorrupt) {
        Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
            + journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
      }

      // The cache is corrupted, attempt to delete the contents of the directory. This can throw and
      // we'll let that propagate out as it likely means there is a severe filesystem problem.
      try {
        //有緩存損壞導(dǎo)致異常,則刪除緩存目錄下所有文件
        delete();
      } finally {
        closed = false;
      }
    }

當(dāng)存在journalFile,執(zhí)行readJournal(),讀取journalFile文件。

private void readJournal() throws IOException {
    //利用Okio讀取journalFile文件
    BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
    try {
      String magic = source.readUtf8LineStrict();
      String version = source.readUtf8LineStrict();
      String appVersionString = source.readUtf8LineStrict();
      String valueCountString = source.readUtf8LineStrict();
      String blank = source.readUtf8LineStrict();
      //保證和默認(rèn)值相同
      if (!MAGIC.equals(magic)
          || !VERSION_1.equals(version)
          || !Integer.toString(appVersion).equals(appVersionString)
          || !Integer.toString(valueCount).equals(valueCountString)
          || !"".equals(blank)) {
        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
            + valueCountString + ", " + blank + "]");
      }

      int lineCount = 0;
      while (true) {
        try {
          //逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
          readJournalLine(source.readUtf8LineStrict());
          lineCount++;
        } catch (EOFException endOfJournal) {
          break;
        }
      }
      //日志操作的記錄數(shù)=總行數(shù)-lruEntries中實(shí)際add的行數(shù)
      redundantOpCount = lineCount - lruEntries.size();
      //source.exhausted()表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false
      // If we ended on a truncated line, rebuild the journal before appending to it.
      if (!source.exhausted()) {
        //如果有多余的字節(jié),則重新構(gòu)建下journal文件
        rebuildJournal();
      } else {
        //獲取這個(gè)文件的Sink,以便Writer
        journalWriter = newJournalWriter();
      }
    } finally {
      Util.closeQuietly(source);
    }
  }

可以看到這里用到了使用OkHttp必須要依賴的庫(kù)Okio,這個(gè)庫(kù)內(nèi)部對(duì)輸入輸出流進(jìn)行了很多優(yōu)化,分幀讀取寫(xiě)入,幀還有池的概念,具體原理可以網(wǎng)上去學(xué)習(xí)。

String magic = source.readUtf8LineStrict();
      String version = source.readUtf8LineStrict();
      String appVersionString = source.readUtf8LineStrict();
      String valueCountString = source.readUtf8LineStrict();
      String blank = source.readUtf8LineStrict();
      //保證和默認(rèn)值相同
      if (!MAGIC.equals(magic)
          || !VERSION_1.equals(version)
          || !Integer.toString(appVersion).equals(appVersionString)
          || !Integer.toString(valueCount).equals(valueCountString)
          || !"".equals(blank)) {
        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
            + valueCountString + ", " + blank + "]");
      }

這里利用Okio讀取journalFile,前面主要是逐行讀取一些參數(shù),進(jìn)行校驗(yàn),保證這些參數(shù)的正確性。

int lineCount = 0;
      while (true) {
        try {
          //逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
          readJournalLine(source.readUtf8LineStrict());
          lineCount++;
        } catch (EOFException endOfJournal) {
          break;
        }
      }

校驗(yàn)成功了,就進(jìn)行逐行讀取,所以這里需要看一下readJournalLine()方法。

private void readJournalLine(String line) throws IOException {
    //記錄第一個(gè)空串的位置
    int firstSpace = line.indexOf(' ');
    if (firstSpace == -1) {
      throw new IOException("unexpected journal line: " + line);
    }

    int keyBegin = firstSpace + 1;
    //記錄第二個(gè)空串的位置
    int secondSpace = line.indexOf(' ', keyBegin);
    final String key;
    if (secondSpace == -1) {
      //如果中間沒(méi)有空串,則直接截取得到key
      key = line.substring(keyBegin);
      //如果解析出來(lái)的是"REMOVE skjdglajslkgjl"這樣以REMOVE開(kāi)頭
      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
        //移除這個(gè)key,lruEntries是LinkedHashMap
        lruEntries.remove(key);
        return;
      }
    } else {
      //解析兩個(gè)空格間的字符串為key
      key = line.substring(keyBegin, secondSpace);
    }
    //取出Entry對(duì)象
    Entry entry = lruEntries.get(key);
    //如果Enty對(duì)象為null
    if (entry == null) {
      //new一個(gè)Entry,put進(jìn)去
      entry = new Entry(key);
      lruEntries.put(key, entry);
    }
    //如果是“CLEAN 1 2”這樣的以CLAEN開(kāi)頭
    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
      //取第二個(gè)空格后面的字符串,parts變成[1,2]
      String[] parts = line.substring(secondSpace + 1).split(" ");
      //可讀
      entry.readable = true;
      //不被編輯
      entry.currentEditor = null;
      //設(shè)置長(zhǎng)度
      entry.setLengths(parts);
    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
      //如果是“DIRTY lskdjfkl”這樣以DIRTY開(kāi)頭,新建一個(gè)Editor
      entry.currentEditor = new Editor(entry);
    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
      //如果是“READ slkjl”這樣以READ開(kāi)頭,不需要做什么事
      // This work was already done by calling lruEntries.get().
    } else {
      throw new IOException("unexpected journal line: " + line);
    }
  }

這里一開(kāi)始可能比較難以理解,說(shuō)明一下journalFile每一行的保存格式是這樣的:REMOVE sdkjlg 2341 1234
第一個(gè)空格前面代表這條日志的操作內(nèi)容,后面的第一個(gè)個(gè)保存的是key,后面這兩個(gè)內(nèi)容根據(jù)前面的操作存入緩存內(nèi)容對(duì)應(yīng)的length...

if (secondSpace == -1) {
      //如果中間沒(méi)有空串,則直接截取得到key
      key = line.substring(keyBegin);
      //如果解析出來(lái)的是"REMOVE skjdglajslkgjl"這樣以REMOVE開(kāi)頭
      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
        //移除這個(gè)key,lruEntries是LinkedHashMap
        lruEntries.remove(key);
        return;
      }
    } else {
      //解析兩個(gè)空格間的字符串為key
      key = line.substring(keyBegin, secondSpace);
    }

如果沒(méi)有第二個(gè)空格,那么數(shù)據(jù)格式就是這樣的REMOVE skjdglajslkgjl
截取第一個(gè)空格后面的內(nèi)容作為key,如果是以REMOVE開(kāi)頭,則從lruEntries中移除這個(gè)key對(duì)應(yīng)的緩存。

final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);

這里說(shuō)明一下,使用一個(gè)LinkedHashMap保存的。
如果有第二個(gè)空格,則還是去第一個(gè)和第二個(gè)空格之間的內(nèi)容當(dāng)做key。

//取出Entry對(duì)象
    Entry entry = lruEntries.get(key);
    //如果Enty對(duì)象為null
    if (entry == null) {
      //new一個(gè)Entry,put進(jìn)去
      entry = new Entry(key);
      lruEntries.put(key, entry);
    }

并且嘗試取這個(gè)key對(duì)應(yīng)的Entry,如果沒(méi)有,則new一個(gè)put進(jìn)入。

//如果是“CLEAN jklldsg 2 5”這樣的以CLAEN開(kāi)頭
    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
      //取第二個(gè)空格后面的字符串,parts變成[1,2]
      String[] parts = line.substring(secondSpace + 1).split(" ");
      //可讀
      entry.readable = true;
      //不被編輯
      entry.currentEditor = null;
      //設(shè)置長(zhǎng)度
      entry.setLengths(parts);
    }

CLEAN jklldsg 2 5如果是以CLEAN開(kāi)頭的話,則將取出key后面的數(shù)組,設(shè)置可讀,不可編輯,設(shè)置entry的長(zhǎng)度。這里先說(shuō)明一下Entry類。

private final class Entry {
    final String key;

    /** Lengths of this entry's files. */
    final long[] lengths;
    //用于保存持久數(shù)據(jù),作用是讀取 最后的格式:key.0
    final File[] cleanFiles;
    //用于保存編輯的臨時(shí)數(shù)據(jù),作用是寫(xiě),最后的格式:key.0.tmp
    final File[] dirtyFiles;
}

我的理解是,Entry中有兩個(gè)數(shù)組,cleanFile是用于保存持久性數(shù)據(jù),用于讀取,dirtyFiles是用于進(jìn)行編輯,當(dāng)編輯完成后會(huì)執(zhí)行commit操作,將dirtyFile賦值給cleanFile。length適用于保存Entry中每個(gè)數(shù)組對(duì)應(yīng)的file的數(shù)量。
所以當(dāng)CLEAN jklldsg 2 5如果是以CLEAN開(kāi)頭的話,cleanFiles對(duì)應(yīng)的size就是2,dirtyFiles對(duì)應(yīng)的數(shù)量是5(默認(rèn)都是2個(gè))

else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
      //如果是“DIRTY lskdjfkl”這樣以DIRTY開(kāi)頭,新建一個(gè)Editor
      entry.currentEditor = new Editor(entry);
    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
      //如果是“READ slkjl”這樣以READ開(kāi)頭,不需要做什么事
      // This work was already done by calling lruEntries.get().
    } else {
      throw new IOException("unexpected journal line: " + line);
    }

上面理解了,后面其實(shí)也就對(duì)應(yīng)很好理解了,如果是以DIRTY開(kāi)頭,則新建一個(gè)Editor表示這個(gè)Entry可以編輯。如果是READ開(kāi)頭,則不需要做任何事。
到此結(jié)束了對(duì)readJournalLine()方法的分析,總結(jié)一下這個(gè)方法的作用:逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove。接著返回到readJournal()方法中。

while (true) {
        try {
          //逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
          readJournalLine(source.readUtf8LineStrict());
          lineCount++;
        } catch (EOFException endOfJournal) {
          break;
        }
      }

可以看到這里,利用lineCount記錄讀取的行數(shù)。

//日志中操作的記錄數(shù)=總行數(shù)-lruEntries中實(shí)際add的行數(shù)
      redundantOpCount = lineCount - lruEntries.size();
      //source.exhausted()表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false
      // If we ended on a truncated line, rebuild the journal before appending to it.
      if (!source.exhausted()) {
        //如果有多余的字節(jié),則重新構(gòu)建下journal文件
        rebuildJournal();
      } else {
        //獲取這個(gè)文件的Sink,以便Writer
        journalWriter = newJournalWriter();
      }

讀取完畢后會(huì)計(jì)算日志中操作的記錄數(shù),日志中操作的記錄數(shù)=讀取的總行數(shù)-lruEntries中實(shí)際保存的行數(shù)。
接下來(lái)source.exhausted()是表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false,如果有多余的字節(jié)則需要執(zhí)行rebuildJournal(),沒(méi)有則獲得這個(gè)文件的Sink,用于Write操作。

synchronized void rebuildJournal() throws IOException {
    if (journalWriter != null) {
      journalWriter.close();
    }

    BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
    try {
      //寫(xiě)入校驗(yàn)信息
      writer.writeUtf8(MAGIC).writeByte('\n');
      writer.writeUtf8(VERSION_1).writeByte('\n');
      writer.writeDecimalLong(appVersion).writeByte('\n');
      writer.writeDecimalLong(valueCount).writeByte('\n');
      writer.writeByte('\n');
      //利用剛才逐行讀的內(nèi)容按照格式重新構(gòu)建
      for (Entry entry : lruEntries.values()) {
        if (entry.currentEditor != null) {
          writer.writeUtf8(DIRTY).writeByte(' ');
          writer.writeUtf8(entry.key);
          writer.writeByte('\n');
        } else {
          writer.writeUtf8(CLEAN).writeByte(' ');
          writer.writeUtf8(entry.key);
          entry.writeLengths(writer);
          writer.writeByte('\n');
        }
      }
    } finally {
      writer.close();
    }
    //用新構(gòu)建的journalFileTmp替換當(dāng)前的journalFile文件
    if (fileSystem.exists(journalFile)) {
      fileSystem.rename(journalFile, journalFileBackup);
    }
    fileSystem.rename(journalFileTmp, journalFile);
    fileSystem.delete(journalFileBackup);

    journalWriter = newJournalWriter();
    hasJournalErrors = false;
    mostRecentRebuildFailed = false;
  }

可以看到這里主要是將lruEntries中保存的內(nèi)容逐行寫(xiě)成一個(gè)journalFileTmp,將新構(gòu)建的journalFileTmp替換當(dāng)前包含冗余信息的journalFile文件,達(dá)到重新構(gòu)建的效果。
到這里readJournal()方法分析完了,總結(jié)下這個(gè)方法的作用:主要是讀取journalFile,根據(jù)日志文件中的日志信息,過(guò)濾無(wú)用冗余的信息,有冗余的則重新構(gòu)建,最后保證journalFile日志文件沒(méi)有冗余信息。

執(zhí)行完readJournal()方法,回到initialize()方法中。

    try {
        readJournal();
        processJournal();
        //標(biāo)記初始化完成
        initialized = true;
        return;
      } catch (IOException journalIsCorrupt) {
        Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
            + journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
      }

這里需要看一下processJournal()方法

private void processJournal() throws IOException {
    //刪除journalFileTmp文件
    fileSystem.delete(journalFileTmp);
    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
      Entry entry = i.next();
      if (entry.currentEditor == null) {
        //表明數(shù)據(jù)是CLEAN,循環(huán)記錄SIZE
        for (int t = 0; t < valueCount; t++) {
          size += entry.lengths[t];
        }
      } else {
        //表明數(shù)據(jù)是DIRTY,刪除
        entry.currentEditor = null;
        for (int t = 0; t < valueCount; t++) {
          fileSystem.delete(entry.cleanFiles[t]);
          fileSystem.delete(entry.dirtyFiles[t]);
        }
        //移除Entry
        i.remove();
      }
    }
  }

可以看到,這里刪除了剛才創(chuàng)建的journalFileTmp文件,并且遍歷lruEntries,記錄不可編輯的數(shù)據(jù)長(zhǎng)度size(也就是CLEAN),刪除DIRTY數(shù)據(jù),也就是只保留CLEAN持久性數(shù)據(jù),刪除編輯的數(shù)據(jù)。

    try {
        readJournal();
        processJournal();
        //標(biāo)記初始化完成
        initialized = true;
        return;
      } catch (IOException journalIsCorrupt) {
        Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
            + journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
      }

可以看到到這里,接下來(lái)的就是將initialized標(biāo)記為true,表示初始化完成。到這里其實(shí)initialize已經(jīng)完成了,繼續(xù)看initialize()方法。
后面就比較簡(jiǎn)單了,當(dāng)沒(méi)有journalFile,則會(huì)調(diào)用我們剛才分析過(guò)的方法rebuildJournal()重新創(chuàng)建一個(gè)日志文件,仍然將initialized標(biāo)記為true,說(shuō)明無(wú)論有沒(méi)有journal文件,initialized都會(huì)標(biāo)記為true,只執(zhí)行一遍。
到這里總算將initialize()分析完了,這里總結(jié)一下這個(gè)方法:

1.這個(gè)方法線程安全
2.如果初始化過(guò)了,則什么都不干,只初始化一遍
3.如果有journalFile日志文件,則對(duì)journalFile文件和lruEntries進(jìn)行初始化操作,主要是刪除冗余信息,和DIRTY信息。
4.沒(méi)有則構(gòu)建一個(gè)journalFile文件。

到這initialize()方法總算分析完了,接下來(lái)回到get()方法中,剩下的其實(shí)就容易點(diǎn)了。
接下來(lái)這些都沒(méi)有什么重要的地方,我注釋都寫(xiě)的很清楚,總結(jié)一下get()方法的主要操作:

1.初始化日志文件和lruEntries
2.檢查保證key正確后獲取緩存中保存的Entry。
3.操作計(jì)數(shù)器+1
4.往日志文件中寫(xiě)入這次的READ操作。
5.根據(jù)redundantOpCount判斷是否需要清理日志信息。
6.需要?jiǎng)t開(kāi)啟線程清理。
7.不需要?jiǎng)t返回緩存。

這里看一下兩個(gè)地方,第一個(gè)journalRebuildRequired()用于判斷是否需要清理緩存。

boolean journalRebuildRequired() {
    final int redundantOpCompactThreshold = 2000;
    //清理的條件是當(dāng)前redundantOpCount大于2000,并且redundantOpCount的值大于linkedList里面的size
    return redundantOpCount >= redundantOpCompactThreshold
        && redundantOpCount >= lruEntries.size();
  }

可以看到清理的條件是當(dāng)前redundantOpCount大于2000,并且redundantOpCount的值大于linkedList里面的size。
下面一個(gè)需要看的地方就是清理線程cleanupRunnable。

private final Runnable cleanupRunnable = new Runnable() {
    public void run() {
      synchronized (DiskLruCache.this) {
        //如果沒(méi)有初始化或者已經(jīng)關(guān)閉了,則不需要清理,這里注意|和||的區(qū)別,|會(huì)兩個(gè)條件都檢查
        if (!initialized | closed) {
          return; // Nothing to do
        }

        try {
          //清理
          trimToSize();
        } catch (IOException ignored) {
          mostRecentTrimFailed = true;
        }

        try {
          if (journalRebuildRequired()) {
            //如果還要清理,重新構(gòu)建
            rebuildJournal();
            //計(jì)數(shù)器置0
            redundantOpCount = 0;
          }
        } catch (IOException e) {
          //如果拋異常了,設(shè)置最近的一次構(gòu)建失敗
          mostRecentRebuildFailed = true;
          journalWriter = Okio.buffer(Okio.blackhole());
        }
      }
    }
  };

這里先總結(jié)一下這里的操作流程:

1.如果還沒(méi)有初始化或者緩存關(guān)閉了,則不清理。
2.執(zhí)行清理操作。
3.如果清理完了還是判斷后還需要清理,只能重新構(gòu)建日志文件,并且日志記錄器記0。

這里主要就需要看一下清理操作trimToSize()

void trimToSize() throws IOException {
    //遍歷直到滿足大小
    while (size > maxSize) {
      Entry toEvict = lruEntries.values().iterator().next();
      removeEntry(toEvict);
    }
    mostRecentTrimFailed = false;
  }

可以看到這里就是一個(gè)遍歷,知道滿足maxSize條件,這里的maxSize是可以設(shè)置的。

boolean removeEntry(Entry entry) throws IOException {
    if (entry.currentEditor != null) {
      //結(jié)束editor
      entry.currentEditor.detach(); // Prevent the edit from completing normally.
    }

    for (int i = 0; i < valueCount; i++) {
      //清除用于保存文件的cleanFiles
      fileSystem.delete(entry.cleanFiles[i]);
      size -= entry.lengths[i];
      entry.lengths[i] = 0;
    }
    //計(jì)數(shù)器加1
    redundantOpCount++;
    //增加一條刪除日志
    journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');
    //移除entry
    lruEntries.remove(entry.key);
    //如果需要重新清理一下,邊界情況
    if (journalRebuildRequired()) {
      //清理
      executor.execute(cleanupRunnable);
    }

    return true;
  }

這里的執(zhí)行流程:

1.停止編輯操作
2.清楚用于保存的cleanFiles
3.增加一條清楚日志記錄,計(jì)數(shù)器+1
4.移除對(duì)應(yīng)key的entry
5.由于增加了一條日志,判斷是否需要清理,不然可能會(huì)越清越多...

至此,get()方法終于分析完成了,接著就要返回Cache中的get()方法繼續(xù)看。

@Nullable Response get(Request request) {
    String key = key(request.url());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      snapshot = cache.get(key);
      if (snapshot == null) {
        //沒(méi)拿到,返回null
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

    try {
      //創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }
    //得到緩存構(gòu)建得到的response
    Response response = entry.response(snapshot);

    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }

這里一樣,先總結(jié)一下get()方法的具體流程。

1.通過(guò)執(zhí)行DiskLruCache的get方法拿到snapshot信息。
2.通過(guò)拿到的snapshot信息,取cleanFiles[0]中保存的頭信息,構(gòu)建頭相關(guān)的信息的Entry.
3.通過(guò)snapshot中的cleanFiles[1]構(gòu)建body信息,最終構(gòu)建成緩存中保存的Response。
4.返回緩存中保存的Resposne。

可以看到這里的重點(diǎn)是先構(gòu)建header,再構(gòu)建body,最后組合成Resposne。對(duì)應(yīng)的兩個(gè)流程這里分析一下,

try {
      //創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }

可以看到,這里通過(guò)得到的snapshot.getSource構(gòu)建了Entry(這個(gè)Entry是Cache的內(nèi)部類,不是DiskLruCache的內(nèi)部類)。這里注意一個(gè)地方,這里的ENTRY_METADATA = 0。

public final class Snapshot implements Closeable {
    private final String key;
    private final long sequenceNumber;
    private final Source[] sources;
    private final long[] lengths;

    Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) {
      this.key = key;
      this.sequenceNumber = sequenceNumber;
      this.sources = sources;
      this.lengths = lengths;
    }
    public Source getSource(int index) {
      return sources[index];
    }
}

可以看到這里的getSource其實(shí)就是返回Source數(shù)組中的元素,而Source數(shù)組是在Snapshot的構(gòu)造函數(shù)的時(shí)候賦值,所以對(duì)應(yīng)的可以找構(gòu)造Snapshot的地方。

Snapshot snapshot() {
      if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();

      Source[] sources = new Source[valueCount];
      long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out.
      try {
        for (int i = 0; i < valueCount; i++) {
          //可以看到這里其實(shí)是將cleanFiles傳給了sources
          sources[i] = fileSystem.source(cleanFiles[i]);
        }
        return new Snapshot(key, sequenceNumber, sources, lengths);
      } catch (FileNotFoundException e) {
        // A file must have been deleted manually!
        for (int i = 0; i < valueCount; i++) {
          if (sources[i] != null) {
            Util.closeQuietly(sources[i]);
          } else {
            break;
          }
        }
        // Since the entry is no longer valid, remove it so the metadata is accurate (i.e. the cache
        // size.)
        try {
          removeEntry(this);
        } catch (IOException ignored) {
        }
        return null;
      }
    }

看到這個(gè)方法,其實(shí)應(yīng)該注意到這個(gè)就是我們?cè)谡{(diào)用DiskLruCache中的get()方法時(shí)最后返回Snapshot調(diào)用的方法,具體下方代碼貼出了,這時(shí)候可以看到source數(shù)組其實(shí)是將entry中的cleanfile數(shù)組對(duì)應(yīng)的保存到source數(shù)組中,這也驗(yàn)證了我們前面說(shuō)的clean數(shù)組是用來(lái)保存持久性數(shù)據(jù),也就是真正用來(lái)存東西的地方,而且記得前面提到ENTRY_METADATA = 0,所以對(duì)應(yīng)的取的也是clean數(shù)組中的第一個(gè)文件,也驗(yàn)證了前面說(shuō)的clean數(shù)組分兩部分,第一部分保存頭,第二部分保存body

public synchronized Snapshot get(String key) throws IOException {
    ...
    Snapshot snapshot = entry.snapshot();
    ...
  }

getSource看完了,這時(shí)候來(lái)看一下Cache中Entry這個(gè)內(nèi)部類,注意不是DiskLruCache中的Entry

Entry(Source in) throws IOException {
      try {
        BufferedSource source = Okio.buffer(in);
        url = source.readUtf8LineStrict();
        requestMethod = source.readUtf8LineStrict();
        //得到cleanfiles[0]來(lái)構(gòu)建頭信息
        Headers.Builder varyHeadersBuilder = new Headers.Builder();
        int varyRequestHeaderLineCount = readInt(source);
        for (int i = 0; i < varyRequestHeaderLineCount; i++) {
          varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
        }
        varyHeaders = varyHeadersBuilder.build();

        StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
        protocol = statusLine.protocol;
        code = statusLine.code;
        message = statusLine.message;
        Headers.Builder responseHeadersBuilder = new Headers.Builder();
        int responseHeaderLineCount = readInt(source);
        for (int i = 0; i < responseHeaderLineCount; i++) {
          responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
        }
        String sendRequestMillisString = responseHeadersBuilder.get(SENT_MILLIS);
        String receivedResponseMillisString = responseHeadersBuilder.get(RECEIVED_MILLIS);
        responseHeadersBuilder.removeAll(SENT_MILLIS);
        responseHeadersBuilder.removeAll(RECEIVED_MILLIS);
        sentRequestMillis = sendRequestMillisString != null
            ? Long.parseLong(sendRequestMillisString)
            : 0L;
        receivedResponseMillis = receivedResponseMillisString != null
            ? Long.parseLong(receivedResponseMillisString)
            : 0L;
        //構(gòu)建了header
        responseHeaders = responseHeadersBuilder.build();

        if (isHttps()) {
          String blank = source.readUtf8LineStrict();
          if (blank.length() > 0) {
            throw new IOException("expected \"\" but was \"" + blank + "\"");
          }
          String cipherSuiteString = source.readUtf8LineStrict();
          CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
          List<Certificate> peerCertificates = readCertificateList(source);
          List<Certificate> localCertificates = readCertificateList(source);
          TlsVersion tlsVersion = !source.exhausted()
              ? TlsVersion.forJavaName(source.readUtf8LineStrict())
              : TlsVersion.SSL_3_0;
          handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates);
        } else {
          handshake = null;
        }
      } finally {
        in.close();
      }
    }

這里通過(guò)Entry的構(gòu)造方法更能說(shuō)明clean數(shù)組中的第一項(xiàng)是用來(lái)保存header信息的,從代碼中可以看到利用Header.builder對(duì)傳入進(jìn)來(lái)的Source(也就是clean[0])進(jìn)行構(gòu)建,最后利用build()方法構(gòu)建了header信息。

頭構(gòu)建完了,現(xiàn)在就需要找構(gòu)建body的地方,因?yàn)槭O碌拇a只剩下

//得到緩存構(gòu)建得到的response
    Response response = entry.response(snapshot);

//Entry內(nèi)部類
public Response response(DiskLruCache.Snapshot snapshot) {
      String contentType = responseHeaders.get("Content-Type");
      String contentLength = responseHeaders.get("Content-Length");
      Request cacheRequest = new Request.Builder()
          .url(url)
          .method(requestMethod, null)
          .headers(varyHeaders)
          .build();
      return new Response.Builder()
          .request(cacheRequest)
          .protocol(protocol)
          .code(code)
          .message(message)
          .headers(responseHeaders)
          .body(new CacheResponseBody(snapshot, contentType, contentLength))
          .handshake(handshake)
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(receivedResponseMillis)
          .build();
    }

大體上一看,這個(gè)方法的作用基本上就是利用Resposne.builder構(gòu)建緩存中的Resposne了,但是沒(méi)有找到明顯的寫(xiě)入Body的地方,唯一由body的就是
.body(new CacheResponseBody(snapshot, contentType, contentLength)),所以只能進(jìn)入CacheResponseBody的構(gòu)造函數(shù)中。

CacheResponseBody(final DiskLruCache.Snapshot snapshot,
        String contentType, String contentLength) {
      this.snapshot = snapshot;
      this.contentType = contentType;
      this.contentLength = contentLength;
      //這里ENTRY_BODY=1,同樣拿的是CleanFiles數(shù)組,構(gòu)建Responsebody
      Source source = snapshot.getSource(ENTRY_BODY);
      bodySource = Okio.buffer(new ForwardingSource(source) {
        @Override public void close() throws IOException {
          snapshot.close();
          super.close();
        }
      });
    }

可以看到終于發(fā)現(xiàn)了和剛才Header一樣的代碼,這里ENTRY_BODY=1,對(duì)應(yīng)的還是取source數(shù)組中的下標(biāo)為1的地方,構(gòu)建body。到現(xiàn)在可以看出來(lái),clean數(shù)組的0對(duì)應(yīng)保存的Header信息,1對(duì)應(yīng)保存的BODY信息。

這里分析完構(gòu)建header和body得到對(duì)應(yīng)的緩存的Resposne后,對(duì)應(yīng)非常長(zhǎng)長(zhǎng)長(zhǎng)的從緩存中拿緩存的Resposne流程終于結(jié)束。
其實(shí)到這里,緩存的主要思想其實(shí)已經(jīng)理解大概了后面的的其實(shí)就比較好理解了。這里get()方法結(jié)束了,也終于要回到CacheInterceptor的主要方法中了。這里再放一遍代碼,因?yàn)榉先ヌL(zhǎng)了。。。

@Override public Response intercept(Chain chain) throws IOException {
    //默認(rèn)cache為null,可以配置cache,不為空嘗試獲取緩存中的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存
    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.
    //如果緩存策略中禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=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 we don't need the network, we're done.
    //不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //直接走后續(xù)過(guò)濾器
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //當(dāng)緩存響應(yīng)和網(wǎng)絡(luò)響應(yīng)同時(shí)存在的時(shí)候,選擇用哪個(gè)
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶
        // 只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。
        //則使用緩存的響應(yīng)
        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());
      }
    }
    //使用網(wǎng)絡(luò)響應(yīng)
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //所以默認(rèn)創(chuàng)建的OkHttpClient是沒(méi)有緩存的
    if (cache != null) {
      //將響應(yīng)緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        //緩存Resposne的Header信息
        CacheRequest cacheRequest = cache.put(response);
        //緩存body
        return cacheWritingResponse(cacheRequest, response);
      }
      //只能緩存GET....不然移除request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

這里就可以分析CacheInterceptor的主要流程了。

1.通過(guò)Request嘗試到Cache中拿緩存(里面非常多流程),當(dāng)然前提是OkHttpClient中配置了緩存,默認(rèn)是不支持的。
2.根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存。
3.如果緩存策略中設(shè)置禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=504
4.緩存策略中設(shè)置不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
5.接著走后續(xù)過(guò)濾器的流程,chain.proceed(networkRequest)
6.當(dāng)緩存存在的時(shí)候,如果網(wǎng)絡(luò)返回的Resposne為304,則使用緩存的Resposne。
7.構(gòu)建網(wǎng)絡(luò)請(qǐng)求的Resposne
8.當(dāng)在OKHttpClient中配置了緩存,則將這個(gè)Resposne緩存起來(lái)。
9.緩存起來(lái)的步驟也是先緩存header,再緩存body。
10.返回Resposne。

這里要注意的是2,9兩個(gè)點(diǎn)。其中2對(duì)應(yīng)的是CacheStrategy這個(gè)類,里面主要涉及Http協(xié)議中緩存的相關(guān)設(shè)置,具體的我也沒(méi)太搞明白,準(zhǔn)備入手一本Http的書(shū)好好研究研究。但是這里不影響理解主要流程。
下面就是對(duì)9,也就是存緩存這個(gè)步驟的分析了,其實(shí)前面的取緩存的分析結(jié)束后,這里對(duì)存緩存不難猜測(cè)其實(shí)是想對(duì)應(yīng)的,也就比較好理解了,對(duì)應(yīng)的大體應(yīng)該是header存入clean[0],body存入clean[1]。這里詳細(xì)看一下。

if (cache != null) {
      //將響應(yīng)緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        //緩存Resposne的Header信息
        CacheRequest cacheRequest = cache.put(response);
        //緩存body
        return cacheWritingResponse(cacheRequest, response);
      }
      //只能緩存GET....不然移除request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

當(dāng)可以緩存的時(shí)候,這里用了cache.put(response)方法。

@Nullable CacheRequest put(Response response) {
    String requestMethod = response.request().method();

    if (HttpMethod.invalidatesCache(response.request().method())) {
      //OKhttp只能緩存GET請(qǐng)求!。。。
      try {
        remove(response.request());
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
      return null;
    }
    if (!requestMethod.equals("GET")) {
      //OKhttp只能緩存GET請(qǐng)求!。。。
      // 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;
      }
      //緩存了Header信息
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

可以看到這里先有幾種可能返回null,也就是對(duì)應(yīng)的不能緩存,這里會(huì)驚訝的發(fā)現(xiàn)!OkHttpClient源碼中支持GET形式的緩存

 if (!requestMethod.equals("GET")) {
      //OKhttp只能緩存GET請(qǐng)求!。。。
      // 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;
    }

public static boolean invalidatesCache(String method) {
    return method.equals("POST")
        || method.equals("PATCH")
        || method.equals("PUT")
        || method.equals("DELETE")
        || method.equals("MOVE");     // WebDAV
  }

通過(guò)注釋其實(shí)也可以看到,這里okHttp的開(kāi)發(fā)者認(rèn)為,從效率角度考慮,最好不要支持POST請(qǐng)求的緩存,暫時(shí)只要支持GET形式的緩存。(如果需要支持,對(duì)應(yīng)的其實(shí)也就是修改源碼,將這里的判斷給刪除,其實(shí)還有一處判斷,具體方法Google,Baidu)

//緩存了Header信息
      entry.writeTo(editor);
//Entry的writeTo方法===============================
public void writeTo(DiskLruCache.Editor editor) throws IOException {
      //往dirty中寫(xiě)入header信息,ENTRY_METADATA=0,所以是dirtyFiles[0]
      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));

      sink.writeUtf8(url)
          .writeByte('\n');
      sink.writeUtf8(requestMethod)
          .writeByte('\n');
      sink.writeDecimalLong(varyHeaders.size())
          .writeByte('\n');
      for (int i = 0, size = varyHeaders.size(); i < size; i++) {
        sink.writeUtf8(varyHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(varyHeaders.value(i))
            .writeByte('\n');
      }

      sink.writeUtf8(new StatusLine(protocol, code, message).toString())
          .writeByte('\n');
      sink.writeDecimalLong(responseHeaders.size() + 2)
          .writeByte('\n');
      for (int i = 0, size = responseHeaders.size(); i < size; i++) {
        sink.writeUtf8(responseHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(responseHeaders.value(i))
            .writeByte('\n');
      }
      sink.writeUtf8(SENT_MILLIS)
          .writeUtf8(": ")
          .writeDecimalLong(sentRequestMillis)
          .writeByte('\n');
      sink.writeUtf8(RECEIVED_MILLIS)
          .writeUtf8(": ")
          .writeDecimalLong(receivedResponseMillis)
          .writeByte('\n');

      if (isHttps()) {
        sink.writeByte('\n');
        sink.writeUtf8(handshake.cipherSuite().javaName())
            .writeByte('\n');
        writeCertList(sink, handshake.peerCertificates());
        writeCertList(sink, handshake.localCertificates());
        sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');
      }
      sink.close();
    }

對(duì)應(yīng)寫(xiě)緩存的地方可以看到調(diào)用了Entry的writeTo方法,這里別看那么長(zhǎng),其實(shí)主要看到一行代碼就了解了這個(gè)方法的功能了。

//往dirty中寫(xiě)入header信息,ENTRY_METADATA=0,所以是dirtyFiles[0]
      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));

還是原來(lái)的配方,還是原來(lái)的味道,又看到了剛才的參數(shù)ENTRY_METADATA=0,可以看到這里對(duì)應(yīng)的其實(shí)就是往dirtyFiles[0]中寫(xiě)入header信息,這里其實(shí)可以根據(jù)前面的分析對(duì)應(yīng),dirty是用于保存編輯更新等不是持久的數(shù)據(jù),而對(duì)應(yīng)的0對(duì)應(yīng)的header,1對(duì)應(yīng)的body。

    //緩存了Header信息
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);

寫(xiě)完header后,繼續(xù)找寫(xiě)body的地方,這里返回了一個(gè)CacheRequestImpl對(duì)象,一定不要忽略,不然就找不到寫(xiě)body的地方了。

CacheRequestImpl(final DiskLruCache.Editor editor) {
      this.editor = editor;
        //ENTRY_BODY = 1
      this.cacheOut = editor.newSink(ENTRY_BODY);
      this.body = new ForwardingSink(cacheOut) {
        @Override public void close() throws IOException {
          synchronized (Cache.this) {
            if (done) {
              return;
            }
            done = true;
            writeSuccessCount++;
          }
          super.close();
          editor.commit();
        }
      };
    }

看到ENTRY_BODY就放心, 這里對(duì)應(yīng)的ENTRY_BODY=1對(duì)應(yīng)的就是數(shù)組的第二個(gè)位置。那到了數(shù)據(jù)源,接著就要找寫(xiě)入的地方,這里還要注意一個(gè)地方editor.commit();

//CacheIntercetor中==========================
        //緩存Resposne的Header信息
        CacheRequest cacheRequest = cache.put(response);
        //緩存body
        return cacheWritingResponse(cacheRequest, response);

可以看到返回了一個(gè)CacheRequestImpl對(duì)象后,最終執(zhí)行了一個(gè)cacheWritingResponse方法。

private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
      throws IOException {
    // Some apps return a null body; for compatibility we treat that like a null cache request.
    if (cacheRequest == null) return response;
    Sink cacheBodyUnbuffered = cacheRequest.body();
    if (cacheBodyUnbuffered == null) return response;
    //獲得body
    final BufferedSource source = response.body().source();
    final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);

    Source cacheWritingSource = new Source() {
      boolean cacheRequestClosed;

      @Override public long read(Buffer sink, long byteCount) throws IOException {
        long bytesRead;
        try {
          bytesRead = source.read(sink, byteCount);
        } catch (IOException e) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true;
            cacheRequest.abort(); // Failed to write a complete cache response.
          }
          throw e;
        }

        if (bytesRead == -1) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true;
            cacheBody.close(); // The cache response is complete!
          }
          return -1;
        }
        //讀的時(shí)候會(huì)將body寫(xiě)入
        sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
        cacheBody.emitCompleteSegments();
        return bytesRead;
      }

      @Override public Timeout timeout() {
        return source.timeout();
      }

      @Override public void close() throws IOException {
        if (!cacheRequestClosed
            && !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
          cacheRequestClosed = true;
          //關(guān)閉的時(shí)候會(huì)執(zhí)行commit操作,最終合并header和body,完成緩存
          cacheRequest.abort();
        }
        source.close();
      }
    };

    String contentType = response.header("Content-Type");
    long contentLength = response.body().contentLength();
    return response.newBuilder()
        .body(new RealResponseBody(contentType, contentLength, Okio.buffer(cacheWritingSource)))
        .build();
  }

這里分析一下主要主要流程。
1.獲得Resposne中的body

//獲得body
    final BufferedSource source = response.body().source();

2.將Resposne中獲得的body寫(xiě)入緩存中,也就是剛在拿到的dirtyfile[1]

//讀的時(shí)候會(huì)將body寫(xiě)入
        sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);

可以看到這里bytesRead就是讀的Body,最后利用sink.copyTo,寫(xiě)入cacheBody.buffer()中,也就是剛在拿到的dirtyfile[1]。
3.在close中會(huì)執(zhí)行abort操作,對(duì)應(yīng)的里面會(huì)執(zhí)行commit的操作,會(huì)將dirtyfile寫(xiě)入cleanfile中,完成持久化保存。

@Override public void close() throws IOException {
        if (!cacheRequestClosed
            && !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
          cacheRequestClosed = true;
          //關(guān)閉的時(shí)候會(huì)執(zhí)行commit操作,最終合并header和body,完成緩存
          cacheRequest.abort();
        }
        source.close();
      }

可以看到這里再關(guān)閉時(shí)會(huì)執(zhí)行abort方法。

@Override public void abort() {
      synchronized (Cache.this) {
        if (done) {
          return;
        }
        done = true;
        writeAbortCount++;
      }
      Util.closeQuietly(cacheOut);
      try {
        editor.abort();
      } catch (IOException ignored) {
      }
    }

對(duì)應(yīng)執(zhí)行了editor的abort()方法。

public void abort() throws IOException {
      synchronized (DiskLruCache.this) {
        if (done) {
          throw new IllegalStateException();
        }
        if (entry.currentEditor == this) {
          completeEdit(this, false);
        }
        done = true;
      }
    }

可以看到這里執(zhí)行了completeEdite方法。

synchronized void completeEdit(Editor editor, boolean success) throws IOException {
    ...

    for (int i = 0; i < valueCount; i++) {
      File dirty = entry.dirtyFiles[i];
      if (success) {
        if (fileSystem.exists(dirty)) {
          File clean = entry.cleanFiles[i];
          fileSystem.rename(dirty, clean);
          long oldLength = entry.lengths[i];
          long newLength = fileSystem.size(clean);
          entry.lengths[i] = newLength;
          size = size - oldLength + newLength;
        }
      } else {
        fileSystem.delete(dirty);
      }
    }

    ...
  }

這里刪點(diǎn)代碼吧,貼的代碼太多了,這里只放了最重要的一條,可以看到將dirtyFiles數(shù)組保存賦值到cleanFiles數(shù)組中,完成了最終的持久化保存。

數(shù)一下這里的重點(diǎn)吧

1.緩存中是有日志文件用于保存操作記錄
2.緩存中的Entry有用CleanFiles和DirtyFiles,其中Clean是用于保存持久性數(shù)據(jù)的,也就是真正保存數(shù)據(jù)的地方,Dirty是用于保存編輯過(guò)程中的數(shù)據(jù)的。
3.CleanFiles[]大小為2,第一個(gè)保存Header,第二個(gè)保存Body,最終保存緩存。

到此。。。結(jié)束了。。。沒(méi)有結(jié)束語(yǔ)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,290評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,776評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,510評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,866評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,036評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,331評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,536評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,754評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,154評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,469評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,273評(píng)論 3 399
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,505評(píng)論 2 379

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