(譯)Android中的Kotlin協(xié)程-基礎(chǔ)

如果英文較好公你,建議直接閱讀原文

譯文

什么是協(xié)程

基本上,coroutines是輕量級(jí)線程璃弄,它使得我們可以用串行的方式寫(xiě)出異步的哥捕、非阻塞的代碼牧抽。

Android中如何導(dǎo)入Kotlin協(xié)程

根據(jù)Kotlin Coroutines Github repo,我們需要導(dǎo)入kotlinx-coroutines-core和kotlinx-coroutines-android(類(lèi)似于RxJava的io.reactivex.rxjava2:rxandroid遥赚,該庫(kù)支持Android主線程扬舒,同時(shí)保證未捕獲的異常可以在應(yīng)用崩潰前輸出日志)凫佛。如果項(xiàng)目里使用了RxJava讲坎,可以導(dǎo)入kotlinx-coroutines-rx2來(lái)同時(shí)使用RxJava和協(xié)程孕惜,這個(gè)庫(kù)幫助將RxJava代碼轉(zhuǎn)為協(xié)程。

添加如下代碼導(dǎo)入

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.2"

記得添加最新的Kotlin版本到根build.gradle:

buildscript {
    ext.kotlin_version = '1.3.50'
    repositories {
        jcenter()
        ...
    }
    ...
}

OK晨炕,準(zhǔn)備工作已就緒衫画,讓我們開(kāi)始吧~

內(nèi)容目錄

  1. 掛起函數(shù)(Suspending functions)
  2. 協(xié)程作用域 (Coroutine scope)
    (1) 自定義作用域(CoroutineScope)
    (2) 主作用域(MainScope)
    (3) 全局作用域(GlobalScope)
  3. 協(xié)程上下文(Coroutine context)
    (1) 調(diào)度器(Dispatchers)
    (2) 協(xié)程異常處理器(CoroutineExceptionHandler)
    (3) 任務(wù)(Job)
    — (3.1) 父-子層級(jí)(Parent-child hierarchies)
    — (3.2) SupervisorJob v.s. Job
  4. 協(xié)程構(gòu)建器 (Coroutine builder)
    (1) launch
    (2) async
  5. 協(xié)程體(Coroutine body)

協(xié)程基礎(chǔ)

先看看協(xié)程長(zhǎng)啥樣:

CoroutineScope(Dispatchers.Main + Job()).launch {
  val user = fetchUser() // A suspending function running in the I/O thread.
  updateUser(user) // Updates UI in the main thread.
}

private suspend fun fetchUser(): User = withContext(Dispatchers.IO) {
  // Fetches the data from server and returns user data.
}

這段代碼在后臺(tái)線程拉取服務(wù)器數(shù)據(jù),然后回到主線程更新UI.

1. 掛起函數(shù)(Suspending functions)

掛起函數(shù)是Kotlin協(xié)程中的特殊函數(shù)瓮栗,用關(guān)鍵字suspend定義削罩。掛起函數(shù)可以中斷(suspend)當(dāng)前協(xié)程的執(zhí)行,這意味著它一直等待费奸,直到掛起函數(shù)恢復(fù)(resume)弥激。因?yàn)檫@篇博客關(guān)注協(xié)程的基本概念, Android中的Kotlin協(xié)程-掛起函數(shù)將會(huì)討論更多細(xì)節(jié)

我們回過(guò)頭來(lái)看看上面的代碼货邓,它可以分為4個(gè)部分:

suspend functions.png

2. 協(xié)程作用域(Coroutine scope)

為新協(xié)程定義一個(gè)作用域秆撮。每個(gè)協(xié)程構(gòu)建器都是CoroutineScope的拓展,繼承其coroutineContext以自動(dòng)傳遞上下文對(duì)象和取消换况。

所有的協(xié)程都在協(xié)程作用域里運(yùn)行职辨,并接受一個(gè)CoroutineContext(協(xié)程上下文,后文詳述)作為參數(shù)戈二。有幾個(gè)作用域我們可以使用:

(1) CoroutineScope

用自定義的協(xié)程上下文創(chuàng)建作用域舒裤。例如,根據(jù)我們的需要觉吭,指定線程腾供、父job和異常處理器(the thread, parent job and exception handler):

CoroutineScope(Dispatchers.Main + job + exceptionHandler).launch {
    ...
}

(2) MainScope

為UI組件創(chuàng)建一個(gè)主作用域。它使用SupervisorJob()鲜滩,在主線程運(yùn)行伴鳖,這意味著如果它的某個(gè)子任務(wù)(child job)失敗了,不會(huì)影響其他子任務(wù)徙硅。

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

(3) GlobalScope

這個(gè)作用域不跟任何任務(wù)(job)綁定榜聂。它用來(lái)啟動(dòng)頂級(jí)協(xié)程,這些協(xié)程可以運(yùn)行在整個(gè)的應(yīng)用生命周期嗓蘑,且永遠(yuǎn)不能取消须肆。

3. 協(xié)程上下文(Coroutine context)

協(xié)程總是運(yùn)行在某個(gè)CoroutineContext類(lèi)型的上下文中。協(xié)程上下文是一系列元素桩皿,用來(lái)指定線程策略豌汇、異常處理器、控制協(xié)程生命周期等泄隔【芗可以用+操作符將這些元素組合起來(lái)。

有3種最重要的協(xié)程上下文:調(diào)度器佛嬉,協(xié)程異常處理器柜思,任務(wù)(Dispatchers岩调,CoroutineExceptionHandler,Job)

(1) 調(diào)度器(Dispatchers)

指定協(xié)程在哪個(gè)線程執(zhí)行赡盘。協(xié)程可以隨時(shí)用withContext()切換線程号枕。

Dispatchers.Default

使用共享的后臺(tái)線程緩存池。默認(rèn)情況下陨享,它使用的最大線程數(shù)等于CPU內(nèi)核數(shù)葱淳,但至少2個(gè)。這個(gè)線程看起來(lái)會(huì)像是Thread[DefaultDispatcher-worker-2,5,main].

Dispatchers.IO

跟Dispatchers.Default共享線程抛姑,但它數(shù)量受kotlinx.coroutines.io.parallelism限制赞厕,默認(rèn)最多是64個(gè)線程或CPU內(nèi)核數(shù)(其中的大值)。跟Dispatchers.Default一樣定硝,線程看起來(lái)像Thread[DefaultDispatcher-worker-1,5,main].

Dispatchers.Main

等效于主線程皿桑。線程看起來(lái)像Thread[main,5,main].

Dispatchers.Unconfined

未指定特定線程的協(xié)程分發(fā)器。協(xié)程在當(dāng)前線程執(zhí)行蔬啡,并讓協(xié)程恢復(fù)到對(duì)應(yīng)的suspending function用過(guò)的任意線程上诲侮。

CoroutineScope(Dispatchers.Unconfined).launch {
    // Writes code here running on Main thread.
    
    delay(1_000)
    // Writes code here running on `kotlinx.coroutines.DefaultExecutor`.
    
    withContext(Dispatchers.IO) { ... }
    // Writes code running on I/O thread.
    
    withContext(Dispatchers.Main) { ... }
    // Writes code running on Main thread.
}

(2) CoroutineExceptionHandler

處理未捕獲的異常。

一般的箱蟆, 未捕獲異常只會(huì)從launch構(gòu)建器創(chuàng)建的協(xié)程中拋出. async構(gòu)建器創(chuàng)建的協(xié)程總是捕獲所有的異常沟绪,并在返回的Deferred對(duì)象中表示.

例子1:不能通過(guò)外層try-catch捕獲IOException()。不能用try-catch包圍整個(gè)協(xié)程作用域空猜,否則應(yīng)用還是會(huì)崩潰绽慈。

try {
  CoroutineScope(Dispatchers.Main).launch {
    doSomething()
  }
} catch (e: IOException) {
  //  無(wú)法捕獲IOException()
  Log.d("demo", "try-catch: $e")
}

private suspend fun doSomething() {
  delay(1_000)
  throw IOException()
}

例子2:用CoroutineExceptionHandler捕獲IOException()。除CancellationException外的其他異常辈毯,如IOException()坝疼,將傳遞給CoroutineExceptionHandler。

// Handles coroutine exception here.
val handler = CoroutineExceptionHandler { _, throwable ->
  Log.d("demo", "handler: $throwable") // Prints "handler: java.io.IOException"
}

CoroutineScope(Dispatchers.Main + handler).launch {
  doSomething()
}

private suspend fun doSomething() {
  delay(1_000)
  throw IOException()
}

例子3:CancellationException()會(huì)被忽略谆沃。

如果協(xié)程拋出CancellationException钝凶,它將會(huì)被忽略(因?yàn)檫@是取消運(yùn)行中的協(xié)程的預(yù)期機(jī)制,所以該異常不會(huì)傳遞給CoroutineExceptionHandler)(譯注:不會(huì)導(dǎo)致崩潰)

// Handles coroutine exception here.
val handler = CoroutineExceptionHandler { _, throwable ->
  // Won't print the log because the exception is "CancellationException()".
  Log.d("demo", "handler: $throwable")
}

CoroutineScope(Dispatchers.Main + handler).launch {
  doSomething()
}

private suspend fun doSomething() {
  delay(1_000)
  throw CancellationException()
}

例子4:用invokeOnCompletion可以獲取所有異常信息管毙。

CancellationException不會(huì)傳遞給CoroutineExceptionHandler腿椎,但當(dāng)該異常發(fā)生時(shí)桌硫,如果我們想打印出某些信息夭咬,可以使用invokeOnCompletion來(lái)獲取。

val job = CoroutineScope(Dispatchers.Main).launch {
  doSomething()
}

job.invokeOnCompletion {
    val error = it ?: return@invokeOnCompletion
    // Prints "invokeOnCompletion: java.util.concurrent.CancellationException".
    Log.d("demo", "invokeOnCompletion: $error")
  }
}

private suspend fun doSomething() {
  delay(1_000)
  throw CancellationException()
}

(3) Job

控制協(xié)程的生命周期铆隘。一個(gè)協(xié)程有如下?tīng)顟B(tài):

job狀態(tài).png

查詢(xún)job的當(dāng)前狀態(tài)很簡(jiǎn)單卓舵,用Job.isActive。

狀態(tài)流圖是:

job狀態(tài)流圖.png
  1. 協(xié)程工作時(shí)job是active態(tài)的
  2. job發(fā)生異常時(shí)將會(huì)變成cancelling. 一個(gè)job可以隨時(shí)用cancel方法取消膀钠,這個(gè)強(qiáng)制使它立刻變?yōu)閏ancelling態(tài)
  3. 當(dāng)job工作完成時(shí)掏湾,會(huì)變成cancelled態(tài)
  4. 父job會(huì)維持在completingcancelling態(tài)直到所有子job完成裹虫。注意completing是一種內(nèi)部狀態(tài),對(duì)外部來(lái)說(shuō)融击,completing態(tài)的job仍然是active的筑公。
(3.1) Parent-child hierarchies(父-子層級(jí))

弄明白狀態(tài)后,我門(mén)還必須知道父-子層級(jí)是如何工作的尊浪。假設(shè)我們寫(xiě)了如下代碼:

val parentJob = Job()
val childJob1 = CoroutineScope(parentJob).launch {
    val childJob2 = launch { ... }
    val childJob3 = launch { ... }
}

則其父子層級(jí)會(huì)長(zhǎng)這樣:

job父子層級(jí).png

我們可以改變父job匣屡,像這樣:

val parentJob1 = Job()
val parentJob2 = Job()
val childJob1 = CoroutineScope(parentJob1).launch {
    val childJob2 = launch { ... }
    val childJob3 = launch(parentJob2) { ... }
}

則父子層級(jí)會(huì)長(zhǎng)這樣:

job父子層級(jí)2.png

基于以上知識(shí),我們需要知道如下一些重要概念:

  • 父job取消將立即導(dǎo)致所有子job取消

    val parentJob = Job()
    CoroutineScope(Dispatchers.Main + parentJob).launch {
        val childJob = launch {
            delay(5_000)
            
            // This function won't be executed because its parentJob is 
            // already cancelled after 1 sec. 
            canNOTBeExcecuted()
        }
        launch {
            delay(1_000)
            parentJob.cancel() // Cancels parent job after 1 sec.
        }
    }
    
  • 當(dāng)某個(gè)子job因?yàn)槌?a target="_blank">CancellationException外的異常而失敗或取消時(shí)拇涤,會(huì)立刻導(dǎo)致所有父job和其他子job取消捣作。但如果是CancellationException,則除該job的子job外的其他jobs不會(huì)受到影響鹅士。

例子1:如果拋出CancellationException券躁,只有childJob1下的job被取消。

val parentJob = Job()
CoroutineScope(Dispatchers.Main + parentJob).launch {
  val childJob1 = launch {
    val childOfChildJob1 = launch {
      delay(2_000)
      // This function won't be executed since childJob1 is cancelled.
      canNOTBeExecuted()
    }
    delay(1_000)
    
    // Cancel childJob1.
    cancel()
  }

  val childJob2 = launch {
    delay(2_000)
    canDoSomethinghHere()
  }

  delay(3_000)
  canDoSomethinghHere()
}

例子2:如果某個(gè)子job拋出IOException掉盅,則所有關(guān)聯(lián)job都會(huì)被取消

val parentJob = Job()
val handler = CoroutineExceptionHandler { _, throwable ->
  Log.d("demo", "handler: $throwable") // Prints "handler: java.io.IOException"
}

CoroutineScope(Dispatchers.Main + parentJob + handler).launch {
  val childJob1 = launch {
    delay(1_000)
    // Throws any exception "other than CancellationException" after 1 sec.
    throw IOException() 
  }

  val childJob2 = launch {
    delay(2_000)
    // The other child job: this function won't be executed.
    canNOTBExecuted()
  }

  delay(3_000)
  // Parent job: this function won't be executed.
  canNOTBExecuted()
}
  • cancelChildren(): 父job可以取消它的所有子job(遞歸到它們的子job)而不取消自己也拜。注意:如果一個(gè)job已取消,則它不能再作為父job運(yùn)行協(xié)程了怔接。

如果我們用Job.cancel()搪泳,父job將會(huì)變成cancelled(當(dāng)前是Cancelling),當(dāng)其所有子job都cancelled后扼脐,父job會(huì)成為cancelled態(tài)岸军。

val parentJob = Job()
val childJob = CoroutineScope(Dispatchers.Main + parentJob).launch {
  delay(1_000)
  
  // This function won't be executed because its parent is cancelled.
  canNOTBeExecuted()
}

parentJob.cancel()

// Prints "JobImpl{Cancelling}@199d143", parent job status becomes "cancelling".
// And will be "cancelled" after all the child job is cancelled.
Log.d("demo", "$parentJob")

而如果我們用Job.cancelChildren(),父job將會(huì)變?yōu)锳ctive態(tài)瓦侮,我們?nèi)匀豢梢杂盟鼇?lái)運(yùn)行其他協(xié)程艰赞。

val parentJob = Job()
val childJob = CoroutineScope(Dispatchers.Main + parentJob).launch {
  delay(1_000)
  
  // This function won't be executed because its parent job is cancelled.
  canNOTBeExecuted()
}

// Only children are cancelled, the parent job won't be cancelled.
parentJob.cancelChildren()

// Prints "JobImpl{Active}@199d143", parent job is still active.
Log.d("demo", "$parentJob")

val childJob2 = CoroutineScope(Dispatchers.Main + parentJob).launch {
  delay(1_000)
  
  // Since the parent job is still active, we could use it to run child job 2.
  canDoSomethingHere()
}
(3.2) SupervisorJob v.s. Job

supervisor job的子job可以獨(dú)立失敗,而不影響其他子job肚吏。

正如前文提到的方妖,如果我們用Job()作為父job,當(dāng)某個(gè)子job失敗時(shí)將會(huì)導(dǎo)致所有子job取消罚攀。

val parentJob = Job()
val handler = CoroutineExceptionHandler { _, _ -> }
val scope = CoroutineScope(Dispatchers.Default + parentJob + handler)
val childJob1 = scope.launch {
    delay(1_000)
    // ChildJob1 fails with the IOException().
    throw IOException()
}

val childJob2 = scope.launch {
    delay(2_000)
    // This line won't be executed due to childJob1 failure.
    canNOTBeExecuted()
}

如果我們使用SupervisorJob()作為父job党觅,則其中一個(gè)子job取消時(shí)不會(huì)影響其他子jobs。

val parentJob = SupervisorJob()
val handler = CoroutineExceptionHandler { _, _ -> }
val scope = CoroutineScope(Dispatchers.Default + parentJob + handler)
val childJob1 = scope.launch {
    delay(1_000)
    // ChildJob1 fails with the IOException().
    throw IOException()
}

val childJob2 = scope.launch {
    delay(2_000)
    // Since we use SupervisorJob() as parent job, the failure of
    // childJob1 won't affect other child jobs. This function will be 
    // executed.
    canDoSomethinghHere()
}

4. 協(xié)程構(gòu)建器(Coroutines Builder)

(1) launch

啟動(dòng)一個(gè)新協(xié)程斋泄,不會(huì)阻塞當(dāng)前線程杯瞻,返回一個(gè)指向當(dāng)前協(xié)程的Job引用。

(2) async and await

async協(xié)程構(gòu)建器是CoroutineScope的拓展方法炫掐。它創(chuàng)建一個(gè)協(xié)程魁莉,并以Deferred實(shí)現(xiàn)來(lái)返回它的未來(lái)結(jié)果,這是一個(gè)非阻塞的可取消future——一個(gè)帶結(jié)果的Job

Async協(xié)程搭配await使用:不阻塞當(dāng)前線程的前提下持續(xù)等待結(jié)果旗唁,并在可延遲的任務(wù)完成后恢復(fù)(resume)畦浓,返回結(jié)果,或者如果deferred被取消了检疫,拋出相應(yīng)的異常讶请。

下列代碼展示了兩個(gè)suspending functions的串行調(diào)用。在fetchDataFromServerOne()和fetchDataFromServerTwo()中屎媳,我們做了一些耗時(shí)任務(wù)秽梅,分別耗時(shí)1秒。在launch構(gòu)建器里調(diào)用它們剿牺,會(huì)發(fā)現(xiàn)最終的耗時(shí)是它們的和:2秒企垦。

override fun onCreate(savedInstanceState: Bundle?) {
  ...

  val scope = MainScope()
  scope.launch {
    val time = measureTimeMillis {
      val one = fetchDataFromServerOne()
      val two = fetchDataFromServerTwo()
      Log.d("demo", "The sum is ${one + two}")
    }
    Log.d("demo", "Completed in $time ms")
  }
}

private suspend fun fetchDataFromServerOne(): Int {
  Log.d("demo", "fetchDataFromServerOne()")
  delay(1_000)
  return 1
}
  
private suspend fun fetchDataFromServerTwo(): Int {
  Log.d("demo", "fetchDataFromServerTwo()")
  delay(1_000)
  return 2
}

日志是:

2019-12-09 00:00:34.547 D/demo: fetchDataFromServerOne()
2019-12-09 00:00:35.553 D/demo: fetchDataFromServerTwo()
2019-12-09 00:00:36.555 D/demo: The sum is 3
2019-12-09 00:00:36.555 D/demo: Completed in 2008 ms

耗時(shí)是兩個(gè)suspending functions延時(shí)的和。該協(xié)程在fetchDataFromServerOne()結(jié)束前會(huì)中斷(suspend)晒来,然后執(zhí)行fetchDataFromServerTwo()钞诡。

如果我們想同時(shí)運(yùn)行兩個(gè)方法以減少耗時(shí)呢?Async閃亮登場(chǎng)湃崩!Async和launch很像荧降。它啟動(dòng)一個(gè)可以和其他協(xié)程同時(shí)運(yùn)行的新協(xié)程,返回Deferred引用——一個(gè)帶返回值的Job攒读。

public interface Deferred<out T> : Job {
  public suspend fun await(): T
  ...
}

在Deferred上調(diào)用await()獲取結(jié)果朵诫,例如:

override fun onCreate(savedInstanceState: Bundle?) {
  ...
  
  val scope = MainScope()
  scope.launch {
    val time = measureTimeMillis {
      val one = async { fetchDataFromServerOne() }
      val two = async { fetchDataFromServerTwo() }
      Log.d("demo", "The sum is ${one.await() + two.await()}")
    }
    
    // Function one and two will run asynchrously,
    // so the time cost will be around 1 sec only. 
    Log.d("demo", "Completed in $time ms")
  }
}

private suspend fun fetchDataFromServerOne(): Int {
  Log.d("demo", "fetchDataFromServerOne()")
  delay(1_000)
  return 1
}

private suspend fun fetchDataFromServerTwo(): Int {
  Log.d("demo", "fetchDataFromServerTwo()")
  Thread.sleep(1_000)
  return 2
}

日志是:

2019-12-08 23:52:01.714 D/demo: fetchDataFromServerOne()
2019-12-08 23:52:01.718 D/demo: fetchDataFromServerTwo()
2019-12-08 23:52:02.722 D/demo: The sum is 3
2019-12-08 23:52:02.722 D/demo: Completed in 1133 ms

5. 協(xié)程體(Coroutine body)

在CoroutineScope中運(yùn)行的代碼,包括常規(guī)函數(shù)或掛起函數(shù)——掛起函數(shù)在結(jié)束前會(huì)中斷協(xié)程薄扁,下篇博客將會(huì)詳述剪返。

今天就到這里啦。下篇博客將會(huì)深入介紹掛起函數(shù)及其用法邓梅。 Android中的Kotlin協(xié)程-掛起函數(shù).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脱盲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子日缨,更是在濱河造成了極大的恐慌钱反,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匣距,死亡現(xiàn)場(chǎng)離奇詭異面哥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)毅待,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)尚卫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恩静,你說(shuō)我怎么就攤上這事焕毫。” “怎么了驶乾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵邑飒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我级乐,道長(zhǎng)疙咸,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任风科,我火速辦了婚禮撒轮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贼穆。我一直安慰自己题山,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布故痊。 她就那樣靜靜地躺著顶瞳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愕秫。 梳的紋絲不亂的頭發(fā)上慨菱,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音戴甩,去河邊找鬼符喝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甜孤,可吹牛的內(nèi)容都是我干的协饲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缴川,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼囱稽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起二跋,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤战惊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扎即,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體吞获,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年谚鄙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了各拷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闷营,死狀恐怖烤黍,靈堂內(nèi)的尸體忽然破棺而出知市,到底是詐尸還是另有隱情,我是刑警寧澤速蕊,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布嫂丙,位于F島的核電站,受9級(jí)特大地震影響规哲,放射性物質(zhì)發(fā)生泄漏跟啤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一唉锌、第九天 我趴在偏房一處隱蔽的房頂上張望隅肥。 院中可真熱鬧,春花似錦袄简、人聲如沸腥放。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捉片。三九已至,卻和暖如春汞舱,著一層夾襖步出監(jiān)牢的瞬間伍纫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工昂芜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莹规,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓泌神,卻偏偏與公主長(zhǎng)得像良漱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子欢际,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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