OkHttp源碼(一)

前幾天在在一本書上看到這樣一句話

如果僅從微觀的視角關注每一個單獨的點,可能會因為看不到整體而迷失方向。

所以不會每一行代碼都去很細致地分析。因為抓住了整體再去看細節會比較輕松,而最初就一味追求細致可能會在不知不覺中放棄探索吧。

OkHttp

這篇的分析過程中忽略了InterceptorChain攔截器鏈的部分,在介紹完大概流程后單獨拿出來分析,下一篇再講。

OkHttp基本使用

來看源碼的話,OkHttp用起來應該已經很熟練了,我們從最基礎開始看它是如何實現的,直接上基本用法的代碼。

public class OkHttpTest {

    private OkHttpClient mOkHttpClient;
    
    public OkHttpTest() {
        mOkHttpClient = new OkHttpClient();
        mOkHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.MINUTES).build();

    }

    public void synRequest() {
        Request newRequest = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();
        Call call = mOkHttpClient.newCall(newRequest);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void asynRequest() {
        Request newRequest = new Request.Builder()
                .url("www.baidu.com")
                .get()
                .build();
        Call call = mOkHttpClient.newCall(newRequest);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("onFailure");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("onSuccess");
                System.out.println(response.body().string());
            }
        });
    }

}

主要分為以下五個步驟:

  • 構建OkHttpClient
  • 構建Request
  • 構建Call
  • 執行call的execute()發送同步請求或enqueue()發送異步請求
  • 對execute()返回的Response處理或enqueue()回調中的Response處理

以上為OkHttp基本使用方法,同步發送請求和異步發送請求的過程。

OkHttpClient的創建流程

OkHttpClient構造器

可以通過以下兩種方式創建OkHttpClient

mOkHttpClient = new OkHttpClient();
mOkHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.MINUTES).build();

進入OkHttpClient()中會發現無參數構造方法調用了一個參數的構造方法,并傳入了一個Builder對象。

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

這里傳入一個Builder對象給OkHttpClient一個參數的構造器。

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    //...

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    
    //...

    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

一個參數的構造器主要利用傳入的Builder對象初始了OkHttpClient的成員,并做了其他的一些初始工作。
那么我們先來看一下Builder到底是什么。

OkHttpClient的內部類Builder

這里的Builder是OkHttp的一個靜態內部類。

public static final class Builder {
    //成員變量
    Dispatcher dispatcher;
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    //...
    
    //對成員變量賦初始值
    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      //...
    }
    
    //使用參數okHttpClient來對成員變量賦值
    Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      this.protocols = okHttpClient.protocols;
      this.connectionSpecs = okHttpClient.connectionSpecs;
      this.interceptors.addAll(okHttpClient.interceptors);
      this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
      //...
    }
    
    //下面是對成員變量參數的設置的方法
    public Builder connectTimeout(long timeout, TimeUnit unit) {
      connectTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }
    
    public Builder readTimeout(long timeout, TimeUnit unit) {
      readTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }
    
    //...很多方法
    
    //將此Builder對象傳入OkHttpClient構造器
    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
}

可以看出來Builder對象實際存儲了很多參數變量,構造器對其初始化,內部提供改變參數的方法,方法返回本身使其可以鏈式調用

mOkHttpClient = new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES)
.build()

最后調用build()將此包含處理過的參數的Builder對象傳給OkHttpClient的構造器,構造器中將此Builder對象給OkHttpClient成員變量賦值。

上述Builder內部類其實用到了一種設計模式,構建者模式。通過分析其代碼不難發現Builder的主要工作就是封裝了外部類需要的參數,并提供了一種比較方便的鏈式調用的方法去初始化一個類的成員變量,最后傳給外部類完成初始化得到外部對象。

創建Request對象

Request newRequest = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();

Request也是使用了Builder模式。

public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}

Builder(Request request) {
  this.url = request.url;
  this.method = request.method;
  this.body = request.body;
  this.tag = request.tag;
  this.headers = request.headers.newBuilder();
}

可以看到默認的Request為Get請求。和OkHttpClient的方式很類似,就不多說了。

Call的創建

Call call = mOkHttpClient.newCall(newRequest);

將創建好的Request傳入client的newCall方法返回一個Call對象。

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

OkHttpClient實現了Call接口,newCall()是Call接口內部的Factory接口的抽象方法。
newCall調用的是Call實現類RealCall的newRealCall(),點進去看看,是個靜態方法。

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

構造器私有了,三個參數分別是傳入的client,傳入的原始request和一個是否為WebSocket的flag。

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

注意這里的retryAndFollowUpInterceptor,這個是之后攔截器鏈的第一個攔截器,之后會介紹到。

RealCall的execute()

Response response = call.execute();

真正的實現仍然是在RealCall中,來看RealCall的execute()。

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

我們來一點一點分析

首先判斷了此call對象是否被執行過,如果執行過就拋出異常,否則設置executed=true,繼續下面操作。這里synchronized包裹,表明call對象一定只能執行一次。

synchronized (this) {
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
}

dispatcher()只是返回了client在Builder中初始的dispatcher成員變量,所以來看dispatcher的executed()。這里只做了一項工作,就是將call加入runningSyncCalls隊列中,這里留一個印象,只需要知道是將call加入了同步請求隊列中就可以了,在后面會細細來講Dispatcher類的,因為它還是比較重要的。

client.dispatcher().executed(this);
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

之前都一直是和request,到這里終于出現了reponse,也可以才想到,整個請求流程到這里應該是真正拿到了響應,是攔截器鏈的開端。

Response result = getResponseWithInterceptorChain();

來看getResponseWithInterceptorChain()的實現,將client中的攔截器和默認的攔截器加入集合中,將其傳入創建了一個RealInterceptorChain攔截器鏈chain。最后返回了chain的proceed()的返回值,暫時只需要知道它傳入了原始的request,返回了response,具體實現是在RealInterceptorChain這個類中的,我們在專門講攔截器時再去研究。

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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
}

回到RealCall的execute()中來,在最后的finally中有這樣一行代碼,必須執行dispatcher的finished(),又調用了三個參數的finshed(),再一次看到runningSyncCalls這個隊列。記得在開始時我們將call加入到了這個隊列中,現在已經請求完畢,猜想應該要從隊列中刪除這個call對象。

client.dispatcher().finished(this);
void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
}

果然在同步代碼塊中就有移除call的指令。其他操作是關于dispatcher的,我們在后面再去講。

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

到一個GET請求就完成了。

RealCall的equeue()

發送一個異步請求其他步驟都是一樣的,唯獨這一步不一樣,我們從RealCall的equeue()開始分析。

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

首先也是判斷是否執行過,之后調用了dispatcher的equeue(),不同的是這里有一個參數,將傳入的responseCallback這個回調接口封裝成了AsyncCall對象。

AsyncCall是RealCall的一個靜態內部類,繼承了NamedRunnable,NamedRunnable實現了Runnable。來看NamedRunnable的run(),調用了execute(),但是并沒有實現,它在RealCall中的AsyncCall中實現了,看類聲明final class AsyncCall extends NamedRunnable就確定了。

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

來看AsyncCall的execute(),有沒有一點似曾相識的感覺。和RealCall的executed()一樣也調用了getResponseWithInterceptorChain()獲得response,之后進行一些回調操作。最后同樣調用finished(),將其移除隊列。那么問題來了,Call是在哪里加入隊列的?又是如何實現異步的呢?

@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 {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}

我們回到client.dispatcher().enqueue(new AsyncCall(responseCallback));這句話來看看dispatcher的equeue()都做了哪些工作,又為何能夠實現異步。

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

做了一個判斷將call加入runningAsyncCalls還是readyAsyncCalls。同步請求中只有的一個隊列,而這里有兩個,一個為等待隊列一個為執行隊列,正是這兩個隊列實現了異步。在滿足條件后加入到執行隊列的下一行代碼可以理解為線程池直接執行了這個call請求,不滿足條件則加入等待隊列等待調度。

還記得這個call請求嗎,它是AsyncCalld對象,最終會調用上面分析過的實現了NamedRunnable的executed()真正地發送請求。

Dispatcher類

通過上面對同步異步發送請求的分析,可以感覺到實現這兩者區別的重點是由Dispatcher來實現的,Dispatcher的代碼不多,主要是這樣的流程:

dispatcher

我們來分析代碼。

變量

  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable 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<>();

Dspatcher中有這些成員,maxRequests是正在執行的異步請求個數的最大值,maxRequestsPerHost是每個Host正在請求的請求個數最大值。executorService是執行請求的線程池,之后三個隊列前兩個用于異步請求的準備隊列和執行隊列,最后一個是同步請求的執行隊列。

enqueue()

先從我們熟悉的開始。

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

enqueue()在上面我們已經簡單的分析過一次了,是從realCalld的enqueue()調用的。這里的判斷條件就涉及到maxRequests和maxRequestsPerHost了。

runningAsyncCalls.size() < maxRequests,控制了執行隊列中請求個數。

runningCallsForHost(call) < maxRequestsPerHost控制Host的值,來看runningCallsForHost()代碼

  /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

遍歷了執行隊列中所有的call,計算出除了webSocket以外所有與傳入參數call同一個Host的call的個數,和我們上面分析的一致。

executorService().execute(call);滿足條件加入隊列后的執行操作。來看executorService()

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

單例模式返回了一個ThreadPoolExecutor對象,是一個線程池。

public ThreadPoolExecutor(
    int corePoolSize, 
    int maximumPoolSize, 
    long keepAliveTime, 
    TimeUnit unit, 
    BlockingQueue<Runnable> workQueue, 
    ThreadFactory threadFactory
)

參數非常多:

  • corePoolSize: 核心線程數,默認情況下核心線程會一直存活。
  • maximumPoolSize: 線程池所能容納的最大線程數。超過這個數的線程將被阻塞。
  • keepAliveTime: 非核心線程的閑置超時時間,超過這個時間就會被回收。
  • unit: keepAliveTime的單位。
  • workQueue: 線程池中的任務隊列。
  • threadFactory: 線程工廠,提供創建新線程的功能。

corePoolSize設置為0表示一旦有閑置的線程就可以回收。容納最大線程數設置的非常大,但是由于受到maxRequests的影響,并不會創建特別多的線程。60秒的閑置時間。

finished()

三個重載,前面兩個都是調用第三個private,記不記得前面兩個都是在什么地方調用的?看參數和注釋應該也能記起來,第一個是異步最后一步調用,第二個是在同步最后一步調用。它們調用第三個finished()區別就在于第三個參數異步為true,同步為false。

  /** 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();
    }
  }

來分析三個參數的finished(),同步代碼塊從隊列中移除了此時已經完成的這個call,接著判斷如果第三個參數為true就執行promoteCalls(),來看promoteCalls()。

  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.
    }
  }

promoteCalls()起到一個調控的作用,來控制readyAsyncCalls中的call進入runningAsyncCalls的。首先如果runningAsyncCalls滿了或者readyAsyncCalls中沒有call了就不做任何操作直接返回。之后遍歷readyAsyncCalls中的call,如果call的host滿足maxRequestsPerHost限制,就將其從maxRequestsPerHost中移除,加入runningAsyncCalls并立即執行,繼續循環遍歷直到將runningAsyncCalls加滿。異步比同步會多一個調控的步驟promoteCalls()。

回到finished()代碼中來,下一步是計算runningCallsCount,是同步異步正在請求的call的總數。

  public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

之后將成員遍歷idleCallback賦值給局部變量,idleCallback是空閑回調,當正在請求總數為0并且idleCallback不為空就調用其run()。

至此,Dispatcher類中的大部分核心就都涉及到了,還會有小部分代碼在后面的分析中再提。

總結

最后來總結一下以上分析到的大致流程。

  1. 首先使用Builder模式創建并初始化了OkHttpClient和Request對象。
  2. 傳入request調用client的newCall()創建了一個RealCall對象,實際操作都是由它完成的。
  3. 如果是同步請求則調用call的executed():
    1. 判斷此call對象是否執行過,未執行過再繼續。
    2. 調用dispatcher的executed(),將call加入同步執行隊列。
    3. 調用getResponseWithInterceptorChain()初始化攔截器集合并生成攔截器鏈。
    4. 調用攔截器鏈的proceed()依次處理request并依次處理response最終返回。
    5. 調用dispatcher的finished()將call移除隊列。
  4. 如果是異步請求則調用call的enqueue():
    1. 判斷此call對象是否執行過,未執行過再繼續。
    2. 將傳入的callBack封裝進AsyncCall對象,它實際為一個Runnable,實現了run方法的一部分。
    3. 調用dispatcher的enqueue并將asyncCall對象傳入。
    4. 判斷asyncCall加入執行隊列還是等待隊列。
    5. 如果滿足條件加入了等待序列則從線程池中分配線程立即執行asyncCall。
    6. 調用asyncCall的executed()。
    7. 調用getResponseWithInterceptorChain()初始化攔截器集合并生成攔截器鏈。
    8. 調用攔截器鏈的proceed()依次處理request并依次處理response最終返回。
    9. 根據結果,調用asyncCall中callBack的回調方法。
    10. 調用dispatcher的finished()將此call移除running隊列,并對隊列重新調整分配。
  5. 對響應結果進行處理。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375