二财饥、深入理解OKHttp:緩存處理-CacheIntercepter

一闸氮、前言

【1.1】OkHttp系列其他篇章:

  1. 同步請求的實現(xiàn)流程
  2. 異步請求的實現(xiàn)流程
  3. 重要攔截器:CacheInterceptor 的解析货抄。
  4. 重要攔截器:ConnectInterceptor 的解析述召。
  5. 重要攔截器:CallServerInterceptor 的解析

【1.2】陳述

OkHttp中提供了網(wǎng)絡(luò)請求的緩存機制蟹地,當(dāng)我們在上篇中追溯請求的流程時积暖,知道每個Request都需要進過CacheInterceptor.process()的處理,但是整個緩存處理肯定是不止緩存攔截器的這一個方法的邏輯怪与,它還涉及到:

  • Http緩存機制夺刑,對應(yīng)CacheStrategy 類
  • LRUCache/DiskLRUCahche:對緩存進行高效增刪改查。
  • okio:進行IO處理琼梆。
    在開始 CacheInterceptor 的源碼解析前性誉,我們需要先了解 Http 緩存機制才能明白CacheStrategy類的存在意義。
    而本篇只介紹Http的緩存機制和OkHttp中的處理茎杂。對于LruCache和okio以后會單拎開篇错览。

二、Http 緩存機制

【2.1】 為什么需要緩存

讓我們設(shè)想一個場景:一個用戶一天內(nèi)都打開多次某個頁面煌往,而這個頁面的內(nèi)容相對固定倾哺,并不是每次都更改。那么我們有必要每次都從服務(wù)器中下載資源嗎刽脖?答案是不用的羞海。此時緩存就排上用場了。上面場景只是緩存的其中的一個好處曲管,合理的使用緩存還能有如下好處:

  1. 優(yōu)化用戶體驗却邓,避免空白頁面的展示,提供默認數(shù)據(jù)展示院水。
  2. 避免不必要的訪問服務(wù)器腊徙,減輕寬帶負擔(dān)。

【2.2】緩存分類之強制緩存

【2.2.1】簡介: 一般地檬某,當(dāng)客戶端向服務(wù)端請求時撬腾,按照是否重新向服務(wù)器發(fā)起請求來劃分,那么有強制緩存和協(xié)商緩存兩種緩存類型恢恼。他們的優(yōu)劣勢各不相同民傻。而強制緩存:當(dāng)緩存處在且未失效的條件下,直接使用緩存作為返回而且http返回的狀態(tài)碼為200场斑,否則請求服務(wù)器漓踢。簡要的請求流程如下:

image

【2.2.2】優(yōu)缺點:

  • 優(yōu)點:加載速度快,性能好和簸。
  • 缺點:在緩存沒失效前彭雾,都不會請求服務(wù)器。如果服務(wù)器此時更新了資源锁保,客戶端得不到最新的響應(yīng)薯酝。

【2.2.3】相關(guān)請求頭

  1. Pragma: no-cache。代表禁用緩存爽柒,目前是在HTTP1.1中已被廢棄吴菠。
  2. Expires: GMT時間。代表改緩存的有效時間浩村∽隹可兼容HTTP/1.0和HTTP1.1。但是由于這個時間是服務(wù)器給的心墅,會出現(xiàn)服務(wù)器和客戶端時間不一致的問題酿矢。

【2.3】緩存分類之協(xié)商緩存

【2.3.1】簡介: 協(xié)商緩存:當(dāng)緩存存在時榨乎,帶上用緩存標(biāo)識先向服務(wù)器請求,服務(wù)器對比資源標(biāo)識瘫筐,如果不需要下發(fā)新資源蜜暑,那么會直接返回304狀態(tài)碼,告訴客戶端可用緩存策肝;否則將新的資源和新的資源標(biāo)識一起返回肛捍,此時的狀態(tài)碼為200。簡要的請求流程如下:

image

【2.3.2】優(yōu)缺點:

  • 優(yōu)點:減少服務(wù)器數(shù)據(jù)傳輸壓力之众。能夠及時更新數(shù)據(jù)拙毫。
  • 缺點:每次都需要想服務(wù)器請求一次判斷資源是否最新。
【2.3.3】相關(guān)請求頭
  1. Last-Modified/If-Modified-Since:xxx 在服務(wù)器首次請求回來的數(shù)據(jù)的請求頭棺禾,附帶了Last-Modified:xxx缀蹄。這個時間值會在下次請求時,被附帶在If-Modified-Since的請求值里帘睦。服務(wù)器對比兩個值袍患,如果一至就返回304狀態(tài)碼,告知客戶端繼續(xù)使用緩存竣付。如果不一致诡延,服務(wù)器返回新的Expires和Last-Modifed。缺點:Last-Modified只能精確到秒級古胆,如果一個文件在1s內(nèi)被更改肆良,那么他們的值Last-Modified值是一樣的,這會導(dǎo)致更新不到新資源問題逸绎。
  2. ETag/If-Not-Match: 鑒于上面Last-Modified的缺點惹恃,增加了一個新的字段。服務(wù)器通過某種算法棺牧,對資源進行計算巫糙,比如MD5,然后賦值在Etag返回到客戶端颊乘〔窝停客戶端下次請求將值賦值到If-Not-Match/或者If-Match上,服務(wù)器進行比較乏悄,如果一致則直接返回304狀態(tài)碼浙值,通知客戶端可以使用緩存。如果需要更新檩小,那么狀態(tài)碼為200开呐,并返回整個新的資源。并且他們的優(yōu)先級高于Last-Modified/If-Modified-Since

有了上面的一些Http緩存基本知識,接下來就可以跟隨Okhttp的代碼筐付,來看看它是怎么處理緩存的了卵惦。

【2.4】緩存控制:CacheControl

【2.4.1】當(dāng)它在請求頭時
可選字段 意義
no-cache 不使用緩存,直接向服務(wù)器發(fā)起請求瓦戚。
no-store 不儲存緩存
max-age = xxx 告訴服務(wù)器鸵荠,請求一個存在時間不超過xxx秒的資源
max-stale = xxx 告訴服務(wù)器,可接受一個超過緩存時間為xxx秒的資源伤极,如果xxx秒沒有定義,則時間為任意時間
min-fresh = xxx 告訴服務(wù)器姨伤,希望接收一個在小于xxx秒內(nèi)被更新過的資源
【2.4.1】當(dāng)它在響應(yīng)頭時
header 1 header 2
可選字段 意義
no-cache 不直接使用緩存哨坪,需要向服務(wù)器發(fā)起請求校驗緩存。
no-store 服務(wù)器告訴客戶端不緩存響應(yīng)
no-transform 告知客戶端在緩存響應(yīng)時乍楚,不得對數(shù)據(jù)做改變
only-if-cached 告知客戶端不進行網(wǎng)絡(luò)請求当编,只使用緩存,如果緩存不命中徒溪,那么返回503狀態(tài)碼
Max-age=xxx 告知客戶端忿偷,該響應(yīng)在xxx秒內(nèi)是合法的,不需要向服務(wù)器發(fā)起請求臊泌。
public 表示任何情況下都緩存該響應(yīng)
private=“xxx” 表示xxx或者不指明是為全部鲤桥,值對部分用戶做緩存。

三渠概、OkHttp的緩存處理

【3.1】CacheInterceptor:緩存開始的地方

CacheInterceptor.java
@Override public Response intercept(Chain chain) throws IOException {
    //1.從LruCache中茶凳,根據(jù)Request,取出緩存的Response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    //2.緩存選擇策略類播揪,根據(jù)request和response來決定需不需要使用緩存贮喧。
    //new CacheStrategy.Factory() 詳見:【3.2】
    //CacheStrategy.Factory.get() 詳見:【3.3】
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //緩存策略邏輯執(zhí)行后的產(chǎn)物,主要根據(jù)這兩個對象判斷是否使用緩存等猪狈。
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    //3.緩存跟蹤記錄緩存策略選擇后的結(jié)果箱沦。
    if (cache != null) {
      cache.trackResponse(strategy);
    }

    //4.緩存數(shù)據(jù)庫里的響應(yīng)緩存不為空,但是結(jié)果緩存策略選擇后的結(jié)果為空
    //證明這個響應(yīng)緩存已經(jīng)過時不適用了雇庙,將起關(guān)閉谓形,防止內(nèi)存泄露。后續(xù)的操作中也會將不用的Response進行關(guān)閉状共,就不一一贅述套耕。
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); 
    }

    // 5.如果禁用了網(wǎng)絡(luò),此時request為空峡继,而緩存的響應(yīng)也為空冯袍,直接504的響應(yīng)
    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();
    }

    // 6.如果不需要網(wǎng)絡(luò),且緩存的響應(yīng)有效,返回這個緩存的響應(yīng)康愤。
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    //7.到這一步儡循,說明需要執(zhí)行真正的網(wǎng)絡(luò)請求,得到網(wǎng)絡(luò)的響應(yīng)了征冷,所以執(zhí)行下一個攔截器邏輯择膝。
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      ...
    }

    // 8.如果緩存的Response不為空,此時要綜合網(wǎng)絡(luò)返回回來的Respnse進行選擇检激。
    if (cacheResponse != null) {
      //當(dāng)服務(wù)器告訴客戶端數(shù)據(jù)沒有改變時肴捉,客戶端直接使用緩存的Response。
      //但是會更新最新的一些請求頭等數(shù)據(jù)到緩存的Response叔收。
      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();
            ...
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    //9.到這一步齿穗,確定使用網(wǎng)絡(luò)的Response。
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //10.緩存新的Response
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }
    //返回最新的Respnse饺律。
    return response;
  }

總結(jié):總的來說窃页,CacheIntercepter根據(jù)緩存策略選擇出來的Request和Response來決定是否用緩存,和緩存的更新复濒。詳細的脖卖,它做了如下事情:

  1. 嘗試獲取緩存的Response。
  2. 將Request和Resonse投放進CacheStrategy巧颈,得到要進行網(wǎng)絡(luò)請求的netRequest和緩存的響應(yīng)cacheResponse畦木。
  3. 緩存檢測上述得到的2個實體。
  4. 如果(禁用網(wǎng)絡(luò)&&沒有緩存)砸泛,直接返回504的響應(yīng)馋劈。
  5. 如果(禁用網(wǎng)絡(luò)&&有緩存),使用緩存的響應(yīng)晾嘶。
  6. 緩存無效妓雾,進行網(wǎng)絡(luò)請求,得到最新的netReponse垒迂。
  7. 如果本地存在緩存械姻,檢查netResponse的響應(yīng)是否為不需要更新。如果是將netResponse的一些響應(yīng)頭等數(shù)據(jù)更新到cacheResonse并返回緩存的響應(yīng)机断。相應(yīng)的做LRUCache的命中記錄楷拳。
  8. 如果7沒有返回,代表用最新的netResonse作為結(jié)果吏奸。那么此時更新最新的響應(yīng)到緩存中欢揖,并返回。

【3.2】new CacheStrategy.Factory()

CacheStrategy.Factory.java
 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);
          }
        }
      }

總結(jié): 記錄起request和緩存的Resonse奋蔚,然后解析出cacheReonse響應(yīng)頭里面有關(guān)緩存的鍵值對并保存起來她混。之前在一中講到的如ETag烈钞、Last-Modified等在這里就出現(xiàn)了。

【3.3】Factory.get()


CacheStrategy.Factory.java
public CacheStrategy get() {
  【詳見3.4】獲取候選的請求和緩存響應(yīng)坤按。
  CacheStrategy candidate = getCandidate();

  /**這里如果networkRequest != null 代表緩存不可用毯欣,需要進行網(wǎng)絡(luò)請求。
  但是需要檢查cacheControl是否指明了只是用緩存臭脓,不用網(wǎng)絡(luò)酗钞。如果是的話,此時綜合2個判斷来累,可以得出請求失敗砚作。
  而netWorkRequest = null && 擦車 Response = null 的處理結(jié)果我們可以在【3.1】的5中可以看到,返回的是504.
  */
  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    return new CacheStrategy(null, null);
  }

  return candidate;
}

總結(jié): 這里做獲取候選的請求和cacheResponse嘹锁。并且判斷如果需要進行網(wǎng)絡(luò)請求偎巢,并且在只用緩存的情況下,緩存不可用兼耀,那么直接返回2個空的候選結(jié)果。這里的CacheControl是對于Http里cacheControl請求頭字段的描述求冷。

【3.4】獲取候選響應(yīng):CacheStrategy.Factory.getCandidate()


CacheStrategy.Factory.java
 private CacheStrategy getCandidate() {
  //1. 該request沒有緩存的響應(yīng)瘤运,那么返回沒有緩存的策略
  if (cacheResponse == null) {
    return new CacheStrategy(request, null);
  }

  //2. 如果請求是Https,并且緩存的握手已經(jīng)丟失匠题,那么也返回一個沒有緩存的策略拯坟。
  if (request.isHttps() && cacheResponse.handshake() == null) {
    return new CacheStrategy(request, null);
  }

  //3. 這里進行響應(yīng)是否可緩存的判斷。
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }

  //4.如果request要求不用緩存;
  //或者請求里面帶有上次請求時服務(wù)器返回的"If-Modified-Since" || "If-None-Match"
  //那么他們需要返回一個沒有緩存的策略韭山。
  CacheControl requestCaching = request.cacheControl();
  if (requestCaching.noCache() || hasConditions(request)) {
    return new CacheStrategy(request, null);
  }
    

  
  CacheControl responseCaching = cacheResponse.cacheControl();
  //獲得響應(yīng)的年齡
  long ageMillis = cacheResponseAge();
  //獲得最近刷新時間
  long freshMillis = computeFreshnessLifetime();

  //最近刷新時間還需要結(jié)合請求頭里的最大年齡時間郁季,取中間最小值。
  if (requestCaching.maxAgeSeconds() != -1) {
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

  //獲得請求里的最小刷新時間钱磅。
  long minFreshMillis = 0;
  if (requestCaching.minFreshSeconds() != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
  }

  //獲得服務(wù)器的最大驗證秒數(shù)梦裂,如果有的話
  long maxStaleMillis = 0;
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }


  //5.在緩存響應(yīng)可被緩存的條件下
  //如果滿足(cacheResponse的年齡+最小刷新時間)<(最近刷新時間+最大驗證秒數(shù))那么可以不用進行網(wǎng)絡(luò)請求而直接用cacheResonse。
  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\"");
    }
    //返回一個不用網(wǎng)絡(luò)請求盖淡,直接用cacheResponse的策略年柠,這時進行強制緩存。
    return new CacheStrategy(null, builder.build());
  }

  /**
  * 6.到這里褪迟,進行協(xié)商緩存的判斷冗恨。可以看到它們的優(yōu)先級是:etag>lastModified>serverDate味赃。
  */
  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 {
    //如果如果不存在以上請求頭掀抹,那么它將進行一個常規(guī)的網(wǎng)絡(luò)請求。
    return new CacheStrategy(request, null); 
  }
  
  //如果存在上述說的請求頭心俗,那么將他加進入請求里面傲武。
  Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
  Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

  Request conditionalRequest = request.newBuilder()
      .headers(conditionalRequestHeaders.build())
      .build();
  // 7.返回一個需要進行網(wǎng)絡(luò)請求并且存在緩存響應(yīng)的策略,此時他們將進行協(xié)商緩存
  return new CacheStrategy(conditionalRequest, cacheResponse);
}

總結(jié): 緩存策略中,針對請求頭和緩存響應(yīng)頭的一些值谱轨,進行緩存策略的選擇戒幔,并返回⊥镣總的來說他們做了如下判斷:

  1. 該請求對應(yīng)的響應(yīng)沒有緩存:返回?zé)o緩存響應(yīng)策略诗茎。
  2. 該請求的cacheRsponse已經(jīng)丟失握手:返回?zé)o緩存響應(yīng)策略。
  3. cacheResponse是不能緩存的類型献汗,比如響應(yīng)的響應(yīng)碼不符合規(guī)則敢订,或則頭部存在"noStore"等情況,這部分可參照isCacheable()方法罢吃,這里不贅述楚午。:返回?zé)o緩存響應(yīng)策略。
  4. 滿足:(cacheResponse的年齡+最小刷新時間)<(最近刷新時間+最大驗證秒數(shù)):返回直接用緩存策略尿招。
  5. 該請求沒有協(xié)商緩存的相關(guān)頭部:返回常規(guī)網(wǎng)絡(luò)請求策略矾柜。
  6. 存在協(xié)商緩存相關(guān)頭部:返回request+cacheResponse策略。

netWorkRequest和cacheResponse的不同組合情況得出的結(jié)果如下表:

netWorkRequest cacheResponse 表現(xiàn)結(jié)果
不用網(wǎng)絡(luò)且緩存不可用就谜,直接返回503
不為空 不走網(wǎng)絡(luò)怪蔑,直接返回緩存
不為空 緩存不可用,常規(guī)請求丧荐。
不為空 不為空 需要協(xié)商緩存缆瓣,進行網(wǎng)絡(luò)訪問,進一步確認虹统。

本篇小節(jié): 在本篇中弓坞,我們按照是否需要向服務(wù)器請求,介紹了Http緩存的2種緩存方式:強制緩存和協(xié)商緩存车荔。然后從CacheInterceptor.java入手渡冻,輸入理解了Okhttp在緩存邏輯中做的一些事情:CacheStrategy獲取緩存,CacheInterceptor根據(jù)緩存策略獲得的候選Request和Response作出響應(yīng)的邏輯處理忧便,或是返回緩存響應(yīng)菩帝,或是返回錯誤,或是進行協(xié)商緩存等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茬腿,一起剝皮案震驚了整個濱河市呼奢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌切平,老刑警劉巖握础,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悴品,居然都是意外死亡禀综,警方通過查閱死者的電腦和手機简烘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來定枷,“玉大人孤澎,你說我怎么就攤上這事∏分希” “怎么了覆旭?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岖妄。 經(jīng)常有香客問我型将,道長,這世上最難降的妖魔是什么荐虐? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任七兜,我火速辦了婚禮,結(jié)果婚禮上福扬,老公的妹妹穿的比我還像新娘腕铸。我一直安慰自己,他們只是感情好铛碑,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布狠裹。 她就那樣靜靜地躺著,像睡著了一般亚茬。 火紅的嫁衣襯著肌膚如雪刻像。 梳的紋絲不亂的頭發(fā)上姐军,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音抱虐,去河邊找鬼颈将。 笑死梢夯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晴圾。 我是一名探鬼主播颂砸,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼死姚!你這毒婦竟也來了人乓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤都毒,失蹤者是張志新(化名)和其女友劉穎色罚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體账劲,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡戳护,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年金抡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腌且。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡梗肝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铺董,到底是詐尸還是另有隱情巫击,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布柄粹,位于F島的核電站喘鸟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏驻右。R本人自食惡果不足惜什黑,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望堪夭。 院中可真熱鬧愕把,春花似錦、人聲如沸森爽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爬迟。三九已至橘蜜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間付呕,已是汗流浹背计福。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留徽职,地道東北人象颖。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像姆钉,于是被迫代替她去往敵國和親说订。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354