安卓網(wǎng)絡(luò)請求最佳實踐

安卓網(wǎng)絡(luò)請求最佳實踐

在安卓開發(fā)中栏笆,經(jīng)過多年的發(fā)展森渐,網(wǎng)絡(luò)請求的架構(gòu)基本定型做入,通常是 OkHttp + Retrofit + RxJava,盡管在目前在 google 的推動下同衣,有些開發(fā)者正在使用 Kotlin協(xié)程 代替 RxJava母蛛。但是也僅是一線程調(diào)度的一部分,本文依舊可以在網(wǎng)絡(luò)請求部分給出很好的建議乳怎。

返回錯誤碼

網(wǎng)絡(luò)請求可能發(fā)生各種各樣的錯誤彩郊,當錯誤發(fā)生時前弯,Http 會通過響應頭返回 狀態(tài)碼原因短語 來標識錯誤狀態(tài)和原因。

Http status code

自從 RxJava 2.0 普及之后秫逝,一些項目中的錯誤碼返回開始出現(xiàn)了不太好的方式 —— 在 Http body 中自定義響應碼恕出。

{
    "code": 200,
    "msg": "",
    "datas": ...
}

處于什么樣的考慮很難一一找到原始開發(fā)人員進行解答,從項目中代碼的使用可以推斷违帆,很可能有以下原因:

  1. Retrofit 的 Converter 可以返回直接將 body 體轉(zhuǎn)換成數(shù)據(jù)對象浙巫。這樣很多返回結(jié)果可能不想要響應頭。于是會出現(xiàn)這樣的寫法刷后。
@GET("test")
fun getPost(): Observer<BodyContent<Post>>

BodyContent 則是自定義的 Bean 類的畴,用于轉(zhuǎn)換 json 數(shù)據(jù)。

class BodyContent<D> {
    var code = 0
    var msg: String? = null
    var data: D? = null
}

這樣做并不是一個好的實踐尝胆,因為 Http 響應頭和響應體中自定義的狀態(tài)碼丧裁,不能僅判斷響應體中的狀態(tài)碼,這樣做相當于忽略了響應頭中的錯誤含衔。一些服務器服務器本身出現(xiàn)錯誤或者中間件中出現(xiàn)錯誤煎娇,可能沒有執(zhí)行到該請求的代碼,就返回了錯誤贪染,此時可能沒有響應體或自定義錯誤碼缓呛,而是 Http 請求頭中返回錯誤。

如果我們除了檢測自定義的錯誤碼杭隙,還檢測 HTTP 請求頭中錯誤碼哟绊,就需要兩層判斷,代碼就顯得繁雜且冗余痰憎。而且有時候兩層狀態(tài)碼相同的數(shù)字表示不同的含義匿情,很讓人迷惑。

  1. 跟響應體一樣信殊,通過 converter 獲取狀態(tài)碼炬称。

如果僅僅是為了獲取狀態(tài)碼,這有點得不償失涡拘。因為 Retrofit 的一個 Converter 插件寫的非常好玲躯,我們可以使用 Retrofit 的響應頭即可自動轉(zhuǎn)換

// Response 是 Retrofit 自帶的數(shù)據(jù)類,可以通過 Retrofit 的 code() 和 message() 方法來獲取狀態(tài)碼和狀態(tài)短語鳄乏。
@GET("post/{id}")
fun getPost(
    @Path("id") postId: Long
): Observer<Response<Post>>
  1. 另一個原因是因為 RxJava 2 中不允許發(fā)送 null 值跷车,當有些結(jié)果不需要返回結(jié)果的時候,例如上傳數(shù)據(jù)橱野,body 體可能是空的朽缴,轉(zhuǎn)換會發(fā)送一個 null 值,會導致 RxJava 檢測而拋出異常水援。這時候為了正常走返回結(jié)果邏輯密强,需要填充一點默認數(shù)據(jù)茅郎,為了和錯誤狀態(tài)統(tǒng)一,添加狀態(tài)碼就成了一個可能的選擇或渤。

為了處理這種情況系冗,一種選擇是返回 Retrofit 的 Response 作為結(jié)果,即使請求體為空薪鹦,也一定有響應掌敬。

// retrofit 的 Response 作為返回結(jié)果來處理響應體為 null。
@POST("send/post")
fun sendPost(
    @Body post: Post
): Observer<Response<Void>>

然而池磁,這并不是唯一的選擇奔害,如果我們并不想處理狀態(tài)碼,或者響應頭地熄,返回這些數(shù)據(jù)并優(yōu)雅华临。

RxJava 真的不能處理返回結(jié)果為 null 嗎?其實它只是不允許在發(fā)送序列流中摻雜 null 值离斩。對于網(wǎng)絡(luò)請求這種不返回任何數(shù)據(jù)的银舱。其實就是請求完成了瘪匿。我們可以使用 Completable 來處理請求跛梗。

@POST("send/post")
fun sendPost(
    @Body post: Post
): Completable

其實,對于 HTTP 請求這種結(jié)果棋弥,我們根本沒有必要使用 ObserverFlowable核偿。因為它之后一個結(jié)果,而不會出現(xiàn)不斷發(fā)送數(shù)據(jù)的事件流顽染。

  • 對于一定有響應體的請求漾岳,使用 Single

  • 一定沒有響應體的請求粉寞,使用 Completable尼荆。

  • 特殊請求,有時候有響應體唧垦,有時候沒有的捅儒,返回 Maybe

合理的使用 Single振亮、CompletableMaybe 可以有效的提高代碼的清晰度巧还。如果確實需要響應頭的中內(nèi)容,可以在他們的泛型中使用 Retrofit 的 Response 來獲取坊秸。

  1. 如果僅僅是因為包含一些自己應用中特殊狀態(tài)的狀態(tài)碼麸祷,這完全沒有必要,因為 Http 的響應碼僅使用了幾個褒搔,還剩下許多狀態(tài)碼可用于表示特屬含義阶牍。例如喷面,2xx 開頭的狀態(tài)碼表示正常結(jié)果。RFC 僅定義了 200 ~ 206 的狀態(tài)碼荸恕,其余都可以根據(jù)應用自己定義使用乖酬。我們甚至可以跳過 250 之前的,使用 251 ~ 299 之間的表示正常結(jié)果的某些情況融求,防止在未來它們被 HTTP 標準協(xié)議使用咬像。

上面說來這么多錯誤碼,馬上就來討論處理錯誤生宛。

錯誤處理

有過單獨使用 OkHttp 和 Retrofit 而不使用 RxJava县昂,和 RxJava 一起使用兩種使用方案的人可能會意識到,當狀態(tài)碼是 200 ~ 299 以外的錯誤碼時陷舅,OkHttp 和 RxJava 走了 onResponse, 而 RxJava 走了 onError倒彰。 當使用 Body 中使用自定義的響應碼時,它也走了 RxJava 的 onNext 或者 onSuccess莱睁。這種將錯誤碼和正常結(jié)果放在一起會有以下問題:

  • 在正常顯示數(shù)據(jù)之前要先判斷一次錯誤結(jié)果待讳,如果錯誤則要調(diào)用錯誤處理方法,而在 onFailure/OnError 方法中也需要調(diào)用錯誤處理處理方法仰剿,多一條分支顯得不是那么完美创淡。

  • OkHttp/Retrofit 和 RxJava 的錯誤和正確流走的路徑不太一致。甚至 RxJava 自己的錯誤分支都不一致南吮。如果在 RxJava 中琳彩,返回結(jié)果僅僅是響應體則非 200 ~ 299 的錯誤走了 onError

@GET("post/{id}")
fun getPost(
    @Path("id") postId: Long
): Observer<Post>  // 僅返回 Response Body 數(shù)據(jù)

adapter-rxjavaBodyObservable 中的代碼中可以看到:

@Override
public void onNext(Response<R> response) {
  // isSuccessfull 判斷了狀態(tài)碼
  // public boolean isSuccessful() {
  //   return code >= 200 && code < 300;
  // }
  if (response.isSuccessful()) {
    observer.onNext(response.body());
  } else {
    terminated = true;
    Throwable t = new HttpException(response);
    try {
      observer.onError(t);
    } catch (Throwable inner) {
      ...
    }
  }
}

而如果結(jié)果返回的是Retrofit 的 Response部凑。

@GET("post/{id}")
fun getPost(
    @Path("id") postId: Long
): Observer<Response<Post>>  // 返回 Response露乏。

adapter-rxjavaResultObservable 中的代碼則是:

@Override
public void onNext(Response<R> response) {
  observer.onNext(Result.response(response));
}

@Override
public void onError(Throwable throwable) {
  try {
    observer.onNext(Result.error(throwable));
  } catch (Throwable t) {
    ...
    return;
  }
  observer.onComplete();
}

讓我們總結(jié)一些這些錯誤處理的流程

OkHttp         <----------  失敗重連,重定向跟蹤涂邀。Auth 認證重試
  |
  |   -------------------┐  
  |                      |   
  |                      |
  |                      |
 onResponse             onFailure 超時瘟仿,鏈接失敗,拋出 IOException 等異常比勉。
 (Response包含非          |
 200~300錯誤)             |
  |                      |
  ∨                      ∨
Retrofit   --------------┤    <--- Retrofit 主要處理請求參數(shù)劳较,和返回數(shù)據(jù)的轉(zhuǎn)換。錯誤處理沒有任何改變敷搪。
  |                      |
  |                      |
(Response)               |
包含200~300錯誤         跟OkHttp 一樣 
  |                      |
  ∨                      ∨
RxJava(ResponseBody)  ---┤
  |                      |
(200 ~299 正常結(jié)果)       增加 非 200~ 299 的錯誤結(jié)果
  |                      |
  |                      |
onNext/onSuccess       onError
  |                      |
  ∨                      ∨ 
body 里判斷結(jié)果         錯誤顯示兴想。
為 Response 的情況
或 body 里包含錯誤碼           

希望做的的情況

  1. 網(wǎng)絡(luò)請求框架的 Http 響應碼走一套邏輯。

  2. 能在一處判斷赡勘,不要有不同的分支嫂便。無論使用沒使用 Retrofit 和 RxJava 都是一樣的。

  3. 對上層開發(fā)者透明闸与,不用因為網(wǎng)絡(luò)請求錯誤而單獨繼承或者調(diào)用函數(shù)判斷錯誤碼毙替。

即達到這種效果

    OkHttp/Retrofit/RxJava
             |
  ┌------判斷響應碼--------┐
  |                      |
 onRespons               |
 onSuccess            onFailure/OnError 
(只有200~299的正確結(jié)果)    |
  |                      |   
  |                      |
  ∨                      ∨
渲染邏輯               錯誤提示岸售。

要讓所有的上層結(jié)果返回結(jié)果都一致,OkHttp 的攔截器是絕佳的選擇厂画。我們即在此來處理請求結(jié)果凸丸。

public class ResponseStatusInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response =  chain.proceed(request);
        if (!response.isSuccessful()) {
            throw new ApiException(response.code(), response.message());
        }
        return response;
    }

需要說明的是,如果使用 Java袱院,拋出的異常不是繼承自 IOException 將會有錯誤提示屎慢,但是如果使用 Kotlin,則不會提示忽洛,你必須使用繼承自 IOException 的類腻惠,否則將不會走到 onFaile 的回調(diào),這是因為在 OkHttp 的 RealCall 中判斷請求結(jié)果的代碼中:

    @Override protected void execute() {
      ...
      try {
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        ...
        responseCallback.onFailure(RealCall.this, e); // IOException 會回調(diào) onFailure.
      } catch (Throwable t) {
        ...
        throw t; // 非 IOException 則會以及拋出異常欲虚。
      } finally {
        ...
      }
    }

可以看到集灌,判斷如此簡單。但是如果你之前自定義了響應碼复哆,或者你項目中之前在代碼中處理錯誤碼的欣喧。很可能這些錯誤處理的地方非常多,一時修改不完梯找,你并不想一次性將整個項目修改唆阿,這樣工作量很大,也很難保證修改的正確性初肉。我們希望漸進式的演化酷鸦,在新的接口中使用這種方式饰躲,而老的接口不會有影響牙咏。我們將在 Retrofit 的接口定義中添加一個請求頭,然后在攔截器中獲取并移除掉嘹裂。

open class ResponseStatusInterceptor : Interceptor {
    companion object {
        const val CHECK_HTTP_RESPONSE_CODE = "Check-Http-Response-Code: true"
        private const val CHECK_HEADER = "Check-Http-Response-Code"
        private val gson = Gson()
        private val UTF8 = StandardCharsets.UTF_8
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        val value = request.headers()[CHECK_HEADER]
        request = request.newBuilder().removeHeader(CHECK_HEADER)
                .build() // 移除請求頭中的無用標識妄壶,不會發(fā)送到服務器。
        val response = chain.proceed(request)
        if ("true" == value) {
            if (!response.isSuccessful) {
                throw ApiException(response.code(), response.message())
            }
            // Make sure the content is json.
            val contentType = response.headers()["Content-Type"]
            if (contentType != null && response.body() != null && contentType.contains("application/json")) {
                val status =
                    gson.fromJson<BodyContent<Void>>(
                        bodyContent(response.body()),
                        object : TypeToken<BodyContent<Void?>?>() {}.type
                    )
                if (status != null && status.code != 1) {
                    throw ApiException(status.code, (if (status.msg == null) "" else status.msg!!))
                }
            }
        }
        return response
    }

    @Throws(IOException::class)
    protected fun bodyContent(responseBody: ResponseBody?): String? {
        if (responseBody == null) return null
        val contentLength = responseBody.contentLength()
        val source = responseBody.source()
        source.request(contentLength) // Buffer the entire body.
        val buffer = source.buffer

        return if (contentLength != 0L) {
            buffer.clone().readString(UTF8)
        } else null
    }
}

我們將在新接口中添加請求頭寄狼,以表示攔截響應碼丁寄。

@Headers(ResponseStatusInterceptor.CHECK_HTTP_RESPONSE_CODE) // 請求頭中添加標識。
@GET("not_exit_file")
fun testRetrofitErrorCode(): Call<String>

同時我們需要修改自定義響應碼的映射類 BodyContent泊愧,將默認值改為正常請求的伊磺,具體是什么依賴于你項目,這里假設(shè)是 200删咱。

class BodyContent<D> {
    companion object {
        const val SUCCESS = 200
    }
    var code = SUCCESS
    ...
}

這樣如果我們在 body 體中定義了響應碼的話屑埋,就可在因接口中不再使用自定義的,而使用請求頭痰滋。而此時摘能,默認值是 200续崖,而仍然會走正常的流程。唯一需要注意的是团搞,該字段不會被用于其他含義的數(shù)據(jù)严望。

同時,我們將使用 @DeprecatedBodyContent 標記為廢棄逻恐,提醒其他人員轉(zhuǎn)移到新的錯誤處理體系上來像吻。

最后我們形成如下的流程

OkHttp

  |
  |
攔截器攔截錯誤 ------------┒  
  |                      |   
  |                      |
  |                      |
(onResponse)          onFailure 超時,鏈接失敗复隆,拋出 IOException 等異常萧豆。200~300錯誤.
  |                      |
  ↓                      |
Retrofit                 |
  |                      |
  |                      |
(Response)   -------- onResponse   可能會增加一些 json 解析錯誤。
  |                      |
  ↓                      |
RxJava       -------- onError  可能會增加一些 UI 渲染昏名,空指針錯誤
  |                      |    
  |                      |
  |                      |
(onNext/onSuccess)       |
  |                      |
  |                      |  
渲染 UI               錯誤處理邏輯涮雷。

你可以使用任何上層的技術(shù)組合,他們的流程都是一樣的轻局。只需要在 onFailure 或 onError 中處理錯誤即可洪鸭。至于是哪一個,取決于你使用的是 Retroit 還是 RxJava.

盡管異常處理的流程分支統(tǒng)一了仑扑,但是有一種特殊異忱谰簦基本是統(tǒng)一的,那就是 401 授權(quán)失敗異常镇饮。這種異常會在各個頁面都可能發(fā)生蜓竹,然而處理流程和結(jié)果卻是一樣的: 顯示提醒 ——> 跳轉(zhuǎn)到登錄頁面 ——> 返回刷新數(shù)據(jù)。針對這樣的異常储藐,應該是統(tǒng)一處理俱济,而不是每個頁面單獨寫自己的邏輯。我見過不同的方案來實現(xiàn)這種跳轉(zhuǎn)钙勃,有的直接在攔截器中就跳轉(zhuǎn)了蛛碌;有的使用 RxJava 的 Observer 作為基類在 onError 中處理;有的在 RxJava 的 retry 操作符中進行重連辖源。這里將比較它們實現(xiàn)中的問題蔚携,同時給出一個最佳的組合。

登錄授權(quán)

如上的錯誤處理有一個問題克饶,那就是登錄授權(quán)酝蜒。需要登錄驗證通過才能訪問的接口會在驗證失敗時在響應頭中返回 401 錯誤碼。此時我們分兩種情況:

  1. token 過期可以在后臺通過接口自動更新 token矾湃。

  2. 沒有登錄或者登錄失敗等需要用戶操作才能登陸的情況亡脑。

自動更新 token 的情況

對于第一中,OkHttp 已經(jīng)提供了 authenticator 攔截器,唯一需要注意的是远豺,由于網(wǎng)絡(luò)請求可以并發(fā)發(fā)出奈偏,因此你需要避免發(fā)出多個更新 token 請求。這里使用加鎖和本地緩存來避免重復請求躯护。

OkHttpClient.Builder()
.authenticator(object :Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        synchronized(Client::class.java) {
            val token = if (getCachedAuth() == response.request().header("Authorization" )) {
                val request = Request.Builder().url("authenticator url").build()
                val response = okHttpClient.newCall(request).execute()
                val token = "從請求結(jié)果 response 中拿到" // response.body(). 獲取token.
                updateCachedToken(token) // 更新本地緩存的 token
                token
            } else {
                getCachedAuth()
            }
            return response.request().newBuilder()
                .addHeader("Authorization","Bearer {$token}")
                .build()
        }
    }
})

這里沒有使用雙重檢測鎖惊来,主要考慮刷新 token 并不是一個頻繁的操作,代碼簡潔也很重要棺滞。根據(jù)你自己的考慮裁蚁,你可以添加雙重判斷。

獲取和更新本地緩存的方法需要你根據(jù)你的方案來自己實現(xiàn)操作:

fun getCachedAuth(): String  {
    return "Bearer " + "本地存貯的值"
}

fun updateCachedToken(token: String) {
    // 更新本地緩存的 token
}

需要通知用戶手動登錄的情況

第二種情況顯得不那么好處理继准,錯誤的處理情況千差萬別枉证,處理的方式也不一樣。因為這里涉及到 UI 操作移必,一個好的做法是室谚,在 BaseActivity 中添加一個函數(shù),用于處理全部都有的登錄部分崔泵。

// BaseActivity 中添加

protected fun handleException(exception: Throwable): Boolean {
    reportException(exception)
    return if (exception is ApiException && exception.unauthorized()) {
        // 顯示登陸提醒的彈窗秒赤。詢問是否登陸,跳轉(zhuǎn)到登陸頁面憎瘸。
        true
    } else {
        false
    }
}

fun reportException(exception: Throwable) {
    // 上報錯誤數(shù)據(jù)
}

在各個 Activity 中對接網(wǎng)絡(luò)請求和錯誤處理入篮。

val disposable = Client.getServerApi()
    .getPost(12)
    .subscribe({
        // update ui
    }, { exception ->
        // 對 UI 顯示是否提醒和更改狀態(tài),例如隱藏加在動畫幌甘。
        if (攔截) {
            // 如果需要攔截潮售,就不用調(diào)用 handleException。否則就交給默認的錯誤處理锅风。
        } else if (!handleException(exception)) {
            // 默認處理沒有相應酥诽,就自己處理。
        }
    })

中間的交付過程可以使用 LiveData遏弱、RxJava盆均、協(xié)稱的任意組合塞弊。這樣對中間交付過程沒有任何要求漱逸,處理起來非常靈活。

  1. token 過期游沿,

線程切換

由于安卓的特性饰抒,網(wǎng)絡(luò)請求必須在后臺線程中執(zhí)行。因此切換線程就成了一個必選項诀黍。至于在何處切換線程有了五花八門的寫法袋坑。我建議始終把 subscribeOnobserverOn 寫在緊挨著 subsctibe 的前面。例如:

 Single.just(1)
    .doOnSubscribe {
        printThread("doOnSubscribe")
    }
    .map {
        printThread("Map")
        it
    }
    ...
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .subscribe(...)

fun printThread(tag: String) {
    println(tag + "........"+ Thread.currentThread().name)
}

由于網(wǎng)絡(luò)請求是必須的操作眯勾,.subscribeOn(Schedulers.io()) 就成了必須要做的工作枣宫。而 RxJava-adapter 已經(jīng)為我們提供了這個操作婆誓。在添加 Retrofit 的 CallAdapterFActory 時,我們可以選擇添加線程調(diào)度器也颤。

 Retrofit.Builder()
    .addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(Schedulers.io())) // 指定 io 線程訪問網(wǎng)絡(luò)洋幻。

既然切換到后臺線程有默認的方法可供使用,那切換到前臺呢翅娶?然而并沒有文留。 我們來討論為什么不應該這樣做!

這是因為 observeOn 影響它后面流的操作符的線程竭沫。一旦我們把 observeOn 寫在靠前的位置燥翅,后面的變換操作符將在前臺線程中。如果變換操作中有一些耗時的操作蜕提,我們將不得不調(diào)用 observeOn(..) 切換到其他線程森书,然后再次調(diào)用一次,這樣一次流中的線程切換頻繁顯得非常繁瑣谎势。另一方面拄氯,切換到前臺線程的操作不是必須的,例如一個請求是作為另一個請求的輸入它浅,或者我們使用 LiveData 的 postValue 將數(shù)據(jù)更新到 UI 線程時译柏,切換到前臺線程顯得多余。

那如果是這樣姐霍,subscribeOn 為什么提供了默認的接口呢鄙麦?它也智能影響它前面的 doOnSubscribe, 我們再最上游的流設(shè)置了 subscribeOn 并不能使后面的操作也在后臺線程中。 我想著主要是因為兩方面

  1. 網(wǎng)絡(luò)在后臺線程請求是必須的镊折,而切換到前臺更新 UI 則不一定會發(fā)生胯府。

  2. doOnSubscribe 沒有變換操作頻繁。幾乎每個接口請求后都會有一些額外操作恨胚,而請求之前的操作卻不是那么常見骂因,即使有也很少有耗時的操作,并且一個項目中也不會有多少個調(diào)用赃泡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寒波,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子升熊,更是在濱河造成了極大的恐慌俄烁,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件级野,死亡現(xiàn)場離奇詭異页屠,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門辰企,熙熙樓的掌柜王于貴愁眉苦臉地迎上來风纠,“玉大人,你說我怎么就攤上這事牢贸∫楹觯” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵十减,是天一觀的道長栈幸。 經(jīng)常有香客問我,道長帮辟,這世上最難降的妖魔是什么速址? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮由驹,結(jié)果婚禮上芍锚,老公的妹妹穿的比我還像新娘。我一直安慰自己蔓榄,他們只是感情好并炮,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甥郑,像睡著了一般逃魄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澜搅,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天伍俘,我揣著相機與錄音,去河邊找鬼勉躺。 笑死癌瘾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饵溅。 我是一名探鬼主播妨退,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜕企!你這毒婦竟也來了咬荷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糖赔,失蹤者是張志新(化名)和其女友劉穎萍丐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體放典,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奋构。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壳影。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弥臼,靈堂內(nèi)的尸體忽然破棺而出宴咧,到底是詐尸還是另有隱情,我是刑警寧澤径缅,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布掺栅,位于F島的核電站,受9級特大地震影響纳猪,放射性物質(zhì)發(fā)生泄漏氧卧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一氏堤、第九天 我趴在偏房一處隱蔽的房頂上張望沙绝。 院中可真熱鬧,春花似錦鼠锈、人聲如沸闪檬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粗悯。三九已至,卻和暖如春同欠,著一層夾襖步出監(jiān)牢的瞬間为黎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工行您, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铭乾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓娃循,卻偏偏與公主長得像炕檩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捌斧,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355