全網(wǎng)最詳細(xì)的Kotlin協(xié)程-異常篇講解與踩坑

前言

協(xié)程的使用中對異常的處理是非常抽象的一個過程笛坦,google了很多文檔仔拟,在官方文檔中對異常的處理并沒有講的很詳細(xì)姆泻,編寫過程中踩的坑似乎也沒有官方文檔的說明與解釋咒唆,網(wǎng)上也有很對對異常的處理文獻(xiàn),但是看過之后發(fā)現(xiàn)都是零零散散茶鹃,而且很多案例都是沒經(jīng)過代碼推敲的,甚至有些文獻(xiàn)里面的理解是錯誤的艰亮,所以奔著開發(fā)的理念仔細(xì)研究了一下協(xié)程的異常處理闭翩,以便更多的朋友看到這篇文章能帶來更好的理解,也對封裝框架設(shè)計有很大的幫助迄埃,以下案例均可以拷貝到編譯器進(jìn)行自行驗證疗韵,如有理解不對的地方歡迎私信我進(jìn)行交流學(xué)習(xí)并改正

概念

Try Catch能捕獲所有的異常嗎?

答案是不能侄非,簡單的舉例說明:

  • 情況一:如果程序發(fā)生了異常并沒有進(jìn)行拋出蕉汪,這個時候會捕獲不到異常
  • 情況二:在java中如果程序拋出的是錯誤,而不是異常這種情況視捕獲的代碼形態(tài)決定能否捕獲到異常
  • 情況三:比如動態(tài)鏈接庫的加載錯誤逞怨,以及部分系統(tǒng)錯誤引起的異常不一定能捕獲到

協(xié)程異常了怎么辦者疤?

當(dāng)一個協(xié)程發(fā)生了異常,它將把異常傳播給它的父協(xié)程叠赦,父協(xié)程會做以下幾件事:

  1. 取消其他子協(xié)程
  2. 取消自己
  3. 將異常傳播給自己的父協(xié)程

所以要理解協(xié)程異常的處理需要弄清楚下面幾個關(guān)鍵點:

  • try-catch捕獲異常
  • CoroutineExceptionHandler
  • supervisorScope 和SupervisorJob

程序示例

看下面的代碼

  fun test() {
        try {
            Thread() {
                throw NullPointerException()
            }.start()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

結(jié)果是:運行崩潰

這里如果有朋友覺得很不可思議的話可以進(jìn)行自我測試,為什么try-catch中開啟代碼還是會崩潰呢驹马?

==答案是try-catch 只能捕捉當(dāng)前線程的堆棧信息。對于非當(dāng)前線程無法實現(xiàn)捕捉==

既然這樣下面代碼應(yīng)該會被捕捉到:

fun test() = runBlocking(Dispatchers.IO) {
        try {
            launch {
                throw NullPointerException()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Log.d("wangxuyang", "" + e.message)
        }
    }

結(jié)果是:運行崩潰

what f***?,這個協(xié)程是在當(dāng)前線程開啟的,并進(jìn)行了try-catch為什么還是會崩潰呢糯累?

這里直接告訴結(jié)論是:==launch啟動的根協(xié)程算利,是不會傳播異常的==


什么叫傳播異常?

傳播異常泳姐,是指能夠?qū)惓V鲃油鈷伒絾禹攲訁f(xié)程所在的線程效拭。因為launch啟動的協(xié)程,是不會將異常拋到線程胖秒,所以try-catch無法捕捉缎患,為了讓這種異常能夠捕捉到。協(xié)程引入了CoroutineExceptionHandler

啟動協(xié)程還有一種方式是async扒怖,那這種會不會向線程拋出異常呢较锡?代碼運行如下:

 private val job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    private fun doWork(): Deferred<String> = scope.async { throw NullPointerException("自定義空指針異常") }


    private fun loadData() = scope.launch {
        try {
            doWork().await()
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }

結(jié)果是:運行不會崩潰

代碼中try-catch住的代碼是:

doWork().await()

結(jié)論:==雖然向外拋出了異常,但是是在調(diào)用await()方法后拋出的盗痒,并且當(dāng)async作為根協(xié)程時蚂蕴,被封裝到deferred對象中的異常才會在調(diào)用await時拋出,并且這個異常是可以被try-catch捕獲住的==

上面說到根協(xié)程并且這個根協(xié)程是調(diào)用了await()拋出異常俯邓,其實這里是一個大坑骡楼,筆者在測試過程中感到也很神奇,接下來看這段代碼:

  private val job0: Job = Job()
  private val scope0 = CoroutineScope(Dispatchers.Default + job0)
  private fun loadData0() = scope0.launch {
        val asy = async {
            Log.d("async 異常:", "開始準(zhǔn)備拋出異常")
            delay(1000)
            throw NullPointerException("自定義空指針異常")
        }
        try {
            asy.await()
        } catch (e: Exception) {
            Log.d("async 異常: 捕獲的異常-", e.toString())
        }
        Log.d("async 異常:", "繼續(xù)執(zhí)行后續(xù)代碼")
    }

運行結(jié)果是:程序崩潰

2022-03-22 19:51:02.074 25864-25903/com.example.coroutinestest D/async 異常:: 開始準(zhǔn)備拋出異常
2022-03-22 19:51:03.085 25864-25905/com.example.coroutinestest D/async 異常: 捕獲的異常-: java.lang.NullPointerException: 自定義空指針異常
2022-03-22 19:51:03.085 25864-25905/com.example.coroutinestest D/async 異常:: 繼續(xù)執(zhí)行后續(xù)代碼

乍一看稽鞭,跟上面代碼的邏輯走勢一樣鸟整,也是調(diào)用了await方法,也是try-catch了這個方法 朦蕴,打的日志也是捕獲到了篮条,是正常的流程啊
但是我告訴大家這里并不是調(diào)用await方法后才拋出的異常,只是崩潰后這個異常被捕獲到了而已吩抓,是不是大家要覺得我很菜涉茧?可以這樣來印證這個猜想,講await方法屏蔽掉疹娶,再運行這個方法:

  try {
          // asy.await()
      } catch (e: Exception) {
            Log.d("async 異常: 捕獲的異常-", e.toString())
      }
        Log.d("async 異常:", "繼續(xù)執(zhí)行后續(xù)代碼")

結(jié)果是:程序崩潰伴栓,日志如下

 //2022-03-22 19:55:05.460 26378-26415/com.example.coroutinestest D/async 異常:: 繼續(xù)執(zhí)行后續(xù)代碼
 //2022-03-22 19:55:05.461 26378-26415/com.example.coroutinestest D/async 異常:: 開始準(zhǔn)備拋出異常

這里是不是印證了前面的猜想,崩潰原因其實不是在調(diào)用await方法之后引起的崩潰雨饺,是代碼執(zhí)行到 throw NullPointerException("自定義空指針異常")就拋出異常了钳垮,所以前面的結(jié)論是成立的

結(jié)論是:==async開啟一個根協(xié)程或者子協(xié)程,異常都會被拋出給線程额港,并且可以被try-catch捕獲到饺窿。async開啟一個根協(xié)程,在調(diào)用await方法時候會拋出異常锹安,這個異扯碳觯可以用try-catch捕獲不引起崩潰倚舀,如果這個協(xié)程不是根協(xié)程,那么是代碼執(zhí)行到 throw 異常的時候就拋出了異常與是否調(diào)用await方法無關(guān)這個異橙趟危可以用try-catch捕獲但是會引起崩潰痕貌,可以用CoroutineExceptionHandler進(jìn)行捕獲解決崩潰問題==

CoroutineExceptionHandler的應(yīng)用

上面印證了程序的崩潰與異常的拋出,但是這個異常怎么處理呢糠排?這里就用到了官方提供的CoroutineExceptionHandler了

/**
 * Creates a [CoroutineExceptionHandler] instance.
 * @param handler a function which handles exception thrown by a coroutine
 */

==CoroutineExceptionHandler的官方解釋是:處理協(xié)程拋出的異常的函數(shù)舵稠,官方又一個隱藏點沒說就是這個CoroutineExceptionHandler只能處理當(dāng)前域內(nèi)開啟的子協(xié)程或者當(dāng)前協(xié)程拋出的異常==

所以解決上訴不是根協(xié)程引起的崩潰問題可以采用這樣的方式:

 private val coroutineExceptionHandler = CoroutineExceptionHandler { _, _ ->
        Log.d("async 異常:", "異常被內(nèi)部CoroutineExceptionHandler處理掉了")
    }

    private fun loadData0() = scope0.launch(coroutineExceptionHandler) {
        val asy = async {
            Log.d("async 異常:", "開始準(zhǔn)備拋出異常")
            delay(1000)
            throw NullPointerException("自定義空指針異常")
        }
        try {
            asy.await()
        } catch (e: Exception) {
            Log.d("async 異常: 捕獲的異常-", e.toString())
        }
        Log.d("async 異常:", "繼續(xù)執(zhí)行后續(xù)代碼")
    }

運行結(jié)果:不會崩潰,日志如下

    2022-03-22 20:02:31.121 27083-27166/com.example.coroutinestest D/async 異常:: 開始準(zhǔn)備拋出異常
    2022-03-22 20:02:32.134 27083-27167/com.example.coroutinestest D/async 異常: 捕獲的異常-: java.lang.NullPointerException: 自定義空指針異常
    2022-03-22 20:02:32.134 27083-27167/com.example.coroutinestest D/async 異常:: 繼續(xù)執(zhí)行后續(xù)代碼
    2022-03-22 20:02:32.135 27083-27166/com.example.coroutinestest D/async 異常:: 異常被內(nèi)部CoroutineExceptionHandler處理掉了

看到了代碼即使是拋出了異常入宦,但是被內(nèi)部消耗了哺徊,并缺不會引起程序崩潰

前面提到了launch啟動的根協(xié)程,是不會傳播異常的

這里我們繼續(xù)印證這個結(jié)論:

例子1:

   private fun loadData1() = scope1.launch {
        try {
            throw NullPointerException("自定義空指針異常")
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }


結(jié)果:不會崩潰

例子2:

 private fun loadData1() = try {
        scope1.launch {
            throw NullPointerException("自定義空指針異常")
        }
    } catch (e: Exception) {
        Log.d("try catch捕獲的異常:", e.toString())
    }
    

結(jié)果:會崩潰

例子3:

    private fun doWork1() = scope1.launch { throw NullPointerException("自定義空指針異常") }


    private fun loadData1() = scope1.launch {
        try {
            doWork1()
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }
    
結(jié)果:會崩潰

==從例1與例2可以看出異常在協(xié)程內(nèi)部可以被捕獲乾闰,但是在外部不能被捕獲落追,這里印證了launch不向外拋出異常的結(jié)論,再從例3與例1對比可以看出這個協(xié)程涯肩,這個協(xié)程并不是只有根協(xié)程才不向線程拋出異常轿钠,而是只要launch開啟的協(xié)程,無論是根還是子都不會向線程中拋出異常==

同樣可以使用上訴方法來解決這個崩潰問題:

private val job2: Job = Job()
    private val scope2 = CoroutineScope(Dispatchers.Default + job2)

    private fun loadData2() = scope2.launch(CoroutineExceptionHandler { _, exception ->
        {
            Log.d("Handler捕獲的異常", exception.toString())
        }
    }) {
        try {
            //無論launch有幾層都不會崩潰
            launch { launch { throw NullPointerException("自定義空指針異常") } }
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }

再來印證前面所說的:CoroutineExceptionHandler只能處理當(dāng)前域內(nèi)開啟的子協(xié)程或者當(dāng)前協(xié)程拋出的異常

運行下面的代碼:

 private val job3: Job = Job()
    private val scope3 = CoroutineScope(Dispatchers.Default + job3)

    private fun doWork3() = scope3.launch { throw NullPointerException("自定義空指針異常") }

    private fun loadData3() = scope3.launch(CoroutineExceptionHandler { _, exception ->
        {
            Log.d("Handler捕獲的異常", exception.toString())
        }
    }) {
        try {
            doWork3()
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }

結(jié)果是:崩潰
因為doWork3方法開啟的協(xié)程不是在當(dāng)前域下開啟的協(xié)程而是scope3開啟的病苗,只是在當(dāng)前域下運行而已疗垛,這里就印證了上面的說法

但是可以通過增加一個CoroutineExceptionHandler來解決上面的問題,代碼如下:

 private val job4: Job = Job()
    private val scope4 =
        CoroutineScope(Dispatchers.Default + job4 + CoroutineExceptionHandler { _, exception ->
            {
                Log.d("Handler捕獲的異常", exception.toString())
            }
        })

    //無論launch有幾層都不會崩潰
    private fun doWork4() = scope4.launch { launch { throw NullPointerException("自定義空指針異常") } }

    private fun loadData4() = scope4.launch {
        try {
            doWork4()
        } catch (e: Exception) {
            Log.d("try catch捕獲的異常:", e.toString())
        }
    }

結(jié)果是:不會崩潰

supervisorScope 和 SupervisorJob

前面講到了CoroutineExceptionHandler可以捕獲異常并且處理掉異常硫朦,程序不會崩潰贷腕,這里還有一種方式就是使用supervisorScope 和 SupervisorJob

supervisorScope 和 SupervisorJob的原理是:將異常不傳播給自己的父協(xié)程

首先我們來看一個例子:

  private val handler7 = CoroutineExceptionHandler { _, _ ->
        Log.d("kobe", "CoroutineExceptionHandler")
    }

    private fun coroutineBuildRunBlock7() = runBlocking(Dispatchers.IO) {
        CoroutineScope(Job() + handler7)
            .launch {
                launch {
                    Log.d("kobe", "start job1 delay")
                    delay(1000)
                    Log.d("kobe", "end job1 delay")
                }
                launch {
                    Log.d("kobe", "job2 throw execption")
                    throw NullPointerException()
                }
            }
    }

結(jié)果是:不崩潰,日志如下

2022-03-22 15:24:34.022 20373-20411/com.example.coroutinestest D/kobe: start job1 delay
2022-03-22 15:24:34.025 20373-20412/com.example.coroutinestest D/kobe: job2 throw execption
2022-03-22 15:24:34.029 20373-20412/com.example.coroutinestest D/kobe: CoroutineExceptionHandler

看到一個現(xiàn)象就是:子協(xié)程崩潰會引起兄弟協(xié)程的執(zhí)行錯誤咬展,這就是文章前面所說的取消其他子協(xié)程泽裳,這當(dāng)然不是我們想看到的情況,互不影響才是最優(yōu)解破婆,所以有了下面的方法:

  private val handler8 = CoroutineExceptionHandler { _, _ ->
        Log.d("kobe", "CoroutineExceptionHandler")
    }

    private fun coroutineBuildRunBlock8() = runBlocking(Dispatchers.IO) {
        CoroutineScope(Job() + handler8)
            .launch {
                launch {
                    delay(2000)
                    Log.d("kobe", "start job3 delay")
                }
                supervisorScope {
                    launch {
                        Log.d("kobe", "start job1 delay")
                        delay(1000)
                        Log.d("kobe", "end job1 delay")
                    }
                    launch {
                        Log.d("kobe", "job2 throw execption")
                        throw NullPointerException()
                    }
                }
            }
    }

結(jié)果是:不崩潰诡壁,日志如下

 2022-03-22 15:48:07.384 21777-21818/com.example.coroutinestest D/kobe: start job1 delay
2022-03-22 15:48:07.384 21777-21820/com.example.coroutinestest D/kobe: job2 throw execption
2022-03-22 15:48:07.385 21777-21820/com.example.coroutinestest D/kobe: CoroutineExceptionHandler
2022-03-22 15:48:08.391 21777-21818/com.example.coroutinestest D/kobe: end job1 delay
2022-03-22 15:48:09.389 21777-21818/com.example.coroutinestest D/kobe: start job3 delay

按照前面的邏輯異常捕獲了,使用了supervisorScope所以一個子協(xié)程的異常不會會影響另一個子協(xié)程的運行,并且不會影響這個域外的兄弟協(xié)程荠割,所以日志全

所以supervisorScope中開啟協(xié)程,無論多少個子協(xié)程都互不影響旺矾,這是我們想要的處理情況

那我們再來看下SupervisorJob蔑鹦,運行下面代碼:

private val supervisorJob9 = SupervisorJob()
    private val handler9 = CoroutineExceptionHandler { _, _ ->
        Log.d("kobe", "CoroutineExceptionHandler")
    }
     private val handler99 = CoroutineExceptionHandler { _, _ ->
        Log.d("kobe", "頂層異常處理")
    }


    private fun coroutineBuildRunBlock9() = runBlocking(Dispatchers.IO) {

        CoroutineScope(handler99 ).launch {
            CoroutineScope(  handler9+supervisorJob9)
                .launch {
                    launch {
                        Log.d("kobe", "start job1 delay")
                        delay(1000)
                        Log.d("kobe", "end job1 delay")
                    }
                    launch {
                        Log.d("kobe", "job2 throw execption")
                        throw NullPointerException()
                    }
                }
        }
    }


結(jié)果是:不會崩潰,日志如下

2022-03-23 17:32:25.771 8593-8638/com.example.coroutinestest D/kobe: job2 throw execption
2022-03-23 17:32:25.772 8593-8642/com.example.coroutinestest D/kobe: start job1 delay
2022-03-23 17:32:25.785 8593-8642/com.example.coroutinestest D/kobe: CoroutineExceptionHandler

我們這次來分析日志箕宙,日志中沒有“頂層異常處理”所以這個異澈啃啵肯定就沒有傳播出去,也沒有打出“end job1 delay”來表示影響了這個協(xié)程內(nèi)部的兄弟協(xié)程

所以結(jié)論是: ==SupervisorJob這個任務(wù)是阻止異常不會向外傳播柬帕,因此不會影響其父親/兄弟協(xié)程哟忍,也不會被其兄弟協(xié)程拋出的異常影響狡门,但是他內(nèi)部生成的各種協(xié)程是依然會像job一樣互相影響,并且這個異常必須使用CoroutineExceptionHandler處理掉锅很,不然會引起程序崩潰==

看到這里可能又有人會問這個很正常其馏,因為異常被handler9處理掉了,所以就沒有傳遞到父親協(xié)程爆安,那這里我們可以這樣處理叛复,我們?nèi)サ暨@個handler9:

   private fun coroutineBuildRunBlock9() = runBlocking(Dispatchers.IO) {

        CoroutineScope(handler99 ).launch {
            CoroutineScope(supervisorJob9)
                .launch {
                    launch {
                        Log.d("kobe", "start job1 delay")
                        delay(1000)
                        Log.d("kobe", "end job1 delay")
                    }
                    launch {
                        Log.d("kobe", "job2 throw execption")
                        throw NullPointerException()
                    }
                }
        }
    }

結(jié)果:程序崩潰,并且沒有打印出“頂層異常處理”扔仓,所以前面的結(jié)論是正確的

我們再來印證以下兄弟協(xié)程是否被影響褐奥,運行代碼:

  private val supervisorJob10 = SupervisorJob()
    private val handler10 = CoroutineExceptionHandler { _, _ ->
        Log.d("kobe", "CoroutineExceptionHandler")
    }

    private val coroutineContext10 = handler10 + supervisorJob10


    private fun coroutineBuildRunBlock10() = runBlocking(Dispatchers.IO) {
        CoroutineScope(coroutineContext10)
            .launch {
                launch {
                    Log.d("kobe", "start job1 delay")
                    delay(1000)
                    Log.d("kobe", "end job1 delay")
                }
                launch {
                    Log.d("kobe", "start job2 delay")
                    delay(1000)
                    Log.d("kobe", "end job2 delay")
                }

                CoroutineScope(coroutineContext10).launch {
                    launch {
                        Log.d("kobe", "start job3 delay")
                        delay(1000)
                        Log.d("kobe", "end job3 delay")
                    }
                    launch {
                        Log.d("kobe", "job4 throw execption")
                        throw NullPointerException()
                    }
                }
            }
    }

結(jié)果是:不會崩潰,日志如下

2022-03-22 15:45:20.807 21611-21653/com.example.coroutinestest D/kobe: start job1 delay
2022-03-22 15:45:20.809 21611-21652/com.example.coroutinestest D/kobe: start job2 delay
2022-03-22 15:45:20.814 21611-21651/com.example.coroutinestest D/kobe: start job3 delay
2022-03-22 15:45:20.815 21611-21654/com.example.coroutinestest D/kobe: job4 throw execption
2022-03-22 15:45:20.817 21611-21654/com.example.coroutinestest D/kobe: CoroutineExceptionHandler
2022-03-22 15:45:21.820 21611-21654/com.example.coroutinestest D/kobe: end job1 delay
2022-03-22 15:45:21.820 21611-21651/com.example.coroutinestest D/kobe: end job2 delay

結(jié)果是:兄弟協(xié)程并不影響翘簇,前面的結(jié)論正確

結(jié)論

**1. try-catch 只能捕捉當(dāng)前線程的堆棧信息撬码。對于非當(dāng)前線程無法實現(xiàn)捕捉

  1. launch啟動的根協(xié)程,是不會傳播異常的
  2. async開啟一個根協(xié)程或者子協(xié)程版保,異常都會被拋出給線程呜笑,并且可以被try-catch捕獲到。async開啟一個根協(xié)程找筝,在調(diào)用await方法時候會拋出異常蹈垢,這個異常可以用try-catch捕獲不引起崩潰袖裕,如果這個協(xié)程不是根協(xié)程曹抬,那么是代碼執(zhí)行到 throw 異常的時候就拋出了異常與是否調(diào)用await方法無關(guān)這個異常可以用try-catch捕獲但是會引起崩潰急鳄,可以用CoroutineExceptionHandler進(jìn)行捕獲解決崩潰問題
  3. CoroutineExceptionHandler的官方解釋是:處理協(xié)程拋出的異常的函數(shù)谤民,官方又一個隱藏點沒說就是這個CoroutineExceptionHandler只能處理當(dāng)前域內(nèi)開啟的子協(xié)程或者當(dāng)前協(xié)程拋出的異常
  4. SupervisorJob這個任務(wù)是阻止異常不會向外傳播,因此不會影響其父親/兄弟協(xié)程疾宏,也不會被其兄弟協(xié)程拋出的異常影響张足,但是他內(nèi)部生成的各種協(xié)程是依然會像job一樣互相影響,并且這個異常必須使用CoroutineExceptionHandler處理掉坎藐,不然會引起程序崩潰**

最后

協(xié)程的異常處理是很復(fù)雜的一個過程为牍,里面融合了結(jié)構(gòu)化并發(fā)的思想,這個開發(fā)思想伴隨了kotlin的后續(xù)開發(fā)岩馍,并且協(xié)程的異常處理中有很多坑需要一一去踩碉咆,在官方文檔與網(wǎng)上的零散碎片知識中很難找到這些坑點,如果能認(rèn)真看完上訴的講解蛀恩,肯定對協(xié)程的異常有了一個新的認(rèn)知疫铜,更希望讀者將上面的案例放在自己的代碼中去運行總結(jié),若有不對的地方歡迎指出改正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末双谆,一起剝皮案震驚了整個濱河市壳咕,隨后出現(xiàn)的幾起案子席揽,更是在濱河造成了極大的恐慌,老刑警劉巖谓厘,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幌羞,死亡現(xiàn)場離奇詭異,居然都是意外死亡庞呕,警方通過查閱死者的電腦和手機(jī)新翎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來住练,“玉大人地啰,你說我怎么就攤上這事〗补洌” “怎么了亏吝?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盏混。 經(jīng)常有香客問我蔚鸥,道長,這世上最難降的妖魔是什么许赃? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任止喷,我火速辦了婚禮,結(jié)果婚禮上混聊,老公的妹妹穿的比我還像新娘弹谁。我一直安慰自己,他們只是感情好句喜,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布预愤。 她就那樣靜靜地躺著,像睡著了一般咳胃。 火紅的嫁衣襯著肌膚如雪植康。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天展懈,我揣著相機(jī)與錄音销睁,去河邊找鬼。 笑死存崖,一個胖子當(dāng)著我的面吹牛榄攀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播金句,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吕嘀!你這毒婦竟也來了违寞?” 一聲冷哼從身側(cè)響起贞瞒,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趁曼,沒想到半個月后军浆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡挡闰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年乒融,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摄悯。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡赞季,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奢驯,到底是詐尸還是另有隱情申钩,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布瘪阁,位于F島的核電站撒遣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏管跺。R本人自食惡果不足惜义黎,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豁跑。 院中可真熱鬧廉涕,春花似錦、人聲如沸贩绕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淑倾。三九已至馏鹤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娇哆,已是汗流浹背湃累。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留碍讨,地道東北人治力。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像勃黍,于是被迫代替她去往敵國和親宵统。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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