知識點(diǎn) | ViewModel 四種集成方式

ViewModel 甫一發(fā)布炭菌,便成為了 Jetpack 中的核心組件之一醇王。我們在 2019 年做的一份開發(fā)者問卷顯示敌卓,超過 40% 的 Android 開發(fā)者已經(jīng)在自己的應(yīng)用中使用了 ViewModel遂黍。ViewModel 可以將數(shù)據(jù)層與 UI 分離俐填,而這種架構(gòu)不僅可以簡化 UI 的生命周期的控制安接,也能讓代碼獲得更好的可測試性。如果想了解更多英融,可以參考 ViewModel: 簡單介紹視頻官方文檔盏檐。

由于 ViewModel 是許多功能實(shí)現(xiàn)的基礎(chǔ),我們在過去的幾年里做了許多工作來改進(jìn) ViewModel 的易用性驶悟,也讓它能夠更加簡便地與其他組件庫相結(jié)合胡野。下面的文章中,我將介紹 ViewModel 的四種集成方式:

  • ViewModel 中的 Saved State —— 后臺進(jìn)程重啟時痕鳍,ViewModel 的數(shù)據(jù)恢復(fù)硫豆;
  • 在 NavGraph 中使用 ViewModel —— ViewModel 與導(dǎo)航 (Navigation) 組件庫的集成龙巨;
  • ViewModel 配合數(shù)據(jù)綁定(data-binding) —— 通過使用 ViewModel 和 LiveData 簡化數(shù)據(jù)綁定;
  • viewModelScope —— Kotlin 協(xié)程與 ViewModel 的集成熊响。

ViewModel 的 Saved State —— 后臺進(jìn)程重啟時旨别,ViewModel 的數(shù)據(jù)恢復(fù)

  • 于 lifecycle-viewmodel-savedstate 的 1.0.0-alpha01 版本時加入
  • 支持 Java 和 Kotlin

onSaveInstanceState 帶來的挑戰(zhàn)
ViewModel 一發(fā)布,執(zhí)行 onSaveInstanceState 的相關(guān)的邏輯時要如何操作 ViewModel汗茄,便成為了一個令人困惑的問題秸弛。Activity 和 Fragment 通常會在下面三種情況下被銷毀:

  1. 從當(dāng)前界面永久離開: 用戶導(dǎo)航至其他界面或直接關(guān)閉 Activity (通過點(diǎn)擊返回按鈕或執(zhí)行的操作調(diào)用了 finish() 方法)。對應(yīng) Activity 實(shí)例被永久關(guān)閉洪碳;
  2. Activity 配置 (configuration) 被改變: 例如递览,旋轉(zhuǎn)屏幕等操作,會使 Activity 需要立即重建瞳腌;
  3. 應(yīng)用在后臺時绞铃,其進(jìn)程被系統(tǒng)殺死: 這種情況發(fā)生在設(shè)備剩余運(yùn)行內(nèi)存不足,系統(tǒng)又亟須釋放一些內(nèi)存的時候纯趋。當(dāng)進(jìn)程在后臺被殺死后憎兽,用戶又返回該應(yīng)用時,Activity 也需要被重建吵冒。

在后兩種情況中纯命,我們通常都希望重建 Activity。ViewModel 會幫您處理第二種情況痹栖,因?yàn)樵谶@種情況下 ViewModel 沒有被銷毀亿汞;而在第三種情況下, ViewModel 被銷毀了揪阿。所以一旦出現(xiàn)了第三種情況疗我,便需要在 Activity 的 onSaveInstanceState 相關(guān)回調(diào)中保存和恢復(fù) ViewModel 中的數(shù)據(jù)。我在 ViewModels: 持久化南捂、onSaveInstanceState()吴裤、恢復(fù) UI 狀態(tài)與加載器一文中更加詳細(xì)地描述了這兩種情況的區(qū)別。

Saved State 模塊

現(xiàn)在溺健,ViewModel Saved State 模塊將會幫您在應(yīng)用進(jìn)程被殺死時恢復(fù) ViewModel 的數(shù)據(jù)麦牺。在免除了與 Activity 繁瑣的數(shù)據(jù)交換后,ViewModel 也真正意義上的做到了管理和持有所有自己的數(shù)據(jù)鞭缭。

ViewModel 的這一新功能是通過 SavedStateHandle 實(shí)現(xiàn)的剖膳。SavedStateHandle 和 Bundle 一樣,以鍵值對形式存儲數(shù)據(jù)岭辣,它包含在 ViewModel 中吱晒,并且可以在應(yīng)用處于后臺時進(jìn)程被殺死的情況下幸存下來。諸如用戶 id 等需要在 onSaveInstanceState 時得到保存下來的數(shù)據(jù)沦童,現(xiàn)在都可以存在 SavedStateHandle 中仑濒。

設(shè)置 Save State 模塊

現(xiàn)在讓我們看看如何使用 SaveState 組件叹话。注意接下來的代碼會和 Lifecycles Codelab 第六步中的一段代碼十分相似。那段是 Java 代碼躏精,而接下來的是 Kotlin 代碼:

第一步: 添加依賴
SaveStateHandle 目前在一個獨(dú)立的模塊中渣刷,您需要在依賴中添加:

def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedsta

注意,本文發(fā)布時 lifecycle 組件的最新穩(wěn)定版為 2.2.0矗烛,如果您希望持續(xù)關(guān)注相關(guān)組件庫的進(jìn)展辅柴,可以查看 lifecycle 版本發(fā)布文檔

第二步: 修改調(diào)用 ViewModelProvider 的方式

接下來瞭吃,您需要創(chuàng)建一個持有 SaveStateHandle 的 ViewModel碌嘀。在 Activity 或 Fragment 的 onCreate 方法中,將 ViewModelProvider 的調(diào)用修改為:


//下面的 Kotlin 擴(kuò)展需要依賴以下或更新新版本的 ktx 庫:
//androidx.fragment:fragment-ktx:1.0.0(最新版本 1.2.4) 或
//androidx.activity:activity-ktx:1.0.0 (最新版本 1.1.0)
val viewModel by viewModels { SavedStateViewModelFactory(application, this) }
// 或者不使用 ktx
val viewModel = ViewModelProvider(this, SavedStateViewModelFactory(application, this))
            .get(MyViewModel::class.java)

創(chuàng)建 ViewModel 的類是 ViewModel 工廠 (ViewModel factory)歪架,而創(chuàng)建包含 SaveStateHandle 的 View Model 的工廠類是 SavedStateViewModelFactory股冗。通過此工廠創(chuàng)建的 ViewModel 將持有一個基于傳入 Activity 或 Fragment 的 SaveStateHandle。

第三步: 使用 SaveStateHandle

當(dāng)前面的步驟準(zhǔn)備完成時和蚪,您就可以在 ViewModel 中使用 SavedStateHandle 了止状。下面是一個保存用戶 ID 的示例:


class MyViewModel(state :SavedStateHandle) :ViewModel() {

    // 將Key聲明為常量
    companion object {
        private val USER_KEY = "userId"
    }

    private val savedStateHandle = state

    fun saveCurrentUser(userId: String) {
        // 存儲 userId 對應(yīng)的數(shù)據(jù)
        savedStateHandle.set(USER_KEY, userId)
    }

    fun getCurrentUser(): String {
        // 從 saveStateHandle 中取出當(dāng)前 userId
        return savedStateHandle.get(USER_KEY)?: ""
    }
}
  1. 構(gòu)造方法: SavedStateHandle 作為構(gòu)造方法參數(shù)傳入 MyViewModel;
  2. 保存: saveNewUser 方法展示了使用鍵值對的形式保存 USER_KEY 和 userId 到 SaveStateHandle 的例子攒霹。每當(dāng)數(shù)據(jù)更新時怯疤,要保存新的數(shù)據(jù)到 SavedStateHandle;
  3. 獲取: 如代碼中所示催束,調(diào)用 savedStateHandle.get(USER_KEY) 方法獲取被保存的 userId集峦。

現(xiàn)在,無論是第二還是第三種情況下抠刺,SavedStateHandle 都可以幫您恢復(fù)界面數(shù)據(jù)了塔淤。

如果您想要在 ViewModel 中使用 LiveData,可以調(diào)用 SavedStateHandle.getLiveData()速妖,示例如下:

// getLiveData 方法會取得一個與 key 相關(guān)聯(lián)的 MutableLiveData 
// 當(dāng)與 key 相對應(yīng)的 value 改變時 MutableLiveData 也會更新高蜂。
private val _userId : MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)

// 只暴露一個不可變 LiveData 的情況
val userId : LiveData<String> = _userId

如需了解更多,請移步至 Lifecycles Codelab 第六步官方文檔罕容。

ViewModel 與 Jetpack 導(dǎo)航: 在 NavGraph 中使用 ViewModel

  • 于 navigation 的 2.1.0-rc01 版本時加入
  • 支持 Java 與 Kotlin

共享 ViewModel 數(shù)據(jù)所帶來的挑戰(zhàn)
Jetpack 導(dǎo)航組件 (Navigation) 十分適用于那些只有少量或一個 Activity妨马,但是 Activity 中會包含多個 Fragment 的應(yīng)用。Ian Lake 在他的演講: 單 Activity 架構(gòu): 為什么杀赢、什么情況下以及如何使用中介紹了一些我們選擇單一 Activity 架構(gòu)的原因,而與本文相關(guān)的一點(diǎn)湘纵,是這種架構(gòu)允許在多個界面 (destination) 間共享 ActivityViewModel脂崔。您可以用 Activity 創(chuàng)建一個 ViewModel 實(shí)例,然后從這個 Activity 中的任一個 Fragment 中獲得 ViewModel 的引用:

// 在Fragment的 onCreate 或 onActivityCreated 方法中執(zhí)行
// 這個Kotlin擴(kuò)展需要依賴最KTX庫:androidx.fragment:fragment-ktx:1.1.0
val sharedViewModel: ActivityViewModel by activityViewModels()

假設(shè)我們有這樣一個單 Activity 應(yīng)用梧喷,它包含了八個 Fragment砌左,其中四個 Fragment 是購買支付流程:
△ 包含一些購買支付流程的導(dǎo)航圖 (Navigation Graph)

這四個頁面需要共享一些諸如收貨地址脖咐、是否使用了優(yōu)惠券等信息。按照前面所講的做法汇歹,需要共享的數(shù)據(jù)會放在一個 ActivityViewModel 中屁擅,但這同時也意味著所有八個頁面都會共享這些數(shù)據(jù)。支付流程外的界面并不需要關(guān)心這些數(shù)據(jù)产弹,這么做顯然并不合適派歌。

ViewModel 與 NavGraph 集成
Navigation 2.1.0 中引入了依托一個導(dǎo)航圖 (navigation graph) 創(chuàng)建 ViewModel 的功能。在使用時痰哨,您需要先把一個界面集合 (例如: 登錄流程胶果、支付流程的相關(guān)界面),放到一個嵌套導(dǎo)航圖 (nested navigation graph) 中斤斧。此時再通過嵌套導(dǎo)航圖創(chuàng)建出 ViewModel早抠,便可以在相關(guān)界面中共享數(shù)據(jù)了。
想要創(chuàng)建嵌套導(dǎo)航圖撬讽,您需要選中對應(yīng)流程相關(guān)的界面蕊连,點(diǎn)擊鼠標(biāo)右鍵,并選擇 Nested Graph → New Graph:

△ 創(chuàng)建嵌套導(dǎo)航圖的截圖

注意嵌套導(dǎo)航圖在 XML 文件中的 id游昼,在這里是 checkout_graph:


<navigation app:startDestination="@id/homeFragment" ...>
    <fragment android:id="@+id/homeFragment" .../>
    <fragment android:id="@+id/productListFragment" .../>
    <fragment android:id="@+id/productFragment" .../>
    <fragment android:id="@+id/bargainFragment" .../>

    <navigation 
      android:id="@+id/checkout_graph" 
      app:startDestination="@id/cartFragment">

        <fragment android:id="@+id/orderSummaryFragment".../>
        <fragment android:id="@+id/addressFragment" .../>
        <fragment android:id="@+id/paymentFragment" .../>
        <fragment android:id="@+id/cartFragment" .../>

    </navigation>

</navigation>

以上工作完成時甘苍,便可以使用 by navGraphViewModels 獲取到對應(yīng)的 ViewModel:


val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

Java 中同樣適用,代碼如下:


public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 設(shè)置其他 fragment 
    NavController navController = NavHostFragment.findNavController(this);

    ViewModelProvider viewModelProvider = new ViewModelProvider(this,
        navController.getViewModelStore(R.id.checkout_graph));

    CheckoutViewModel viewModel = viewModelProvider.get(CheckoutViewModel.class);

    // 使用 Checkout ViewModel
}

需要注意的是酱床,嵌套導(dǎo)航圖相對于導(dǎo)航圖的其他部分是一個獨(dú)立的整體羊赵。您無法導(dǎo)航至嵌套導(dǎo)航圖中包含的某個特定界面;當(dāng)您導(dǎo)航至一個嵌套導(dǎo)航圖時扇谣,打開的只會是其中的開始界面 (startDestination)昧捷。這種特性使得嵌套導(dǎo)航圖適合用于封裝特定流程的界面組合,比如前面提到過的登錄和支付流程罐寨。

ViewModel 與 NavGraph 的集成靡挥,是 2019 年 I/O 大會所發(fā)布的關(guān)于 Navigation 框架的新特性之一。

詳細(xì)了解更多鸯绿,請參閱:

ViewModel 與 Data Binding: 在 Data Binding 中使用 ViewModel 和 LiveData

  • 于 Android Studio 的 3.1 版本時加入
  • 支持 Java 與 Kotlin

移除 LiveData 相關(guān)的模板代碼

ViewModel跋破、LiveData 與 Data Binding 的集成方式并不是什么新功能,但它始終非常好用瓶蝴。ViewModel 通常都包含一些 LiveData毒返,而 LiveData 意味著可以被監(jiān)聽。所以最常見的使用場景是在 Fragment 中給 LiveData 添加一個觀察者:


override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    myViewModel.name.observe(this, { newName ->
        // 更新UI舷手,這里是一個TextView
        nameTextView.text = newName
    })

}

Data Binding 是一個通過觀察數(shù)據(jù)變化來更新 UI 的組件庫拧簸。通過 ViewModel、LiveData 和 Data Binding 的組合男窟,您可以移除以往給 LiveData 添加觀察者的做法盆赤,改為直接在 XML 中綁定 View Model 和 LiveData贾富。

使用 Data Binding、ViewModel 和 LiveData

假設(shè)您希望在 XML 布局文件中引用 ViewModel:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewmodel"
type="com.android.MyViewModel"/>
</data>
<... Rest of your layout ...>
</layout>

調(diào)用 binding.setLifecycleOwner(this) 方法牺六,然后將 ViewModel 傳遞給 binding 對象颤枪,就可以將 LiveData 與 Data Binding 結(jié)合起來:


class MainActivity : AppCompatActivity() {

    // 這個ktx擴(kuò)展需要依賴 androidx.activity:activity-ktx:1.0.0
    // 或更新版本
    private val myViewModel: MyViewModel by viewModels()

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

         //填充視圖并創(chuàng)建 Data Binding 對象
        val binding: MainActivityBinding = 
            DataBindingUtil.setContentView(this, R.layout.main_activity)

        //聲明這個 Activity 為 Data Binding 的 lifecycleOwner
        binding.lifecycleOwner = this

        // 將 ViewModel 傳遞給 binding
        binding.viewmodel = myViewModel
    }
}

現(xiàn)在,您可以像下面這樣使用 ViewModel:


<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="viewmodel" 
                  type="com.android.MyViewModel"/>
    </data>
    <TextView
            android:id="@+id/name"
            android:text="@{viewmodel.name}"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"/>
</layout>

注意淑际,這里的 viewmodel.name 既可以是 String 類型畏纲,也可以是 LiveData。如果它是 LiveData庸追,那么 UI 將根據(jù) LiveData 值的改變自動刷新霍骄。

ViewMode 與 Kotlin 協(xié)程: viewModelScope

  • 于 Lifecycle 的 2.1.0 版本時加入
  • 只支持 Kotlin

Android 平臺上的協(xié)程
通常情況下,我們使用回調(diào) (Callback) 處理異步調(diào)用淡溯,這種方式在邏輯比較復(fù)雜時读整,會導(dǎo)致回調(diào)層層嵌套,代碼也變得難以理解咱娶。Kotlin 協(xié)程 (Coroutines) 同樣適用于處理異步調(diào)用米间,它讓邏輯變得簡單的同時,也確保了操作不會阻塞主線程膘侮。如果您不了解協(xié)程屈糊,這里有一系列很棒的博客《在 Android 開發(fā)中使用協(xié)程》以及 codelab: 在 Android 應(yīng)用中使用 Kotlin 協(xié)程以供參考。
一段簡單的協(xié)程代碼:


// 下面是示例代碼琼了,真實(shí)情景下不要使用 GlobalScope 
GlobalScope.launch {
    longRunningFunction()
    anotherLongRunningFunction()
}

這段示例代碼只啟動了一個協(xié)程逻锐,但我們在真實(shí)的使用環(huán)境下很容易創(chuàng)建出許多協(xié)程,這就難免會導(dǎo)致有些協(xié)程的狀態(tài)無法被跟蹤雕薪。如果這些協(xié)程中剛好有您想要停止的任務(wù)時昧诱,就會導(dǎo)致任務(wù)泄漏 (work leak)。

為了防止任務(wù)泄漏所袁,您需要將協(xié)程加入到一個 CoroutineScope 中盏档。CoroutineScope 可以持續(xù)跟蹤協(xié)程的執(zhí)行,它可以被取消燥爷。當(dāng) CoroutineScope 被取消時蜈亩,它所跟蹤的所有協(xié)程都會被取消。上面的代碼中前翎,我使用了 GlobalScope稚配,正如我們不推薦隨意使用全局變量一樣,這種方式通常不推薦使用港华。所以药有,如果想要使用協(xié)程,您要么限定一個作用域 (scope),要么獲得一個作用域的訪問權(quán)限愤惰。而在 ViewModel 中,我們可以使用 viewModelScope 來管理協(xié)程的作用域赘理。

viewModelScope

當(dāng) ViewModel 被銷毀時宦言,通常都會有一些與其相關(guān)的操作也應(yīng)當(dāng)被停止。

例如商模,假設(shè)您正在準(zhǔn)備將一個位圖 (bitmap) 顯示到屏幕上奠旺。這種操作就符合我們前面提到的一些特征: 既不能在執(zhí)行時阻塞主線程,又要求在用戶退出相關(guān)界面時停止執(zhí)行施流。使用協(xié)程進(jìn)行此類操作時响疚,就應(yīng)當(dāng)使用 viewModelScope

viewModelScope 是一個 ViewModel 的 Kotlin 擴(kuò)展屬性瞪醋。正如前面所說忿晕,它能在 ViewModel 銷毀時 (onCleared() 方法調(diào)用時) 退出。這樣一來银受,只要您使用了 ViewModel践盼,您就可以使用 viewModelScope 在 ViewModel 中啟動各種協(xié)程,而不用擔(dān)心任務(wù)泄漏宾巍。

示例如下:


class MyViewModel() : ViewModel() {

    fun initialize() {
        viewModelScope.launch {
            processBitmap()
        }
    }

    suspend fun processBitmap() = withContext(Dispatchers.Default) {
        // 在這里做耗時操作
    }

}

詳細(xì)了解更多咕幻,請參閱:

總結(jié)

本文中,我們講了:

  1. ViewModel 使用 SaveStateHandle 組件處理 onSaveInstanceState 相關(guān)邏輯顶霞;
  2. 通過配合 View Model 和導(dǎo)航圖來精確限定數(shù)據(jù)在 Fragment 中的共享范圍肄程;
  3. 使用 DataBinding 庫時,可以將 ViewModel 傳遞給數(shù)據(jù)綁定 (binding)选浑,如果同時有在 ViewModel 中使用 LiveData蓝厌,則可以通過 binding.setLifecycleOwner(lifecycleOwner) 讓 UI 根據(jù) LiveData 自動更新;
  4. 在 ViewModel 中使用 Kotlin 協(xié)程時鲜侥,使用 viewModelScope 來讓協(xié)程在 ViewModel 被銷毀時自動取消褂始。

以上這些功能很多都來自社區(qū)提交的請求和反饋,如果您正在尋找 ViewModel 相關(guān)的功能描函,可以留意功能需求列表或者考慮提交自己的需求崎苗。

如果您想了解架構(gòu)組件和 Android Jetpack 的最新進(jìn)展,請關(guān)注 Android 開發(fā)者博客舀寓,并留意 AndroidX 發(fā)布文檔胆数。

如果您對這些功能仍有疑問,可以在下方留言互墓。感謝閱讀必尼!
點(diǎn)擊這里查看 Android 官方中文文檔 —— ViewModel 概覽

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子判莉,更是在濱河造成了極大的恐慌豆挽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件券盅,死亡現(xiàn)場離奇詭異帮哈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锰镀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門娘侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泳炉,你說我怎么就攤上這事憾筏。” “怎么了花鹅?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵氧腰,是天一觀的道長。 經(jīng)常有香客問我翠胰,道長容贝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任之景,我火速辦了婚禮斤富,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锻狗。我一直安慰自己满力,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布轻纪。 她就那樣靜靜地躺著油额,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻帚。 梳的紋絲不亂的頭發(fā)上潦嘶,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音崇众,去河邊找鬼掂僵。 笑死,一個胖子當(dāng)著我的面吹牛顷歌,可吹牛的內(nèi)容都是我干的锰蓬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼眯漩,長吁一口氣:“原來是場噩夢啊……” “哼芹扭!你這毒婦竟也來了麻顶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤舱卡,失蹤者是張志新(化名)和其女友劉穎辅肾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轮锥,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宛瞄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了交胚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡盈电,死狀恐怖蝴簇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匆帚,我是刑警寧澤熬词,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站吸重,受9級特大地震影響互拾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚎幸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一颜矿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫉晶,春花似錦骑疆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至椎镣,卻和暖如春诈火,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背状答。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工冷守, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剪况。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓教沾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親译断。 傳聞我的和親對象是個殘疾皇子授翻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345