OkHttp3-使用進階(Recipes)

轉載請注明出處 http://www.reibang.com/p/25e89116847c (作者:韓棟)
本文為譯文沈善,由于譯者水平有限,歡迎拍磚杠人,讀者也可以閱讀原文
OkHttp3-基本用法填帽,OkHttp3-使用進階(Recipes),OkHttp3-請求器(Calls)唧躲,OkHttp3-連接(Connections)造挽,OkHttp3-攔截器(Interceptor)


我們寫了一些例子用來演示如何解決在OkHttp遇到的常見問題。通過這些例子去學習關于OkHttp中的組件是如何一起工作的弄痹》谷耄可以隨意復制粘貼所需要的代碼。

Synchronous Get(Get方式同步請求)

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
  }

這個例子我們演示了一個下載文件并且以字符串的形式打印出它的響應頭和響應主體的例子肛真。

這個response.body().string()中的string()方法在對于小文檔來說是非常方便和高效的谐丢。但是如果這個響應主體比較大(超過1MiB),那么應該避免使用string()方法蚓让,因為它將會把整個文檔一次性加載進內存中(容易造成oom)乾忱。在這種情況下,建議將這個響應主體作為數(shù)據(jù)流來處理历极。

Asynchronous Get(Get方式異步請求)

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
  }

在這個例子中窄瘟,我們在子線程中下載一個文件,并且在對應的回調方法在主線程中讀取響應执解。注意:讀取大量的響應主體可能會堵塞主線程寞肖。OkHttp當前不提供異步Api用來分段獲取響應主體。

Accessing Headers(訪問頭部信息)

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
  }

通常Http的以一個Map<String, String>數(shù)據(jù)結構來存儲Http頭部信息:每一個Key所對應的Value是可空的衰腌,但是有一些字段是允許存在多個的 (可以使用Multimap新蟆,簡單說下,MultimapMap最大的不同就是前者的Key可以重復)右蕊。比如琼稻,一個Http響應提供了多個合法并且常用的Key為Vary響應頭信息。那么OkHttp的Api將會生成多個合適的方案饶囚。(Vary 字段用于列出一個響應字段列表帕翻,告訴緩存服務器遇到同一個 URL 對應著不同版本文檔的情況時鸠补,如何緩存和篩選合適的版本。)

為請求添加請求頭字段有兩種方式嘀掸,header(name, value)addHeader(name, value)紫岩。唯一不同的是前者會覆蓋掉原有的字段(如果原來存在此字段),后者則是在原來的字段信息進行添加睬塌,不會覆蓋泉蝌。

讀取響應頭信息也有兩種方式,header(name)headers(name)揩晴。前者只會返回對應的字段最后一次出現(xiàn)的值勋陪,后者則是將對應的字段所有的值返回。當然硫兰,值是允許為null的诅愚。

如果你想得到所有的頭部信息,使用Headers類是個很好的主意劫映。它支持通過索引來獲取頭部信息违孝。

Post a String(上傳一個字符串)

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

上面的例子使用一個Post的請求方式,將一個字符串放在HTML的格式的文本中上傳到服務器苏研。這種方式的上傳數(shù)據(jù)方式是將整個請求體一次性放入內存中等浊,所以當所需上傳的數(shù)據(jù)大小超過1Mib時,應當避免使用這種方式上傳數(shù)據(jù)摹蘑,因為會對程序的性能損害,甚至oom轧飞。當上傳的數(shù)據(jù)大小超過此值時衅鹿,可以以數(shù)據(jù)流的形式上傳。

Post Streaming(以數(shù)據(jù)流的形式上傳)

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

這個例子展示了通過數(shù)據(jù)流的方式上傳一個和上個例子同樣的字符串过咬。如果你是以依賴的方式使用OkHttp這個庫大渤,那么就無需再手動為它添加Okio庫了。因為OkHttp默認依賴于Okio掸绞。Okio為OkHttp提供了一個可控大小的緩存池泵三,這樣我們就不用擔心因為上傳大數(shù)據(jù)而會出現(xiàn)的問題。如果你更傾向使用OutputStream衔掸,你可以通過BufferedSink.outputStream()獲取到OutputStream對象烫幕。

Posting a File(上傳一個文件)

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

這個例子很簡單展示了如何將一個文件上傳至服務器。

Posting form parameters(上傳表單參數(shù))

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

使用 FormBody.Builder去構建一個像HTML<form>標簽一樣的請求體群叶。

Posting a multipart request(上傳攜帶有多種表單數(shù)據(jù)的主體)

  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

通過MultipartBody.Builder可以創(chuàng)建擁有復雜的請求主體的請求恩商,比如有多種需要上傳的數(shù)據(jù)格式的表單數(shù)據(jù)哄尔。它們每一種表單數(shù)據(jù)都是一個請求主體,你可以為它們分別定義屬于每個請求主體的請求頭信息捷犹,比如Content-Disposition弛饭,并且OkHttp會自動為它們添加Content-LengthContent-Type請求頭。

Parse a JSON Reponse With Gson(用Gson來解析Json數(shù)據(jù))

 private final OkHttpClient client = new OkHttpClient();
  private final Gson gson = new Gson();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/gists/c2a7c39532239ff261be")
        .build();
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
      System.out.println(entry.getKey());
      System.out.println(entry.getValue().content);
    }
  }

  static class Gist {
    Map<String, GistFile> files;
  }

  static class GistFile {
    String content;
  }

Gson是一種可將Json數(shù)據(jù)序列化成Java對象萍歉,或者將Java對象反序列化為Json數(shù)據(jù)侣颂。這個例子中我們將從GitHub Api返回響應的Json數(shù)據(jù)序列化為Gist.class對象。
  需要注意的是枪孩,我們這里使用的ResponseBody.charStream()使用的是響應頭中Content-Type的字段為編碼格式憔晒,如果此響應頭中沒有對應的字段,那么默認為UTF-8編碼销凑。

Response Caching(響應緩存)

 private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient.Builder()
        .cache(cache)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

    String response1Body = response1.body().string();
    System.out.println("Response 1 response:          " + response1);
    System.out.println("Response 1 cache response:    " + response1.cacheResponse());
    System.out.println("Response 1 network response:  " + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

    String response2Body = response2.body().string();
    System.out.println("Response 2 response:          " + response2);
    System.out.println("Response 2 cache response:    " + response2.cacheResponse());
    System.out.println("Response 2 network response:  " + response2.networkResponse());

    System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
  }

為了緩存響應丛晌,你必須先有一個可以讀寫以及大小確定的緩存目錄。這個緩存目錄應該是私有的斗幼,并且通常情況下其他程序是無法對這個目錄進行讀取的澎蛛。

多個緩存無法同時訪問一個相同的緩存目錄。否則可能會造成響應數(shù)據(jù)發(fā)生錯誤蜕窿,甚至程序Crash谋逻。為了避免這種情況,我們一般在程序中只調用new OkHttpClient()創(chuàng)建OkHttpClient一次桐经,并且對它進行緩存配置毁兆,并且在全局中使用這個實例(我們可以通過單例模式來創(chuàng)建它,不過很多人開發(fā)Android的人喜歡在Application中創(chuàng)建它)阴挣。

OkHttp會根據(jù)響應頭的配置配置信息對響應數(shù)據(jù)進行緩存气堕。服務器會在返回的響應數(shù)據(jù)中配置這個響應數(shù)據(jù)在你的程序中應該被緩存多久,比如Cache-Control: max-age=9600畔咧,它緩存配置時間為9600秒茎芭,9600秒后它將會過期。但是我們是否可以自定義緩存時間呢誓沸,答案是可以的梅桩。我們可以在請求頭中添加緩存配置,比如Cache-Control: max-stale=3600拜隧,當服務器返回響應時宿百,OkHttp會使用此配置進行緩存。

There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.這句話不知道怎么翻譯洪添。垦页。求讀者指教。薇组。囧外臂。。
  
  通常在存在請求的響應緩存并且緩存數(shù)據(jù)沒有過期的情況下,那么當你再次發(fā)送這個請求時宋光,OkHttp并不會去服務器上獲取數(shù)據(jù)貌矿,而是直接在本地緩存目錄中取得數(shù)據(jù)返回給你。當然罪佳,如果你想避免從緩存中獲取數(shù)據(jù)逛漫,那么你可以在構建Request的時候使用cacheControl()方法以CacheControl.FORCE_NETWORK為參數(shù)進行配置∽秆蓿或者當緩存過期酌毡,但你還是不想去服務器請求,而是再次使用緩存蕾管。你也可以配置為CacheControl.FORCE_CACHE枷踏。注意:如果此時本地緩存中并沒有緩存數(shù)據(jù),或者因為其他原因(不包括緩存過期)而必須去服務器請求掰曾,那么OkHttp將會返回一個504 Unsatisfiable Request的響應旭蠕。

Canceling a Call(取消一個請求器)

 private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
        call.cancel();
        System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    try {
      System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
      Response response = call.execute();
      System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf("%.2f Call failed as expected: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, e);
    }
  }

使用Call.cancel()方法將會立即停止此Call正在運行中的網(wǎng)絡工作(為什么是工作呢,因為此時Call可能在發(fā)送請求旷坦,或者讀取響應等)掏熬,此時它可能會拋出一個IOException異常。在適當?shù)臅r候取消不再需要的請求有利于我們減少程序的工作以及流量的損耗秒梅。比如在Android開發(fā)中旗芬,用戶點擊了返回鍵離開了這個頁面(假如這個Activity或者Fragment被銷毀),那么我們就可以在相應的回調(一般在onDestroy())中取消掉在這個頁面所有的Call的網(wǎng)絡工作捆蜀,包括異步和同步的都可以取消疮丛。

Timeouts(超時)

private final OkHttpClient client;

  public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    Response response = client.newCall(request).execute();
    System.out.println("Response completed: " + response);
  }

OkHttp支持配置三種超時情況,分別是連接超時辆它、讀取超時以及寫入超時这刷。當發(fā)生超時情況時,OkHttp將會調用Call.cancel()來取消掉此Call的所有網(wǎng)絡工作娩井。

Per-call Configuration(為個別的Call添加特別的配置)

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
        .build();

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(500, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println("Response 1 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 1 failed: " + e);
    }

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(3000, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println("Response 2 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 2 failed: " + e);
    }
  }

在前面我們說過最好以單例模式來創(chuàng)建一個OkHttpClient實例,你可以在這個單例中進行比如代理設置似袁、超時以及緩存等配置洞辣,以后當我們需要的時候直接獲取這個單例來使用,達到一種全局配置的效果£夹疲現(xiàn)在存在一種需求情況扬霜,有一個特殊的Call請求的配置需要發(fā)生一些改變,我們首先可以通過OkHttpClient.newBuilder()的方法來復制一個和原來全局單例相同的OkHttpClient對象而涉,注意著瓶!是復制,也就是說這個新的復制對象會和原來的單例共享同一個連接池啼县,調度器材原,以及相同的配置信息沸久。然后再進行對新的復制對象進行自定義的配置,最后讓這個特殊的Call使用余蟹。

在這個例子中卷胯,我們client.newBuilder()復制了一個新的OkHttpClient對象,并且將它的讀取超時時間重新設置了為500秒威酒。

Handing authentication(配置認證信息)

 private final OkHttpClient client;

  public Authenticate() {
    client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
          }
        })
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

當我們向一個需要進行身份認證的服務器發(fā)送請求時(假設此時我們尚未配置身份認證信息)窑睁,服務器就會返回一個401 Not Authorized的響應。它意味著我們需要進行身份認證才可以獲取到想要的數(shù)據(jù)葵孤。那么我們如何進行配置呢担钮。其實當OkHttp獲取到401 Not Authorized的響應時,OkHttp會向Authenticator對象獲取證書尤仍。Authenticator是一個接口箫津,在這個例子中,我們通過向authenticator()方法添加了一個Authenticator對象為參數(shù)吓著。在匿名內部類的實現(xiàn)中我們可以看到鲤嫡,authenticate()方法返回了一個設置了header("Authorization", credential)的請求頭的新的Request對象,這個請求頭中的credential就是身份驗證信息绑莺。OkHttp使用這個Request自動幫我們再次發(fā)送請求暖眼。如果我們沒有添加身份認證信息配置,那么OkHttp會自動中斷此次請求纺裁,不會再次幫我們重新發(fā)送請求诫肠。

當服務器返回401 Not Authorized的響應時,我們可以通過Response.challenges()方法獲取所需要的認證信息要求信息欺缘。如果只是簡單需要賬號和密碼時候栋豫,我們可以使用Credentials.basic(username, password)對請求頭進行編碼。

為了避免當你提供的身份驗證信息錯誤使服務器一直返回401 Not Authorized的響應而導致程序陷入死循環(huán)(無限地重試)谚殊,你可以在Authenticator接口的實現(xiàn)方法authenticate()中返回null來告訴OkHttp放棄重新請求丧鸯。

if (credential.equals(response.request().header("Authorization"))) {
    return null; // If we already failed with these credentials, don't retry.
   }

你也可以自定義重試的次數(shù),在超過次數(shù)之后放棄重新請求嫩絮。

if (responseCount(response) >= 3) {
    return null; // If we've failed 3 times, give up.
  }

private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末丛肢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剿干,更是在濱河造成了極大的恐慌蜂怎,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件置尔,死亡現(xiàn)場離奇詭異杠步,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門幽歼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朵锣,“玉大人,你說我怎么就攤上這事试躏≈碛拢” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵颠蕴,是天一觀的道長泣刹。 經(jīng)常有香客問我,道長犀被,這世上最難降的妖魔是什么椅您? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮寡键,結果婚禮上掀泳,老公的妹妹穿的比我還像新娘。我一直安慰自己西轩,他們只是感情好员舵,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藕畔,像睡著了一般马僻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上注服,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天韭邓,我揣著相機與錄音,去河邊找鬼溶弟。 笑死女淑,一個胖子當著我的面吹牛,可吹牛的內容都是我干的辜御。 我是一名探鬼主播鸭你,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼擒权!你這毒婦竟也來了苇本?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤菜拓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笛厦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纳鼎,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贱鄙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劝贸。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逗宁,靈堂內的尸體忽然破棺而出映九,到底是詐尸還是另有隱情,我是刑警寧澤瞎颗,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布件甥,位于F島的核電站,受9級特大地震影響哼拔,放射性物質發(fā)生泄漏引有。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一倦逐、第九天 我趴在偏房一處隱蔽的房頂上張望譬正。 院中可真熱鬧,春花似錦檬姥、人聲如沸曾我。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抒巢。三九已至,卻和暖如春荞雏,著一層夾襖步出監(jiān)牢的瞬間虐秦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工凤优, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悦陋,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓筑辨,卻偏偏與公主長得像俺驶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子棍辕,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內容