okhttp源碼學(xué)習(xí)筆記(一)-- 綜述

關(guān)于okhttp是一款優(yōu)秀的網(wǎng)絡(luò)請求框架,關(guān)于它的源碼分析文章有很多再来,這里分享我在學(xué)習(xí)過程中讀到的感覺比較好的文章可以做參考部脚,本系列的文章是在學(xué)習(xí)okhttp源碼過程中的筆記姻采,記錄一是為了總結(jié)知識做祝,二是為了分享學(xué)習(xí)過程,其中有錯(cuò)誤和欠缺之處蠕啄,還請不吝批評指正场勤。

  1. 拆輪子系列:拆 OkHttp
  2. 帶你學(xué)開源項(xiàng)目:OkHttp--自己動(dòng)手實(shí)現(xiàn)okhttp
  3. OkHttp3源碼分析

okhttp是一個(gè)網(wǎng)絡(luò)請求框架,不僅僅可以用于Android應(yīng)用中歼跟。在okhttp之前和媳,Android中有不少的優(yōu)秀網(wǎng)絡(luò)請求框架,比如HttpClient嘹承,Volley等窗价,而okhttp雖然與這些框架完成相同的事情如庭,但是與之存在本質(zhì)的不同叹卷,前者都是對Java中的UrlConnection進(jìn)行封裝,而okhttp則是直接對socket進(jìn)行封裝坪它,也就是說他們所處的層次不同骤竹。除此以外,okhttp在各種功能屬性和性能方面都有很大的優(yōu)勢往毡,關(guān)于這些可以參考以上幾篇文章蒙揣,這里直接進(jìn)入正題介紹okhttp的實(shí)現(xiàn)。

網(wǎng)絡(luò)請求所要完成的基本問題無非就是實(shí)現(xiàn)客戶端請求獲取服務(wù)器的資源开瞭,完成客戶端與服務(wù)器的通信懒震,簡單來說,okhttp就是在特定環(huán)境下(這里可以說移動(dòng)客戶端與服務(wù)器的通信)實(shí)現(xiàn)Http協(xié)議嗤详。那么這里首要問題就是解決如何構(gòu)建請求Request个扰,如何獲取相應(yīng)Response,然后當(dāng)我們擁有了request葱色,response递宅, 以及系統(tǒng)為我們提供的socket通信機(jī)制以后,就需要想辦法解決如何發(fā)送request和接收response苍狰,也就是實(shí)現(xiàn)http協(xié)議所規(guī)定的細(xì)節(jié)流程办龄。在request到response的轉(zhuǎn)換過程中,http協(xié)議涉及到 了建立連接淋昭,維護(hù)連接(連接復(fù)用)俐填,緩存處理以及cookie處理等問題。在請求的過程中翔忽,存在重定向等問題英融,一次請求可能涉及到多次的請求操作赫段,同時(shí)還會有多次請求同一個(gè)服務(wù)器上的資源的情況,這里就涉及到連接復(fù)用的問題矢赁。另外也更多地存在多次請求相同資源的情況糯笙,這也就涉及到緩存處理的情況。此外還有cookie持久化的問題撩银。除此以外给涕,okhttp還支持https協(xié)議,同時(shí)也支持SPDY和http2協(xié)議额获,因此計(jì)劃將分為請求和響應(yīng)的構(gòu)建與轉(zhuǎn)換够庙,連接和流管理,緩存管理抄邀,cookie持久化幾部分介紹我對okhttp框架的學(xué)習(xí)過程耘眨。在這個(gè)過程中完全忽略對https以及SPDY和Http2的考慮,在后續(xù)的學(xué)習(xí)中將會補(bǔ)充這兩部分的描述境肾。

本篇文章主要介紹網(wǎng)絡(luò)請求的整個(gè)過程剔难,而不過于深究一些細(xì)節(jié),首先會簡單介紹request和response的構(gòu)建奥喻, 以及Okhttp引入的Call類偶宫,該類是對request的封裝,okhttp之所以對請求再次封裝环鲤,(僅僅是個(gè)人見解)一是為了分離職責(zé)纯趋,將封裝請求與執(zhí)行請求過程的操作(如同步與異步,終止或者取消等操作)分離冷离,二是因?yàn)槌趁埃琱ttp請求經(jīng)常會遇到重定向等問題,因此一次請求過程會涉及多次請求西剥,而okhttp則傾向于將這多次請求使用同一個(gè)連接完成痹栖,這也就引入了將會在第二篇文章中重點(diǎn)介紹的call, streamAllocation, connection三個(gè)概念,具體原因則在第二篇文章中詳細(xì)分析蔫耽。同時(shí)由于網(wǎng)絡(luò)請求通常都會是異步操作结耀,請求的過程將會在不同線程中執(zhí)行,因此在第二部分中會重點(diǎn)介紹okhttp中的請求任務(wù)隊(duì)列以及調(diào)度機(jī)制匙铡。最后在第三部分則會介紹okhttp中網(wǎng)絡(luò)請求的執(zhí)行流程图甜,這里主要涉及到intercepter與chain之間的遞歸調(diào)用,從而使得不同的攔截器完成對請求以及響應(yīng)的處理鳖眼,最后實(shí)現(xiàn)后續(xù)文章中重點(diǎn)介紹的連接黑毅,緩存管理等機(jī)制。通過這三部分就可以從整體上把握okhttp時(shí)如何完成請求與響應(yīng)的構(gòu)建钦讳,以及他們的轉(zhuǎn)換操作矿瘦。

1. 請求與響應(yīng)的封裝

Request

如果熟悉Http協(xié)議就會知道枕面,請求報(bào)文由請求頭和請求體組成,那么Request則是請求報(bào)文在Java世界中的存在形式缚去,對于其代碼潮秘,我們只需要了解其內(nèi)部結(jié)果就夠了,如下:

/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
 */
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  ...
 }

這里的tag是一個(gè)請求的標(biāo)識易结,可以在后續(xù)的過程中終止或取消這個(gè)請求枕荞,除此以外,url和method都明白為請求url和請求方法搞动,而其他的頭部信息則以headers的形式統(tǒng)一保存躏精,而Headers類是一個(gè)工具類,統(tǒng)一管理請求和響應(yīng)的頭部信息鹦肿,其內(nèi)部結(jié)構(gòu)就是一個(gè)nameAndValue的字符串?dāng)?shù)組保存頭部信息矗烛,保存形式為一個(gè)鍵值緊跟一個(gè)value值, 熟悉Android中的ArrayMap數(shù)據(jù)結(jié)構(gòu)的同學(xué)應(yīng)該對這種實(shí)現(xiàn)方式比較了解箩溃,其優(yōu)勢可以參考ArrayMap的介紹文章瞭吃,Headers類是一個(gè)工具類,維護(hù)一個(gè)nameAndValue并對外提供各種方法(與HashMap類似碾篡,只是占用內(nèi)存更少虱而,而查詢效率略低)筏餐,具體代碼有興趣的同學(xué)可以自行查看开泽。最后就是請求體,下面為RequestBody的代碼:

public abstract class RequestBody {
  /** Returns the Content-Type header for this body. */
  public abstract MediaType contentType();

  /**
   * Returns the number of bytes that will be written to {@code out} in a call to {@link #writeTo},
   * or -1 if that count is unknown.
   */
  public long contentLength() throws IOException {
    return -1;
  }

  /** Writes the content of this request to {@code out}. */
  public abstract void writeTo(BufferedSink sink) throws IOException;
...
}

RequestBody為一個(gè)抽象類魁瞪,實(shí)現(xiàn)類中必須指定contentType穆律, 即請求體的報(bào)文類型,至于具體類型有哪些导俘,可以查看http協(xié)議的相關(guān)內(nèi)容峦耘,然后需要指定請求體需要寫入的內(nèi)容,即在writeTo方法中實(shí)現(xiàn)旅薄,這里的Sink和后面遇到的Source是屬于Okio的內(nèi)容辅髓,也就是square公司封裝的一個(gè)IO框架,暫時(shí)可以將他們分別理解為OutputStream和InputStream少梁。該方法會在建立連接以后調(diào)用洛口,如request.requsetBody().writeTo(Okio.sink(socket.outputStream())), 類似與這種形式凯沪,從而完成將請求體內(nèi)容寫入到socket中第焰。同時(shí)為了發(fā)送字符串和文件內(nèi)容方便,RequestBody還提供了靜態(tài)工具方法妨马,create的多種重載形式挺举,具體可以自行參考源碼杀赢,其實(shí)就是復(fù)寫上述的三個(gè)方法而已。

最后湘纵,請求的構(gòu)建過程參數(shù)角度脂崔,其實(shí)自然也就能想到這里會使用建造者模式,通過Builder設(shè)置不同參數(shù)梧喷,熟悉Okhttp使用的同學(xué)脱篙,對此應(yīng)該較為熟悉,這里不再介紹伤柄。Response的封裝與之類似绊困,但是獲取響應(yīng)必然與緩存有關(guān),這里先省略與緩存相關(guān)的部分(將在第三篇文章中介紹)适刀, 只是簡單介紹一些響應(yīng)的獲取構(gòu)建以及響應(yīng)體的內(nèi)容提取秤朗。

Response

/**
 * An HTTP response. Instances of this class are not immutable: the response body is a one-shot
 * value that may be consumed only once and then closed. All other properties are immutable.
 *
 * <p>This class implements {@link Closeable}. Closing it simply closes its response body. See
 * {@link ResponseBody} for an explanation and examples.
 */
public final class Response implements Closeable {
  final Request request;
  final int code;
  final String message;
  final Handshake handshake;
  final Headers headers;
  final ResponseBody body;

  Response(Builder builder) {
  ...
  }
  ...
}

這里省略了所有與緩存相關(guān)的代碼之后,結(jié)構(gòu)與請求幾乎完全一致笔喉,統(tǒng)一它也是由Builder構(gòu)造取视,包括頭部信息和響應(yīng)體,另外Http協(xié)議中規(guī)定了響應(yīng)碼以及響應(yīng)信息都有對應(yīng)的字段屬性常挚,另外需要注意的是注釋中說明的響應(yīng)體只能使用一次作谭,并且使用完畢以后需要關(guān)閉,Response實(shí)現(xiàn)的Closeable接口奄毡,其內(nèi)部也是調(diào)用的ResponseBody的close方法折欠。關(guān)于Response在緩存管理中將會重點(diǎn)介紹,這里只是與request對應(yīng)做一下簡單了解即可吼过。下面看ResponseBody的代碼:

public abstract class ResponseBody implements Closeable {
  /** Multiple calls to {@link #charStream()} must return the same instance. */
  private Reader reader;

  public abstract MediaType contentType();

  /**
   * Returns the number of bytes in that will returned by {@link #bytes}, or {@link #byteStream}, or
   * -1 if unknown.
   */
  public abstract long contentLength();

  ...

  public abstract BufferedSource source();
...
}

同樣一個(gè)抽象類锐秦,內(nèi)部包括三個(gè)方法,只不過與RequestBody中對應(yīng)的writeTo()不同的是盗忱,這里為source()方法酱床,即返回一個(gè)輸入流,預(yù)計(jì)在建立連接中會有response.body().source(Okio.source(socket.inputStream()))之類的調(diào)用趟佃,完成響應(yīng)體的獲取扇谣,從而輸入一個(gè)BufferedSource,可以將其簡單理解為一個(gè)輸入流闲昭,通過操作它罐寨,我們進(jìn)而可以獲取inputStream, Reader, String, byte[]等, ResponseBody都有提供對應(yīng)的方法汤纸,其實(shí)現(xiàn)方式可以自行查看源碼衩茸,這里不再詳細(xì)介紹。不過需要仔細(xì)閱讀的是這個(gè)類的注釋,如下:

/**
 * A one-shot stream from the origin server to the client application with the raw bytes of the
 * response body. Each response body is supported by an active connection to the webserver. This
 * imposes both obligations and limits on the client application.
 *
 * <h3>The response body must be closed.</h3>
 *
 * Each response body is backed by a limited resource like a socket (live network responses) or
 * an open file (for cached responses). Failing to close the response body will leak resources and
 * may ultimately cause the application to slow down or crash.
 *
 * <p>Both this class and {@link Response} implement {@link Closeable}. Closing a response simply
 * closes its response body. If you invoke {@link Call#execute()} or implement {@link
 * Callback#onResponse} you must close this body by calling any of the following methods:
 *
 * <ul>
 *   <li>Response.close()</li>
 *   <li>Response.body().close()</li>
 *   <li>Response.body().source().close()</li>
 *   <li>Response.body().charStream().close()</li>
 *   <li>Response.body().byteString().close()</li>
 *   <li>Response.body().bytes()</li>
 *   <li>Response.body().string()</li>
 * </ul>
 *
 * <p>There is no benefit to invoking multiple {@code close()} methods for the same response body.
 *
 * <p>For synchronous calls, the easiest way to make sure a response body is closed is with a {@code
 * try} block. With this structure the compiler inserts an implicit {@code finally} clause that
 * calls {@code close()} for you.
 *
 * <pre>   {@code
 *
 *   Call call = client.newCall(request);
 *   try (Response response = call.execute()) {
 *     ... // Use the response.
 *   }
 * }</pre>
 *
 * You can use a similar block for asynchronous calls: <pre>   {@code
 *
 *   Call call = client.newCall(request);
 *   call.enqueue(new Callback() {
 *     public void onResponse(Call call, Response response) throws IOException {
 *       try (ResponseBody responseBody = response.body()) {
 *         ... // Use the response.
 *       }
 *     }
 *
 *     public void onFailure(Call call, IOException e) {
 *       ... // Handle the failure.
 *     }
 *   });
 * }</pre>
 *
 * These examples will not work if you're consuming the response body on another thread. In such
 * cases the consuming thread must call {@link #close} when it has finished reading the response
 * body.
 *
 * <h3>The response body can be consumed only once.</h3>
 *
 * <p>This class may be used to stream very large responses. For example, it is possible to use this
 * class to read a response that is larger than the entire memory allocated to the current process.
 * It can even stream a response larger than the total storage on the current device, which is a
 * common requirement for video streaming applications.
 *
 * <p>Because this class does not buffer the full response in memory, the application may not
 * re-read the bytes of the response. Use this one shot to read the entire response into memory with
 * {@link #bytes()} or {@link #string()}. Or stream the response with either {@link #source()},
 * {@link #byteStream()}, or {@link #charStream()}.
 */

雖然有點(diǎn)長楞慈,但是只是說明了兩個(gè)問題幔烛,一是每個(gè)ResponseBody的背后都是一個(gè)socket或者文件資源作為數(shù)據(jù)后備,因此在使用完以后需要關(guān)閉資源囊蓝,不然會造成浪費(fèi)饿悬,二是,ResponseBody應(yīng)該只能使用一次聚霜, 因?yàn)椴荒軐㈨憫?yīng)完全緩存到內(nèi)存中狡恬,因此需要我們通過byte()或者string一次性將響應(yīng)體加載到內(nèi)存使用,或者source()和byteStream()方法以流的方式使用蝎宇,使用完成以后需要關(guān)閉弟劲,不能第二次使用。除此以外我們還需要注意的是,網(wǎng)絡(luò)通信都是字節(jié)流,那么就會涉及到字符編解碼的問題次慢,即charset問題趴生, 這個(gè)問題這里不再介紹值桩,Java的I/O庫對此有處理,Okio同樣也有處理,有興趣的可以自行查看。

Call

在簡單介紹完請求與響應(yīng)之后淡溯,就需要介紹在使用Okhttp過程中經(jīng)常遇到的Call類了,它是對reques的封裝簿训,也是對一次請求過程的封裝咱娶,因?yàn)閺腸all中我們可以獲取響應(yīng),因此可以將其看做是一次請求過程的封裝煎楣,期間涉及到的重定向等問題對于用戶即使用okhttp的程序員來說是透明的豺总,這也是我們喜歡它的原因。同時(shí)在第二篇文章择懂,連接和流的管理中,流的概念還會涉及到call, 到時(shí)還會詳細(xì)介紹另玖,這里首先看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 {

  Request request();

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

這里首先看接口困曙,省去了注釋時(shí)為了代碼長度不會過長,不過還是建議閱讀注釋谦去,從接口方法中可以清楚地看到它是封裝的請求慷丽,可以獲取它所封裝的請求,同時(shí)提供同步和異步的執(zhí)行方法鳄哭, 最后是可以取消或復(fù)制要糊,以及判斷它的狀態(tài)的方法,最后有一個(gè)工廠類妆丘,可以實(shí)例化Call, 實(shí)現(xiàn)工廠接口的類中我們最常用的就是OkHttpClient锄俄, 在Okhttp的使用介紹文章中通常都會重點(diǎn)介紹該類局劲,可以通過它完成參數(shù)配置,后續(xù)我們也會不斷接觸到它奶赠,這里它實(shí)現(xiàn)工廠接口鱼填,就可以實(shí)例化Call對象。下面我們再分析一下在okhttp中Call的唯一實(shí)現(xiàn)類毅戈,RealCall苹丸, 其代碼如下:

final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

  /** The application's original request unadulterated by redirects or auth headers. */
  final Request originalRequest;
  final boolean forWebSocket;

  // Guarded by this.
  private boolean executed;

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

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

  //同步執(zhí)行方法
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
     //僅僅是標(biāo)識該call對象已經(jīng)開始執(zhí)行
      client.dispatcher().executed(this);
      //真正地去執(zhí)行網(wǎng)絡(luò)請求
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      //標(biāo)識該call對象的請求過程結(jié)束
      client.dispatcher().finished(this);
    }
  }

  ...

 //異步執(zhí)行方法
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  ...

這里對Call的代碼做了簡化,我們暫且忽略forWebsocket字段苇经,以及callStackTrace赘理,重點(diǎn)關(guān)注同步以及異步執(zhí)行方法,需要我們注意的是在代碼中添加的注釋部分扇单,即同同步方法網(wǎng)絡(luò)請求是通過getResponseWithInterceptorChain方法獲取的響應(yīng)感憾, 涉及到dispatcher的部分只是設(shè)置標(biāo)志位而已(為了與異步執(zhí)行統(tǒng)一處理,我們將在本篇文章的第二部分介紹dispatcher部分中介紹)令花,同樣異步執(zhí)行也是將執(zhí)行的getResponseWithInterceptorChain方法獲取的網(wǎng)絡(luò)請求阻桅,該方法將在本篇文章的第三部分重點(diǎn)介紹。Call接口的取消操作都交由retryAndFollowUpInterceptor來處理兼都,關(guān)于該攔截器將會在第二篇文章連接和流管理中介紹嫂沉。
下面需要介紹的是在異步方法中的可以加入任務(wù)隊(duì)列的Runnable, AsyncCall, 該類時(shí)RealCall的一個(gè)內(nèi)部類,雖然叫AsyncCall扮碧, 但是是一個(gè)異步執(zhí)行的Runnable趟章,與Call并沒有關(guān)系, 雖然它也有一個(gè)execute方法慎王,但是它其實(shí)是Runnable的run方法蚓土,與Call的execute方法無關(guān)。我在剛開始看代碼的時(shí)候就被此迷惑赖淤,因此還是有必要首先看一下NamedRunnable的代碼蜀漆, AsyncCall實(shí)現(xiàn)了該接口。其代碼為:

/**
 * Runnable implementation which always sets its thread name.
 */
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

這里可以看出來其實(shí)我們這里的類就是一個(gè)Runnable咱旱, 只不過它可以為執(zhí)行的線程命名确丢,同時(shí)將run方法的名稱修改為了execute(), 就是在run方法中調(diào)用execute()吐限, 在Java編程思想一書中鲜侥,作者也提到run這個(gè)名稱不好,應(yīng)該叫做execute()诸典, square公司的工程師應(yīng)該也有相同見解描函,不過為了修改線程名稱,需要在run方法中操作,而將具體的執(zhí)行代碼留給子類來實(shí)現(xiàn)舀寓,也只能需要另外起一個(gè)方法名稱吧胆数。
下面來看AsyncCall的代碼如下:

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    ...

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

從execute方法中可以看出,網(wǎng)絡(luò)請求同樣也是通過getResponseWithInterceptorChain方法獲取的基公, 在獲取到響應(yīng)以后分別處理成功和失敗的情況幅慌,分別調(diào)用回調(diào)的響應(yīng)方法。

通過分析Call的代碼轰豆,我們也就熟悉了okhttp時(shí)如果封裝請求來獲取響應(yīng)的胰伍,而在大部分情況下網(wǎng)絡(luò)請求都是異步執(zhí)行的,對于異步執(zhí)行酸休,任務(wù)就需要管理和調(diào)度骂租,因此我們還需要學(xué)習(xí)一下okhttp中時(shí)如何管理請求任務(wù)隊(duì)列以及如何調(diào)度網(wǎng)絡(luò)請求任務(wù)的。

2. 任務(wù)隊(duì)列的管理與調(diào)度

首先明確這一部分是針對異步執(zhí)行方法來講的斑司。其實(shí)在okhttp的任務(wù)管理和調(diào)度都是通過Dispatcher類來完成的渗饮, 那么我們來想,任務(wù)的管理和調(diào)度問題宿刮,其實(shí)應(yīng)該將AyncCall對象放在一個(gè)隊(duì)列的數(shù)據(jù)結(jié)構(gòu)中互站,由于在多線程的程序中可以同時(shí)執(zhí)行多個(gè)請求,那么我們需要維護(hù)兩個(gè)數(shù)據(jù)結(jié)構(gòu)僵缺,一個(gè)排隊(duì)等候的隊(duì)列胡桃,一個(gè)正在執(zhí)行的任務(wù)集合,另外我們需要一個(gè)線程尺執(zhí)行這些任務(wù)磕潮。在思考完數(shù)據(jù)結(jié)構(gòu)以后翠胰,再去考慮該類需要設(shè)計(jì)的方法,首先我們需要一個(gè)入隊(duì)方法自脯,在入隊(duì)時(shí)要么直接去執(zhí)行要么加入排隊(duì)等候的隊(duì)列之景,其次我們需要一個(gè)喚醒排隊(duì)等候隊(duì)列的方法,即一個(gè)任務(wù)在執(zhí)行結(jié)束后需要調(diào)用的方法膏潮,最后就是在任務(wù)執(zhí)行結(jié)束時(shí)需要設(shè)置標(biāo)識锻狗,方便我們的喚醒方法被調(diào)用,那么這個(gè)方法我們在上一部分已經(jīng)見到了戏罢,即dispatcher.finishde()方法屋谭。在思考完以后,我們來看Dispatcher的代碼:

Dispatcher

/**
 * 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 {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

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

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  ...

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

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

  ...

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
  ...
}

這里對代碼做了簡化龟糕,只留下我們要找的三個(gè)方法(當(dāng)然不只是三個(gè),因?yàn)橛型愋偷闹剌d)悔耘。首先來看數(shù)據(jù)結(jié)構(gòu)讲岁,這里我們需要了解的是,okhttp對想同一個(gè)主機(jī)地址的網(wǎng)絡(luò)請求也做了上限,因此前兩個(gè)字段分別是請求個(gè)數(shù)的上限閾值缓艳,然后dispatcher也負(fù)責(zé)維護(hù)同步請求的狀態(tài)校摩,因此這里也有一個(gè)同步請求的隊(duì)列runningSyncCalls,因?yàn)橥秸埱笠彩切枰獣r(shí)間的阶淘,在一個(gè)時(shí)刻也會有一個(gè)隊(duì)列衙吩。下面就是我們想要找的兩個(gè)隊(duì)列,一直正在執(zhí)行的異步請求隊(duì)列runningAsyncCalls溪窒,一個(gè)是排隊(duì)等候隊(duì)列readyAsyncCalls坤塞,最后是真正工作的線程池executorService。
下面我們來看我們想要看到的三個(gè)方法澈蚌, 第一個(gè)入隊(duì)方法摹芙, enqueue, 邏輯很簡單宛瞄,如我們所想浮禾,如果可以執(zhí)行則直接執(zhí)行,否則排隊(duì)等候份汗,只不過這里的需要考慮的上限不僅有總的請求數(shù)目上限還有對于同一個(gè)主機(jī)的請求上限盈电,那么我們就可以猜測runningCallsForHost方法就是遍歷runningSyncCalls隊(duì)列,找出這個(gè)請求對應(yīng)的主機(jī)上的其他請求上限數(shù)量杯活,有興趣的可以自行查看代碼匆帚。
然后我們再來看第三個(gè)方法,即finished()方法轩猩,標(biāo)識一個(gè)請求任務(wù)的結(jié)束卷扮,當(dāng)然這里三種重載形式,很容易看出promoteCalls字段是標(biāo)識這是否需要喚醒等候隊(duì)列任務(wù)的均践,同步任務(wù)的結(jié)束不需要晤锹,而異步任務(wù)的結(jié)束則需要,這一點(diǎn)顯而易見彤委,接著就是從隊(duì)列中移除任務(wù)鞭铆,并調(diào)用喚醒方法。不過這里需要注意的是如果正在執(zhí)行的任務(wù)數(shù)為0以后會執(zhí)行空閑任務(wù)焦影,在空閑任務(wù)中我們可以自行定義當(dāng)隊(duì)列空閑的時(shí)候需要做的事情车遂,如發(fā)出通知,輸出log等斯辰。
接著就看第二個(gè)方法舶担,即喚醒方法, promoteCalls()彬呻, 邏輯也很簡單衣陶,就是遍歷排隊(duì)等候隊(duì)列柄瑰,然后執(zhí)行最靠前的任務(wù),一直達(dá)到最大的上限剪况。只不過還是需要判斷兩個(gè)上限教沾。

其實(shí)到這里Dispatcher的代碼基本上已經(jīng)分析結(jié)束了,剩余的方法要么是工具方法译断,要么是getter和setter方法授翻。最近嘗試閱讀一些優(yōu)秀的開源代碼,有一點(diǎn)點(diǎn)的心得體會孙咪,不敢說方法論堪唐,算是自己的一點(diǎn)總結(jié)想在這里插入一段與大家分享討論,有不對的地方還希望批評指正该贾。其實(shí)要說也沒有多少深入的道理羔杨,只是在想閱讀代碼的時(shí)候,遇到一個(gè)類杨蛋,它必然有自己的功能或是說自己的使命兜材,而且根據(jù)單一職責(zé)的原則,一個(gè)類只能有一個(gè)原因引起它的改變逞力,那么這里斷章取義曙寡,它也應(yīng)該只有一個(gè)功能或者說相近的一類功能。 那么我們就需要先考慮它所要解決的問題寇荧,在問題明確以后就需要考慮它需要在內(nèi)存中維護(hù)的數(shù)據(jù)結(jié)構(gòu)举庶,因?yàn)閷?shí)現(xiàn)功能必然需要維護(hù)信息,信息就需要一定的數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中保存揩抡,在考慮清楚數(shù)據(jù)結(jié)構(gòu)以后再去思考該類實(shí)現(xiàn)它的功能所需要的步驟户侥,就是將問題分解,要么分情況要么分步驟(高中數(shù)學(xué)中所謂的加法原理和乘法原理)峦嗤,在這些子問題中我們需要設(shè)計(jì)那些方法來解決子問題蕊唐。在做出上述思考以后再去閱讀優(yōu)秀框架的源碼就會發(fā)現(xiàn)簡單很多,當(dāng)我們抽出重點(diǎn)的字段屬性以及方法烁设,剩下的屬性中基本上都是標(biāo)志位替梨,剩下的方法中要么是工具方法(我么暫且這么稱呼,通常是private的装黑, 為重點(diǎn)的方法提供功能服務(wù))要么就是對外提供的getter和setter方法副瀑, 以及對外提供的工具方法。當(dāng)然雖然我這么說恋谭,本人在閱讀代碼的時(shí)候更多的是事后諸葛糠睡,在看完代碼以后再去思考,不過我認(rèn)為這樣也有助于我們更好地理解開源框架設(shè)計(jì)的原理疚颊,所謂的知其然知其所以然铜幽。固然如此滞谢,我還是建議并且以后也會嘗試去事先思考串稀,其實(shí)思考的過程有兩個(gè)難點(diǎn)除抛,一是明確問題,很多時(shí)候我們不明確問題母截,造成迷失在代碼里到忽,同樣寫代碼也是變成代碼堆積,沒有很好的設(shè)計(jì)清寇,因此明確問題才是成敗的關(guān)鍵喘漏,其次時(shí)問題的分割,這一點(diǎn)我們可以很好地從源碼中學(xué)習(xí)到經(jīng)驗(yàn)华烟,他們是如何分解問題翩迈,通過幾個(gè)方法從而實(shí)現(xiàn)對問題的處理,相信多次練習(xí)思考以及學(xué)習(xí)以后自己也可以很好地把握分解問題的尺度盔夜,很好地通過設(shè)計(jì)類的結(jié)構(gòu)以及方法拆解來解決我們的問題负饲。最后給一點(diǎn)小小地建議就是大膽地猜測方法的功能(特別是private的功能方法,它通常是提供一種功能服務(wù))喂链,自頂向下看待代碼返十,而不要糾結(jié)于代碼細(xì)節(jié)。

好了椭微,言歸正傳洞坑,任務(wù)隊(duì)列的管理與調(diào)度其實(shí)就是Dispatcher一個(gè)類來完成,雖然我們只是針對異步任務(wù)來講解蝇率,但是它也負(fù)責(zé)同步任務(wù)的維護(hù)迟杂,如 executed()方法的調(diào)用標(biāo)識任務(wù)的開始,finished()方法的調(diào)用標(biāo)識任務(wù)結(jié)束本慕,具體代碼可以自行查閱排拷。在任務(wù)被調(diào)度執(zhí)行以后,任務(wù)就需要去執(zhí)行了间狂,也就是請求流程的執(zhí)行過程攻泼。本篇文章的最后一部分對此做介紹,這一部分也是很多文章都會重點(diǎn)介紹的interceptor的調(diào)用流程鉴象,在okhttp中所有的功能幾乎都是通過定義interceptor忙菠, 對request和response做操作來實(shí)現(xiàn)的,其實(shí)就也就是請求流程的執(zhí)行過程纺弊。

3. 請求的執(zhí)行流程

Http協(xié)議中牛欢,從request轉(zhuǎn)換為我們所想要的respose會涉及到失敗重試,重定向淆游,緩存處理傍睹,cookie處理等隔盛,所以okhttp設(shè)計(jì)了一系列的interceptor來分別做這些事情,同時(shí)設(shè)計(jì)了一個(gè)Chain的類來串聯(lián)所有的攔截器拾稳,最終完成請求的執(zhí)行流程吮炕。本文的第三部分重點(diǎn)介紹此過程。

在第三部分中访得,我們知道無論是同步請求還是異步請求最后都是通過getResponseWithIntercepters()方法獲取響應(yīng)龙亲,那么我們首先來看RealCall#getResponseWithInterceptorChain()方法的代碼:

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

這里還是省略forWebSocket字段,(關(guān)于websocket悍抑, 我還沒有了解過鳄炉,有興趣的可以自行學(xué)習(xí),這里暫且略過)搜骡。代碼中很明顯就是構(gòu)建一個(gè)攔截器的鏈表拂盯,其中networkIntercepters就是在OkHttpClient中配置的我們自定的攔截器,可以完成我們需要的功能记靡。通過攔截器鏈表構(gòu)建一個(gè)Chain谈竿, 即一個(gè)鏈,完成最終的請求過程簸呈,下面我們首先來看接口Chain和Interceptor的代碼如下:

/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
 Response intercept(Chain chain) throws IOException;

 interface Chain {
   Request request();

   Response proceed(Request request) throws IOException;

   Connection connection();
 }
}

Interceptor只有一個(gè)方法榕订,就是接收一個(gè)chain, 然后返回響應(yīng), 當(dāng)然從這個(gè)chain中我們可以獲取請求蜕便,對請求可以做處理劫恒, 然后調(diào)用chain.proceed()方法獲取響應(yīng),然后再對響應(yīng)做處理就可以將該響應(yīng)返回了轿腺。而Chain的主要方法時(shí)proceed()方法两嘴,它可以理解為一個(gè)接力傳遞,為了描述涉及到兩個(gè)類的遞歸過程族壳,我們首先來看下面這張圖憔辫。

網(wǎng)絡(luò)請求流程

Chain_1為getResponseWithInterceptorChain方法中構(gòu)建的Chain對象,我們調(diào)用它的proceed()方法即可以將request轉(zhuǎn)換為response仿荆, 那么它內(nèi)部做了什么事情呢贰您,在proceed(request)方法中會封裝請求,流拢操,連接等構(gòu)建Chain_2對象锦亦,并將該對象傳遞給Interceptor_A對象,然后調(diào)用Interceptor_A的intercept(Chain Chain_2)方法獲取響應(yīng)令境, 而在Interceptor_A的intecept()方法中可以獲取到Chain_1裝在Chian_2中的request對象杠园,對請求做操作,然后調(diào)用Chain_2的proceed(request)方法舔庶,將它處理完的請求傳遞進(jìn)去供應(yīng)Chain_2構(gòu)建Chain_3抛蚁,這樣Interceptor_B就可以從Chain_3中獲取的請求就是Interceptor_A處理過的請求陈醒,與此同時(shí)Interceptor_A從Chain_2的proceed(request)方法中獲取響應(yīng), 至于Chain_B中的操作則與Chain_A相同瞧甩,依次遞歸調(diào)用钉跷。但注意到最后Chain_3會構(gòu)建一個(gè)Chain_4(圖中未畫出, 它含有的請求應(yīng)該就是Interceptor_B處理后的請求)亲配, 這樣Interceptor_C就可以從Chain_4中獲取請求尘应,然后從中獲取連接上的流,進(jìn)而執(zhí)行對socket的讀寫操作吼虎, 而不會再調(diào)用Chain_4的proceed()方法, 返回的響應(yīng)則會逐級的向前傳遞苍鲜。
這一段可能有些繞思灰,如果看不懂可以先去查看其他文章中 的介紹,不過有些文章中將Chain看做一個(gè)鏈混滔,然后每個(gè)攔截器都是從鏈上獲取請求洒疚,然后加工以后再將響應(yīng)放置到上面,逐級的操作坯屿,從Chain這個(gè)名字上看確實(shí)如此油湖,可能這是從更高的抽象角度可以如此看待,不過在看源碼的過程中领跛,Chain的proceed()方法中確實(shí)是不斷地創(chuàng)建新的chain對象并傳遞到攔截器的intercept()方法中乏德。由于水平有限,只能這么講吠昭,圖畫的也比較粗糙喊括,下面從源碼的角度再去理解這個(gè)過程,然后回過頭來再來理解這個(gè)過程或許會好一些矢棚。
首先來看Chain的唯一實(shí)現(xiàn)類RealInterceptorChain的代碼郑什。同樣地我們來分析它的數(shù)據(jù)結(jié)構(gòu),它是在遞歸的過程中傳遞請求的蒲肋,而響應(yīng)則是在遞歸方法中則逐級已經(jīng)返回蘑拯,所以該類應(yīng)該有一個(gè)Request, Chain的第二個(gè)功能時(shí)串聯(lián)各個(gè)攔截器,那么它也 應(yīng)該有一個(gè)攔截器的鏈表兜粘,然后分析它的方法應(yīng)該很簡單申窘,我們所能想到的就是proceed(request)方法,在該方法中應(yīng)該會使用傳遞進(jìn)來的請求對象封裝新的Chain對象妹沙,并調(diào)用下一個(gè)攔截器的intercept(Chain)方法偶洋, 這里說下一個(gè)攔截器,那么就需要一個(gè)標(biāo)志位距糖,標(biāo)識下一個(gè)攔截器在攔截器鏈表中的位置玄窝。下面來看代碼:

/**
 * A concrete interceptor chain that carries the entire interceptor chain: all application
 * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
 */
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

  ...
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

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

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

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

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
...
}

代碼中省略了部分的方法牵寺, 這里字段屬性中我們先不去關(guān)注StreamAllocation, HttpCodec和Connection, 在下一篇文章中將會重點(diǎn)介紹這三個(gè)類, 他們分別代表一次請求的流的分配恩脂, 流帽氓, 連接, 這里我們只需要清楚這些屬性是有各個(gè)攔截器逐漸設(shè)置到Chain上的俩块, 最后一個(gè)攔截器可以獲取到他們?nèi)缓筮M(jìn)而執(zhí)行網(wǎng)絡(luò)請求黎休,即對socket執(zhí)行讀寫操作就可以了, 然后數(shù)值call是用來監(jiān)督一個(gè)攔截器中的intercept()方法中執(zhí)行chain.proceed()方法只能執(zhí)行一次玉凯,當(dāng)然有個(gè)例外就是重試攔截器势腮,至于具體邏輯,我們后面來看漫仆。

接著就是Chain的重要方法捎拯, proceed(request)方法, 我們先忽略前面的條件判斷盲厌,直接來看下面兩句

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

就如之前的流程中所述署照, 首先使用傳遞進(jìn)來的處理過的request(其實(shí)不只是請求,還包括處理過的StreamAllocation, HttpCodec, 就是流吗浩, Connection)構(gòu)建一個(gè)新的Chain對象建芙, 調(diào)用下一個(gè)攔截器的intercept(Chain)方法。 然后此方法中三個(gè)判斷條件懂扼,這里暫不分析禁荸,它們跟流的分配有關(guān),下一篇文章中在學(xué)習(xí)完StreamAllocation, HttpCodec以及Connection三個(gè)概念以后在來看這三個(gè)判斷條件的具體含義微王。對于Interceptor的intercept()方法屡限,由于有諸多的攔截器,完成不同任務(wù)炕倘,在后續(xù)的分析中將逐一介紹钧大,這里先說明它們通用的流程,當(dāng)然最后一個(gè)CallServerInterceptor除外罩旋,我們自定義的NetworkInterceptor也應(yīng)該遵守這樣的流程啊央,即從chain中獲取request, 有的還可以獲取StreamAllocation, HttpCodec以及Connection等, 然后對他們做操作涨醋,然后調(diào)用chain(request)方法獲取響應(yīng)瓜饥,然后對響應(yīng)做處理后返回。
RealInterceptorChain的代碼中就只剩下工具方法和getter方法了浴骂, 此時(shí)再嘗試回過頭看剛才的流程乓土,是否可以更明晰一些。

至此,我們就了解了okhttp中整個(gè)網(wǎng)絡(luò)請求過程包括請求的封裝趣苏,Call對請求的封裝狡相, 任務(wù)的隊(duì)列管理和調(diào)度,任務(wù)的執(zhí)行過程食磕,攔截器和攔截器鏈的配合遞歸調(diào)用尽棕,最后一個(gè)過程有點(diǎn)繞之外,整個(gè)過程很明晰也比較容易理解彬伦,可以在代碼之上學(xué)習(xí)優(yōu)秀的開源框架在過程和功能的劃分和分解上是如何做出處理的滔悉。后面則是以getResponseWithInterceptors()方法中加入的攔截器為主線,依次介紹連接和流管理单绑, 緩存管理以及Cookie的持久化等模塊回官,后續(xù)有時(shí)間還會再學(xué)習(xí)okhttp對https, SPDY询张, Http2的支持孙乖,現(xiàn)在的分析中先忽略到對它們支持的部分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末份氧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弯屈,更是在濱河造成了極大的恐慌蜗帜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件资厉,死亡現(xiàn)場離奇詭異厅缺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宴偿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門湘捎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窄刘,你說我怎么就攤上這事窥妇。” “怎么了娩践?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵活翩,是天一觀的道長。 經(jīng)常有香客問我翻伺,道長材泄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任吨岭,我火速辦了婚禮拉宗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己旦事,他們只是感情好魁巩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著族檬,像睡著了一般歪赢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上单料,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天埋凯,我揣著相機(jī)與錄音,去河邊找鬼扫尖。 笑死白对,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的换怖。 我是一名探鬼主播甩恼,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沉颂!你這毒婦竟也來了条摸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤铸屉,失蹤者是張志新(化名)和其女友劉穎钉蒲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彻坛,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷啼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昌屉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钙蒙。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖间驮,靈堂內(nèi)的尸體忽然破棺而出躬厌,到底是詐尸還是另有隱情,我是刑警寧澤蜻牢,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布烤咧,位于F島的核電站,受9級特大地震影響抢呆,放射性物質(zhì)發(fā)生泄漏煮嫌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一抱虐、第九天 我趴在偏房一處隱蔽的房頂上張望昌阿。 院中可真熱鬧,春花似錦、人聲如沸懦冰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刷钢。三九已至笋颤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間内地,已是汗流浹背伴澄。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阱缓,地道東北人非凌。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像荆针,于是被迫代替她去往敵國和親敞嗡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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