OkHttp源碼分析-同步篇

OkHttp源碼分析-同步篇

很早就想拿okhttp開刀了,這次就記一次使用OKhttp的網(wǎng)絡請求嗤放。
首先需要說明的是我這里不提前講述類結構思喊,1如果提前知道了都有什么看起來會很無趣壁酬,2開篇就是一大堆說明次酌,也不方便觀看。因此總結經(jīng)驗舆乔,減少贅述爭取能圖文并茂的為大家展示出一種清晰的流程和調用關系岳服。
OK砚作,Getting back to the point.
同步請求從這里開始:

 Response execute = okHttpClient.newCall(build).execute();
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

進入看OKhttpClient類的newCall方法趟庄,入?yún)⑹荝equest 對象嗜逻,于此同時看這個注釋专执,知道了創(chuàng)建這個Call 并準備好request暑刃,以便將來被調用令宿。


 /**
   * Prepares the {@code request} to be executed at some point in the future.
   * 準備再將來某一時刻被調用铸史。
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }


由此授滓,我們可以看出 在請求之前先準備了三個對象:OkhttpClient,Request,和Call鳞上。其中的屬性參數(shù)請查看源碼这吻。OKHttpClient使用的Builder模式:

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

然而在builder創(chuàng)建的時候有兩個類很特殊:當你定義攔截器后會把你當初定義好的攔截器的集合進行一次不可修改操作(即這里的攔截器一旦定義后放入攔截器集合就不能再被修改了),當然這兩個攔截器是在什么時候被使用篙议,我先不說 只是告訴大家唾糯,一但調用方法build()怠硼,就會創(chuàng)建OkhttpClient就會把當初鏈式定義的東西確定下來,而且是不允許修改的移怯。

    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
   ························ ······························
   
  this.interceptors = Util.immutableList(builder.interceptors);
  this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
  ························································
  //這個工具類返回一個不可修改的list
  /** Returns an immutable copy of {@code list}. */
  public static <T> List<T> immutableList(List<T> list) {
    return Collections.unmodifiableList(new ArrayList<>(list));
  }

我們再看看Call 當然Call是一個接口其實現(xiàn)類是 new RealCall
為了能理解什么是Call我們先看Call這個類的注釋吧:

/**
 * 
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable

忽略前半句(沒有太大的意義)由于該對象表示單個請求/響應對(流)香璃,因此不能執(zhí)行兩次。
總體意思是:call是一個可以被取消的對象而且一個call表示單個請求/響應流舟误,call是不會被反復調用的葡秒。

 /** Returns the original request that initiated this call. */
  Request request();

原來call有個request方法,執(zhí)行該方法會把調用者本身給返回嵌溢。
看看RealCall實現(xiàn):

 @Override public Request request() {
    return originalRequest;
  }

原來如此啊同云,就是把入?yún)⒌膔equest返回。還有這個同步請求的方法也在call里邊堵腹。

  /** 
   * Invokes the request immediately, and blocks until the response can be processed or is in error.
   * 能快速并阻塞的invokes請求直到響應被處理或者出錯
   * 
   * 這里是同步請求
   * 
  ···
   */
  Response execute() throws IOException;

這里originalRequest有一句注釋:

The application's original request unadulterated by redirects or auth headers.
應用程序的原始的request, 沒有攙雜重定向或認證headers炸站。

既然如此我們看看初始化Request的時候做了什么吧,首先Request也是使用Builder的方式創(chuàng)建的:

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.
  }
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }

這里看到創(chuàng)建Request 對象的時候會創(chuàng)建headers疚顷,請求方式默認是“GET”旱易,且必須指定URL 。而且這里的CacheControl 是懶構造的腿堤,即一開始創(chuàng)建的Request的CacheControl 是null的(當你不指定CacheControl 的時候)阀坏。

看到這里我有點蒙蔽,看起來這個call并不像是請求對象笆檀,更像是一個調度對象(其實也是自己管理自己玩):它能取消自己忌堂,能執(zhí)行請求。這樣讓我有些迫不及待的看call的實現(xiàn):RealCall.calss
先看構造:看看當我們開始執(zhí)行請求的時候它做了什么

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  //把client傳了進來
    this.client = client;
    //把request傳了進來
    this.originalRequest = originalRequest;
     //把是否WebSocket
    this.forWebSocket = forWebSocket;
    //重點:這里創(chuàng)建了一個RetryAndFollowUpInterceptor
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

好奇心驅使我需要先了解下RetryAndFollowUpInterceptor.class:

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

哦酗洒,這里先理解為一個幫助失敗的請求重新完成自己使命的Interceptor 士修。(看來OKhttp的重連機制在這里,先mark一下)

請求前的資源整合

好了樱衷,整合下我們現(xiàn)在的資源棋嘲,出現(xiàn)在我們面前的有三個對象:
1 OkHttpClient,這個類是全局唯一的這里定義東西很多簡要的說明一些都
2 Request: 一個請求對應一個Request對象矩桂,其中包含了:請求的URL沸移,請求頭,請求體侄榴,緩存控制器
3 RealCall: 實現(xiàn)了Call的方法雹锣,其中包含了Request對象,OkHttpClient對象的引用還有一個與Call唯一對應重連機制攔截器(RetryAndFollowUpInterceptor )癞蚕,并且call提供了同步請求的方法和異步請求的方法蕊爵,以及返回的response。
這里我把RealCall看作請求的facade(門面設計模式)涣达,把OkhttpClient看作網(wǎng)絡請求的容器在辆,request像是一個上下文對象证薇。那這個容器都為RealCall提供了什么呢?

根據(jù)流程先看execute():

Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
try {
  client.dispatcher().executed(this);
  Response result = getResponseWithInterceptorChain();
  if (result == null) throw new IOException("Canceled");
  return result;
} finally {
  client.dispatcher().finished(this);
}

}

看到這里我們有了兩個結論:
1原來是這樣保證call不會被重復執(zhí)行啊匆篓,調用前先檢查當前的Call是不是被執(zhí)行過(把當前的executed = true)浑度,當然這里也考慮到并發(fā)的情況,如果被執(zhí)行了 或者說當前請求在處理隊列里就拋異常鸦概,
2client.dispatcher().executed(this);看到這里說明call并不管理請求箩张,這里調用了dispasrcher,并把自己傳遞給dispasrcher,這里正好說明了okhttpclient的作用之一就是請求調度窗市∠瓤叮可是RealCall卻提供了一套請求執(zhí)行的棧處理。(符合面向對象的思想)
至于這個執(zhí)行的dispatch是誰咨察?什么時候創(chuàng)建的论熙?
我們繼續(xù)看:

/**

  • Policy on when async requests are executed.
  • <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
  • own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
  • of calls concurrently.
    */
    public final class Dispatcher
1異步請求的執(zhí)行策略,沒有繼承摄狱,不是抽象的脓诡,看來這里做了挺多東西,是個核心類媒役。
什么時候被賦值呢祝谚?

public Builder() {
dispatcher = new Dispatcher();
···
}
public OkHttpClient() {
this(new Builder());
}

2這里我就明白了,創(chuàng)建OkClient的時候會把這個Dispatcher創(chuàng)建出來酣衷。
現(xiàn)在我又有問題了:
1 Dispatcher被創(chuàng)建出來都干了什么交惯,
2 眾所周知OKhttp是相當好的網(wǎng)絡請求相對的這個OkClient將會是單例的出現(xiàn)在整個APP當中,這個Dispatcher將如果處理眾多的call呢穿仪?因為volley一開始會創(chuàng)建多個(大概是5個)dispatcher(每個dispatcher將會是一個線程來處理請求)這里用一個dispatcher行不行呢席爽?我很擔憂啊。
這里先看看Dispatcher在創(chuàng)建的時候都做了什么吧:

public Dispatcher() {
}

納尼牡借?沒搞錯吧空構造拳昌?,事實是這樣的:

private int maxRequests = 64; //最大并發(fā)請求數(shù)為64
private int maxRequestsPerHost = 5;// 每個主機最大請求數(shù)為5
private Runnable idleCallback;

/** Executes calls. Created lazily. */
private ExecutorService executorService;

這里要做一下講解:
>Deque<RealCall> runningSyncCalls:同步調用的生產(chǎn)者隊列钠龙。
ExecutorService executorService:call消費者池。
Deque<AsyncCall> readyAsyncCalls:緩存(用數(shù)組實現(xiàn)御铃,可自動擴容碴里,無大小限制)
Deque<AsyncCall> runningAsyncCalls:正在運行的任務,僅僅是用來引用正在運行的任務以判斷并發(fā)量上真,注意它并不是消費者緩存
*根據(jù)生產(chǎn)者消費者模型的模型理論咬腋,當入隊(enqueue)請求時,如果滿足(runningRequests<64 && runningRequestsPerHost<5)睡互,那么就直接把AsyncCall直接加到runningCalls的隊列中根竿,并在線程池中執(zhí)行陵像。如果消費者緩存滿了,就放入readyAsyncCalls進行緩存等待寇壳。*


但是這個ExecutorService 在同步的時候是沒有創(chuàng)建的:而是通過executorService()方法返回ExecutorService 醒颖,然后使用線程池提供的execute方法把call傳進去。

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

}
 public synchronized ExecutorService executorService() {
        if(this.executorService == null) {
            this.executorService = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
        }

        return this.executorService;
    }
```

其實我是有些失望的怎么這個dispatcher這么不給力壳炎,只是把任務進行調度分發(fā)泞歉。
這些看看就行了,等遇到了再細說吧免得跑偏了:

```
 /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
```
果然馬上就用到了runningSyncCalls匿辩,
Running synchronous calls. Includes canceled calls that haven't finished yet.
運行同步calls腰耙。包括尚未完成的canceled calls。沒想到這里是同步的啊铲球,那就先探索同步調用流程咯挺庞。。稼病。(我會告訴大家我沒用過OKHTTP嗎挠阁?)
當然看到這里也治好了我的懵逼,原來在call的時候就分了同步執(zhí)行和異步執(zhí)行溯饵。

這里用ArrayDeque來存儲侵俗,ArrayDeque?我沒用過就先看下大概介紹吧:一個非線程安全丰刊,不限容量的隊列隘谣。
到此為止的工作很簡單我做個總結:準備對象放入隊列runningSyncCalls,然后就沒有然后了啄巧,至于怎么執(zhí)行寻歧,就需要看RealCall了。

## 不論是同步還是異步秩仆,都是調用這里:

回到RealCall.class看這里码泛,也許 Response result = getResponseWithInterceptorChain();能給我們答案:

```
//這里主要把用戶添加的interceptor和OK自己必要的interceptor放入集合 List<Interceptor> interceptors然后在 Interceptor.Chain中反復創(chuàng)建自己通過傳進去的index從前到后的把interceptor放入棧中,執(zhí)行順序參照棧的執(zhí)行順序澄耍。
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);
  }
```
好想法噪珊,把所有的interceptor放入list中,需要了繼續(xù)加齐莲,執(zhí)行的時候從頭到尾擼一遍痢站,反正都定義的接口方法,只要夠規(guī)范选酗,就能處理的很好阵难。(先膜拜一下)
那讓我們看看RealInterceptorChain類的proceed方法:

```
 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    ···
    
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   ···

    return response;
  }
```
認真梳理流程:先看下到底RealCall有多少個interceptor:
在client里找到這兩個集合
      final List<Interceptor> interceptors;
     final List<Interceptor> networkInterceptors;
 List<Interceptor> interceptors = new ArrayList<>();
   1  interceptors.addAll(client.interceptors());
   2  interceptors.add(retryAndFollowUpInterceptor);
   3  interceptors.add(new BridgeInterceptor(client.cookieJar()));
   4  interceptors.add(new CacheInterceptor(client.internalCache()));
   5  interceptors.add(new ConnectInterceptor(client));
    還有這倆:
 if (!forWebSocket) {
6      interceptors.addAll(client.networkInterceptors());
    }
7    interceptors.add(new CallServerInterceptor(forWebSocket));
  根據(jù)加入的先后順序我已經(jīng)排序好了。

就這樣創(chuàng)建并調用實現(xiàn)intercept接口intercept()方法:
```
 // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
```
明明就是調用芒填,intercept呜叫,為什么還要創(chuàng)建一個RealInterceptorChain  next空繁,想不明白,還差點把自己繞暈了朱庆。假如我不自定義interceptor盛泡,即client.interceptors()的size=0,則第一個執(zhí)行interceptor方法的是這個 interceptors.add(retryAndFollowUpInterceptor);
這里我畫個圖給大家做一下講解:




![OKHttp_Call.png](http://upload-images.jianshu.io/upload_images/2916442-57a58c206621f78a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



 先從這個retryAndFollowUpInterceptor開始椎工,看看ok自己的內部interceptor要next干嘛饭于,這樣就方便我們理解并寫出符合ok邏輯的代碼(享受與大神肩并肩的趕腳):
 
```
Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

  ···
  }
```
這里的Chain chain 就是RealInterceptorChain 類不同的地方是此時的index已經(jīng)+1了,先獲取request维蒙,chain.request()然后在retryAndFollowUpInterceptor中掰吕,創(chuàng)建了StreamAllocation:

```
/**
 * 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
```
這個class協(xié)調三個實體client.connectionPool(),createAddress(request.url())颅痊,callStackTrace之間的關系殖熟。
client.connectionPool():ConnectionPool

```
/**
 * Manages reuse of HTTP and HTTP/2 connections for reduced network latency. 
 * 管理重用HTTP和HTTP / 2連接減少網(wǎng)絡延遲
 * HTTP requests that share the same {@link Address} may share a {@link Connection}.
 *  This class implements the policy of which connections to keep open for future use.
 *  該類實現(xiàn)了保持連接打開方便以后使用的策略
 */
public final class ConnectionPool
```
Sound that's good,讓我們看看連接池的核心哈:
#### ThreadPoolExecutor
```
/**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   */
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
```
corePoolSize :一個executor 斑响,最小并發(fā)0個線程存在(線程池維護線程的最少數(shù)量,這就意味著空閑一段時間后所有的線程將全部被銷毀)
 maximumPoolSize: 最大線程數(shù)菱属,當任務進來時可以擴充的線程最大值,當大于了這個值就會根據(jù)丟棄處理機制來處理(這里直接用了int的最大值舰罚,意思就是可以承載無限多的連接)
 keepAliveTime: 當線程數(shù)大于corePoolSize時纽门,多余的空閑線程的最大存活時間
ThreadFactory threadFactory:    新建線程工廠,這里使用OKhttp的ThreadFactory返回創(chuàng)建的線程并把線程設置成daemon(true)

```
 @Override public Thread newThread(Runnable runnable) {
        Thread result = new Thread(runnable, name);
        result.setDaemon(daemon);//設置為后臺線程(守護線程营罢,服務線程)赏陵,即此線程不依賴jvm(當jvm退出之后該線程不會被結束),什么時候結束:守護線程在沒有用戶線程可服務時自動離開(即便是App退出這個線程中的內容也繼續(xù)執(zhí)行饲漾,執(zhí)行完畢后退出)
        return result;
      }
```
其實還有一個參數(shù)handler(這里使用exector提供的RejectedExecutionHandler):當提交任務數(shù)超過maxmumPoolSize+workQueue之和時蝙搔,任務會交給RejectedExecutionHandler來處理
>重點講解: 
其中比較容易讓人誤解的是:corePoolSize,maximumPoolSize考传,workQueue之間關系吃型。 
1.當線程池小于corePoolSize時,新提交任務將創(chuàng)建一個新線程執(zhí)行任務僚楞,即使此時線程池中存在空閑線程勤晚。 
2.當線程池達到corePoolSize時,新提交任務將被放入workQueue中镜硕,等待線程池中任務調度執(zhí)行 
3.當workQueue已滿运翼,且maximumPoolSize>corePoolSize時,新提交任務會創(chuàng)建新線程執(zhí)行任務 
4.當提交任務數(shù)超過maximumPoolSize時兴枯,新提交任務由RejectedExecutionHandler處理 
5.當線程池中超過corePoolSize線程,空閑時間達到keepAliveTime時矩欠,關閉空閑線程 
6.當設置allowCoreThreadTimeOut(true)時财剖,線程池中corePoolSize線程空閑時間達到keepAliveTime也將關閉
#### SynchronousQueue:
>Java 6的并發(fā)編程包中的SynchronousQueue是一個沒有數(shù)據(jù)緩沖的BlockingQueue悠夯,生產(chǎn)者線程對其的插入操作put必須等待消費者的移除操作take,反過來也一樣躺坟。不像ArrayBlockingQueue或LinkedListBlockingQueue沦补,SynchronousQueue內部并沒有數(shù)據(jù)緩存空間,你不能調用peek()方法來看隊列中是否有數(shù)據(jù)元素咪橙,因為數(shù)據(jù)元素只有當你試著取走的時候才可能存在夕膀,不取走而只想偷窺一下是不行的,當然遍歷這個隊列的操作也是不允許的美侦。隊列頭元素是第一個排隊要插入數(shù)據(jù)的線程产舞,而不是要交換的數(shù)據(jù)。數(shù)據(jù)是在配對的生產(chǎn)者和消費者線程之間直接傳遞的菠剩,并不會將數(shù)據(jù)緩沖數(shù)據(jù)到隊列中易猫。

還沒講解第二個參數(shù):createAddress(request.url())
創(chuàng)建Address 類前先準備好需要被初始化的對象:

```
 private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }
```
SSLSocketFactory :用于創(chuàng)建SSLSocket協(xié)議的工廠。在創(chuàng)建httpclient的時候默認會把這個sslSocketFactory 創(chuàng)建出來具壮,默認TrustManager使用X509TrustManager 准颓。
```
  if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }
```
由此還可以看出,如果自定義sslSocket棺妓,也需要定義certificateChainCleaner攘已,如果使用默認的sslSocketFactory ,certificateChainCleaner 也是會被配置完畢怜跑。

HostnameVerifier :hostnameVerifier = OkHostnameVerifier.INSTANCE;
>此類是用于主機名驗證的基接口样勃。在握手期間,如果 URL 的主機名和服務器的標識主機名不匹配妆艘,則驗證機制可以回調此接口的實現(xiàn)程序來確定是否應該允許此連接彤灶。策略可以是基于證書的或依賴于其他驗證方案。當驗證 URL 主機名使用的默認規(guī)則失敗時使用這些回調批旺。

CertificatePinner :


```
/*Constrains which certificates are trusted.Pinning certificates defends against attacks on certificate authorities.It also prevents connections through man-in-the-middle certificate authorities either known or unknown to the application's user.
* /
certificatePinner = CertificatePinner.DEFAULT
public static final CertificatePinner DEFAULT = new Builder().build();
```



死磕OKHttp源碼繼續(xù)看retryAndFollowUpInterceptor.intercept(Chain chain)方法的代碼:
  
```
int followUpCount = 0;
    Response priorResponse = null;
    //無限輪詢開始
    while (true) {
   ···
      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } 
      ···
    }
```
那我們就看下面那個Interceptor(BridgeInterceptor )

```
/**
 *  Bridges from application code to network code.
 *  從應用程序代碼到網(wǎng)絡代碼的橋梁幌陕。
 *  First it builds a network request from a user request.
 *  首先,它從用戶request建立網(wǎng)絡請求
 *  Then it proceeds to call the network. 
 *  然后它繼續(xù)調用網(wǎng)絡汽煮。
 *  Finally it builds a user response from the network response.
 *  最后搏熄,它建立一個用戶response 從網(wǎng)絡response
 */
public final class BridgeInterceptor implements Interceptor

 private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
```
創(chuàng)建的時候需要傳cookieJar,我們看下cookie從哪里认境唷:

```
client.cookieJar():cookieJar = CookieJar.NO_COOKIES;
 CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };
```
看意思算是明白了這時候只是創(chuàng)建一個空的

```
//這段代碼是我杜撰的
List<Cookie> cookies = Collections.emptyList()
```
看看是怎么調用的心例,這里的Chain chain 依舊是RealInterceptorChain index=index+2
```
Override public Response intercept(Chain chain) throws IOException {
//獲取到原始的request
    Request userRequest = chain.request();
    //說白了就是原始request的拷貝
    //但是多了headers  this.headers = new Headers.Builder();
    //默認請求方式是GET
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    
    if (body != null) {
      MediaType contentType = body.contentType();
      //這里給header添加內容contentType,在一開創(chuàng)建request的時候已經(jīng)添加進去了contentType = MediaType.parse(contentType + "; charset=utf-8")
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

    ···
    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing the transfer stream.
    //這里默認使用gzip對傳輸流進行壓縮
    //如果不想使用定義header的Accept-Encoding和Range
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
//這里怎么使用GZIP壓縮需要向后看
//cookies 獲取方案:Collections.emptyList()一個空的集合
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {//如果不是空則以key=value的形式拼接cookie
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
//代理設置 如果沒有設置代理 就填入"okhttp/3.6.0"
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
//在這里繼續(xù)調用下一個intercept
    Response networkResponse = chain.proceed(requestBuilder.build());
···
```
CacheInterceptor 到了cache處理:到了這里chain的index+3
首先判斷cache的有無:有緩存從緩存中獲取鞋囊,沒有緩存就null
```
 Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
```

1什么是cache止后?
如果仔細看okhttpClient的話會發(fā)現(xiàn)cache分兩個類
一個是Cache類:緩存HTTP和HTTPS響應文件系統(tǒng),以便它們可以被重用,節(jié)省時間和帶寬译株。

```
public final class Cache implements Closeable, Flushable
```
另一個是:InternalCache類瓜喇,InternalCache是OkHttp的內部緩存接口。應用程序不應該實現(xiàn)它歉糜,而是使用Cache

```
/**
 * OkHttp's internal cache interface. Applications shouldn't implement this: instead use {@link
 * okhttp3.Cache}.
 */
public interface InternalCache 
```
通過這個問題兩個cache對比我們明白了:

Cache是外部使用的乘寒,InternalCache是內部使用的。

2那這個cache從哪里來的匪补?
在httpclient里定義了這一個私有方法伞辛,還有一個public方法,如果是一開始就有cache就可以直接傳進去夯缺,這樣this.internalCache = null蚤氏。(默認就是null)
```
MainActivity:
 OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .cache(new Cache())
                .build();
 OkHttpClient:               
/** Sets the response cache to be used to read and write cached responses. */
    public Builder cache(Cache cache) {
      this.cache = cache;
      this.internalCache = null;
      return this;
    }
```
這樣對比貌似cache和internalcache是對立的關系,一個存在另一個就沒有必要了喳逛,而且再次驗證了問題1的結論瞧捌,一個是供外部使用的Cache,另一個是內部使用的InternalCache 润文。
```
 /** Sets the response cache to be used to read and write cached responses. */
    void setInternalCache(InternalCache internalCache) {
      this.internalCache = internalCache;
      this.cache = null;
    }
```
一開始創(chuàng)建HttpClient的時候會先執(zhí)行一個靜態(tài)代碼塊:
```
  static {
    Internal.instance = new Internal() {
   ···

      @Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {
        builder.setInternalCache(internalCache);
      }

   ···
  }
```
這個代碼塊創(chuàng)建了Internal這個抽象類姐呐,其中就有setCache方法具體實現(xiàn)是調用了setInternalCache
現(xiàn)在又有了疑問,Internal是干嘛的典蝌?

```
/**
 * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation packages. 
 * The only implementation of this interface is in {@link OkHttpClient}.
 */
public abstract class Internal
```
okhttp3內部API的升級版曙砂,因此在okhttp包中這些方法是可以被使用的。
該接口的實現(xiàn)只有在OkHttpClient中可見骏掀。
因此可以確定的是OkHttpClient在Application中只可能有一個鸠澈,Internal這個升級方法包也就只會有一個而且它的實現(xiàn)是在OkHttpClient中的。這里先用到一個講一個吧截驮,

3又是在哪里保存著的呢笑陈?如果仔細研究Cache類將會發(fā)現(xiàn)其實這里對cache做了很多處理:

```
  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
```
1使用時需要設置路徑和文件大小,這里默認fileSystem=FileSystem.SYSTEM(okio包)
2DiskLruCache根據(jù)字面意思葵袭,這里存儲使用的是本地文件存儲+LRUCache,算是二級緩存先存內存并用lruCache進行管理涵妥,同時在本地也會有副本,如果內存中沒有就會去本地找坡锡,都沒有蓬网,那就是真的沒有了。
```
 public static String key(HttpUrl url) {
    return ByteString.encodeUtf8(url.toString()).md5().hex();
  }
```
3這里集合使用的key是URL進行MD5再取哈希(挺好)
接著看是怎么取緩存的:
```
  Response get(Request request) {
    String key = key(request.url());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
      ···
      //1 根據(jù)KEY從cache中取值
      snapshot = cache.get(key);
      ···
      //2 把返回的snapshot對象zhuan換成Entry
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
      ···
      //3 返回response 
    Response response = entry.response(snapshot);
      ···
    return response;
  }
```
既然要死磕就把這個吃透:
#### DiskLruCache

DiskLruCache的創(chuàng)建是從create開始的

```
 /**
   * Create a cache which will reside in {@code directory}. 
   * This cache is lazily initialized on first access and will be created if it does not exist.
   * 創(chuàng)建一個緩存鹉勒,緩存在File directory中帆锋。
   * 這個緩存在第一次訪問時被初始化,如果它不存在禽额,將被創(chuàng)建锯厢。
   * 好了注釋幫我把第三個問題回答了哈哈哈
   * @param directory a writable directory
   * @param valueCount the number of values per cache entry. Must be positive.
   * @param maxSize the maximum number of bytes this cache should use to store
   */
  public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion,
      int valueCount, long maxSize) {
    if (maxSize <= 0) {
      throw new IllegalArgumentException("maxSize <= 0");
    }
    if (valueCount <= 0) {
      throw new IllegalArgumentException("valueCount <= 0");
    }

    // Use a single background thread to evict entries.
    //這里緩存類使用一個自己的線程池,而且這個線程池和鏈接線程池一樣是一個后臺線程
    //忍不住吐槽一下:Util.threadFactory只會創(chuàng)建一個后臺線程,別的線程估計也用不到
    Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));

    return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
  }
```
但是這里還是會比較下這個cache線程池和連接線程池哲鸳,先把ConnectionPool的線程池拿過來:

```
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
```
同樣是建立deamon線程(后臺線程)臣疑,同樣是當線程空閑時間>keepAliveTime就會被ThreadPoolExecutor收掉(corePoolSize = 0)
不同的地方在
maximumPoolSize  :  cache.maximumPoolSize = 1 


深入DiskLruCache盔憨,關鍵方法 cache.get(key):
根據(jù)KEY返回對應的條目的快照(Snapshot 類徙菠,是DiskLruCache的內部類),如果不存在郁岩,它將不可讀婿奔。如果一個返回值,它是移動到LRU queue頭问慎。

```
 public synchronized Snapshot get(String key) throws IOException {
    initialize();

    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null || !entry.readable) return null;

    Snapshot snapshot = entry.snapshot();
    if (snapshot == null) return null;

    redundantOpCount++;
    journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
    if (journalRebuildRequired()) {
      executor.execute(cleanupRunnable);
    }

    return snapshot;
  }
```
讓我們接下來看:
```

Override public Response intercept(Chain chain) throws IOException {
  ···
//獲取當前時間
    long now = System.currentTimeMillis();
//創(chuàng)建一個cache存儲類 
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //把網(wǎng)絡請求和緩存的的結果賦值進去
    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)絡和緩存不足如叼,失敱尽:自己創(chuàng)建一個返回結果
    if (networkRequest == null && cacheResponse == null) {
     ···
    }

   ···
    //先請求網(wǎng)絡 看下一個intercept
    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());
      }
    }

    ···

    return response;
  }
```
ConnectInterceptor已經(jīng)被解鎖,在connectIntercept中強轉成RealInterceptorChain笼恰,看看接口Chain提供了什么方法:

```
  interface Chain {
  //獲取請求體
    Request request();
//處理
    Response proceed(Request request) throws IOException;
//鏈接
    Connection connection();
  }
```
然而在ConnectInterceptor中使用了realChain.streamAllocation獲得Socket管理類(StreamAllocation )
看看ConnectInterceptor怎么處理:

```
 @Override public Response intercept(Chain chain) throws IOException {
 
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //從RealInterceptorChain 中獲取Socket管理(StreamAllocation )
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    //我們需要網(wǎng)絡來滿足這個要求踊沸。可能用于驗證條件獲取
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //一開始的創(chuàng)建在這里得到了調用:newStream獲取到HttpCodec 
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
     //開始連接
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
```
什么是HttpCodec社证?對HTTP請求進行編碼并對HTTP響應進行解碼逼龟。 我們進去看看會發(fā)現(xiàn)HttpCodec是個接口類,實現(xiàn)在streamAllocation之中:

```
** Encodes HTTP requests and decodes HTTP responses. */
//對HTTP請求進行編碼并對HTTP響應進行解碼
public interface HttpCodec
```
編碼解碼都有哪些方法呢追葡?

```
  /** Returns an output stream where the request body can be streamed. */
  Sink createRequestBody(Request request, long contentLength);

  /** This should update the HTTP engine's sentRequestMillis field. */
  void writeRequestHeaders(Request request) throws IOException;

  /** Flush the request to the underlying socket. */
  //將請求刷新到基礎套接字
  void flushRequest() throws IOException;

  /** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
  //將請求刷新到基礎套接字腺律,將不再發(fā)送更多的字節(jié)
  void finishRequest() throws IOException;

  /**
   * Parses bytes of a response header from an HTTP transport.
   * 從HTTP傳輸解析響應標頭的字節(jié)。
   */
  Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;

  /** Returns a stream that reads the response body. */
  //返回讀取響應體的流
  ResponseBody openResponseBody(Response response) throws IOException;

  /**
   * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
   * 取消此流宜肉。此流持有的資源將被清理匀钧,但不是同步的。
   * That may happen later by the connection pool thread.
   * 發(fā)生在連接池線程池之后谬返,就是等連接完成后才執(zhí)行清理線程之斯。
   */
  void cancel();
```
好了可以看看最后一個intercept:CallServerInterceptor

```
 @Override public Response intercept(Chain chain) throws IOException {
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
···
    //到了這里才進行了請求
    httpCodec.finishRequest();
    ···
    //構造response
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

  ···

    return response;
  }
```
看似結束了其實這里返回了response之后還做了很多處理 我們還要返回回去繼續(xù)從低到頂?shù)臄]一遍:
看ConnectInterceptor.class,這里直接把response做了處理直接返回了并沒有什么處理:

```
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
```
接下來看CacheInterceptor.class 拿到網(wǎng)絡請求的response還沒完這里進行了再處理朱浴。
如果當前的請求在cache中存在且沒有進行改變則進行更新并保存回去
```
 // If we have a cache response too, then we're doing a conditional get.
 //如果我們也有緩存響應吊圾,那么我們做一個條件得到。
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
      //如果請求結果是沒有被改動的(這里)
      //重新構造一個response從cacheResponse里
        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());
      }
    }
···

```
 
如果沒有緩存則進行緩存并返回給上一個intercept:
```
   Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }
    return response;
```
繼續(xù)往上走翰蠢,在BridgeIntercept.class中

```
 Response networkResponse = chain.proceed(requestBuilder.build());
    //記錄cookie到cookieJar项乒,cookie從networkResponse.headers()中獲取
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    //構造一個空的responseBuilder 
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //把返回結果和head和responseBody加進去
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
```
到了頂層RetryAndFollowUpInterceptor類得到response后在這里跳出循環(huán)
```
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }
```
  // Attach the prior response if it exists. Such responses never have a body.
   //如果存在,附加先前的response 梁沧。這樣的responses 沒有body
  這樣的priorResponse 一開始就是null檀何,怎么會有值了呢?
  看這里:
```
request = followUp;
  priorResponse = response;
```
這里如果請求出現(xiàn)問題如:

```
catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        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.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } 
```
這里循環(huán)還是會繼續(xù)重連機制生效,把response和request賦值開啟請求二周目频鉴,那么問題來了什么時候會結束重連呢栓辜?看這里:

```
//1 大于最大重連次數(shù)
if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
//2如果requestbody是不可重連的,釋放socket拋出異常
      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
 ···
  //3如果之前的streamAllocation沒有被釋放
      if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }
```
最大重連次數(shù)20
```
 private static final int MAX_FOLLOW_UPS = 20;
```
最終結束dispatcher返回請求結果:
```
eturn result;
} finally {
  client.dispatcher().finished(this);
}
```
呼垛孔,大工告成藕甩,流程大抵如此:
1會詳細講解DiskLruCache
2詳細講解StreamAllocation
3Gzip實現(xiàn)
4連接池工作
5HttpCodec這個http編碼解碼類
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市周荐,隨后出現(xiàn)的幾起案子狭莱,更是在濱河造成了極大的恐慌,老刑警劉巖概作,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腋妙,死亡現(xiàn)場離奇詭異,居然都是意外死亡讯榕,警方通過查閱死者的電腦和手機骤素,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愚屁,“玉大人济竹,你說我怎么就攤上這事〖拢” “怎么了规辱?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栽燕。 經(jīng)常有香客問我罕袋,道長,這世上最難降的妖魔是什么碍岔? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任浴讯,我火速辦了婚禮,結果婚禮上蔼啦,老公的妹妹穿的比我還像新娘榆纽。我一直安慰自己,他們只是感情好捏肢,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布奈籽。 她就那樣靜靜地躺著,像睡著了一般鸵赫。 火紅的嫁衣襯著肌膚如雪衣屏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天辩棒,我揣著相機與錄音狼忱,去河邊找鬼膨疏。 笑死,一個胖子當著我的面吹牛钻弄,可吹牛的內容都是我干的佃却。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼窘俺,長吁一口氣:“原來是場噩夢啊……” “哼饲帅!你這毒婦竟也來了?” 一聲冷哼從身側響起批销,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤洒闸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后均芽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昔期,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡缔莲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盔性。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲锄。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡劲妙,死狀恐怖,靈堂內的尸體忽然破棺而出儒喊,到底是詐尸還是另有隱情镣奋,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布怀愧,位于F島的核電站侨颈,受9級特大地震影響,放射性物質發(fā)生泄漏芯义。R本人自食惡果不足惜哈垢,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扛拨。 院中可真熱鬧耘分,春花似錦、人聲如沸绑警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽计盒。三九已至渴频,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間章郁,已是汗流浹背枉氮。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工志衍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聊替。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓楼肪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惹悄。 傳聞我的和親對象是個殘疾皇子春叫,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容