ViewModel

随着 Android 架构的演进,从 MVC 到 MVP 再到现在的 MVVM,项目的结构越来越清晰,耦合度也越来越低,本质上讲就是对 UI 和逻辑的分离,而在这一分离的过程中,MVP 的 presenter 和 MVVM 中的ViewModel 都起了很重要的作用,Presenter 不必多说,就是一个类封装了我们的逻辑代码,并加了一些回调。我们要讲的是 ViewModel 如何创建使用,如何和页面生命周期绑定以及如何在配置更改时恢复数据。

1.what?


ViewModelLiveData 是组成 Jetpack 的一部分,在 MVVM 架构中充当着相当重要的角色。

  • ViewModel 旨在以注重生命周期的方式存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存,所以 ViewModel 在 MVVM 中担当的是一个数据持有者的角色,为 Activity 、Fragment 存储数据,在配置更改的时候恢复数据,其次因为 ViewModel 存储了数据,所以 ViewModel 可以在当前 Activity的 Fragment 中实现数据共享。

  • LiveData 作为ViewModel的好基友,是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData所持有的数据改变时,它会通知相应的组件进行更新。同时,LiveData 持有界面代码 Lifecycle的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。它的优点:不用手动控制生命周期,不用担心内存泄露,数据变化时会收到通知.

2.How?


2.1 基本用法

我们先看看 ViewModel 是怎么使用的(虽然大家都比较熟悉)。首先,我们创建一个ViewModel子类,类里面有一个 LiveData 对象:

class MyViewModel : ViewModel() {
    val mNameLiveData = MutableLiveData()
}

然后我们在 Activity 里面使用它:

class MainActivity : AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val textView = findViewById<TextView>(R.id.textView)
        val viewModel by viewModel<MyViewModel>()
        viewModel.mNameLiveData.observe(this, Observer {
            textView.text = it
        })
    }
}

例子非常简单,这里就不过多的介绍。需要提一句的是,在最新的ViewModel中,以前通过ViewModelProviders.of 方法来获取 ViewModel 已经废弃了,现在我们是通过 ViewModelProvider Factory 创建 ViewModel 对象,因此需要往 ViewModelProider 构造方法里面传递一个工厂类对象,如下:

class MyViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return modelClass.getConstructor().newInstance()
    }
}

当然,我们可以不带 Factory 对象。那么加入 Factory 对象之后,相比较于以前有什么好处呢?加了 Factory 之后,我们可以定义构造方法带参的 ViewModel。比如说,我们的一个 ViewModel 构造方法需要带一个 id 参数,那么我们可以在 Factory 的 create 方法里面创建对象直接带进去。

我们还可以根据提供的参数使用 lazyMap 或类似的 lazy init。当参数是字符串或其他不可变类时,很容易将它们用作映射的键,以获取与提供的参数相对应的 LiveData。

class Books(val names: List<String>)

data class Parameters(val namePrefix: String = "")/*只为示范*/

class GetBooksCase {
   fun loadBooks(parameters: Parameters, onLoad: (Books) -> Unit) { /* Implementation detail */
   }
}
class BooksViewModel(val getBooksCase: GetBooksCase) : ViewModel() {
    private val booksLiveData: Map<Parameters, LiveData<Books>> = lazyMap { parameters ->
        val liveData = MutableLiveData<Books>()
        getBooksCase.loadBooks(parameters) { 
          liveData.value = it 
        }
        return@lazyMap liveData
    }
    fun books(parameters: Parameters): LiveData<Books> = booksLiveData.getValue(parameters)
}
fun <K, V> lazyMap(initializer: (K) -> V): Map<K, V> {
    val map = mutableMapOf<K, V>()
    return map.withDefault { key ->
        val newValue = initializer(key)
        map[key] = newValue
        return@withDefault newValue
    }
}

在上面使用 lazy map 的时候,我们只使用 map 来传递参数,但在许多情况下,ViewModel 的一个实例将始终具有相同的参数。这时候最好将参数传递给构造函数,并在构造函数中使用 lazy load 或 start load。

class BooksViewModel(val getBooksCase: GetBooksCase, parameters: Parameters) : ViewModel() {
    private val booksLiveData: LiveData<Books> by lazy {
        val liveData = MutableLiveData<Books>()
        getBooksCase.loadBooks(parameters) { 
          liveData.value = it 
        }
        return@lazy liveData
    }
    fun books(parameters: Parameters): LiveData<Books> = booksLiveData
}
class BooksViewModelFactory(val getBooksCase: GetBooksCase, val parameters: Parameters) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return BooksViewModel(getBooksCase, parameters) as T
    }
}

切记,我们不要自己创建 ViewModel 对象,因为自己创建的对象不能保存因为配置更改导致 Activity 重建的数据,从而完美避开了 ViewModel 的优点。

2.2 DataBinding 中使用 ViewModel 和 LiveData

ViewModel、LiveData 与 DataBinding 并不是什么新功能,但非常好用(但因为一些 DataBinding 出了问题全局报错不好定位的原因,被众大佬诟病甚至弃用)。ViewModel 通常都包含一些 LiveData,而 LiveData 意味着可以被监听。在 XML 布局文件中使用ViewModel时,调用 binding.setLifecycleOwner(this) 方法,然后将 ViewModel 传递给 binding 对象,就可以将 LiveData 与 Data Binding 结合起来:

class MainActivity : AppCompatActivity() {

    private val myViewModel: MyViewModel by lazy {
        ViewModelProvider(
            this,
            MyViewModelFactory()
        )[MyViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: MainActivityBinding = DataBindingUtil.setContentView(this, R.layout.main_activity)
        binding.lifecycleOwner = this
        // 将 ViewModel 传递给 binding
        binding.viewmodel = myViewModel
    }
}

XML 布局文件中使用 ViewModel:

<layout>
    <data>
        <variable
            name="viewModel"
            type="com.gxj.test.MyViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.text}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

注意,这里的 viewModel.text 可以是 String 类型,也可以是 LiveData。如果它是 LiveData,那么 UI 将根据 LiveData 值的改变自动刷新。

2.3 ViewMode 与 Kotlin 协程: viewModelScope

通常情况下,我们使用回调 (Callback) 处理异步调用,这种方式在逻辑比较复杂时,会导致回调嵌套地狱,代码也变得难以理解。而协程同样适用于处理异步调用,它能够让逻辑变得简单的同时,也确保了操作不会阻塞主线程。一段简单的协程代码,真实情景下不要使用:

GlobalScope.launch {   
    longRunningFunction()    
    longRunningFunction1()
}

这段代码只启动了一个协程,但我们在真实的使用环境下很容易创建出许多协程,这就难免会导致有些协程的状态无法被跟踪。如果这些协程中刚好有您想要停止的任务时,就会导致任务泄漏。而为了防止任务泄漏,需要将协程加入到一个 CoroutineScope 中,它可以持续跟踪协程的执行,也可以被取消。当 CoroutineScope 被取消时,它所跟踪的所有协程都会被取消。上面的代码中,我使用了GlobalScope,正如我们不推荐随意使用全局变量一样,这种方式通常不推荐使用。所以,如果想要使用协程,要么限定一个作用域 (scope),要么获得一个作用域的访问权限。而在 ViewModel 中,我们可以使用 viewModelScope 来管理协程的作用域,它是一个ViewModel 的 kotlin 扩展属性,当 ViewModel 被销毁时,通常都会有一些与其相关的操作也应当被停止。

举个栗子,当我们要加载一个文件的时候: 既要做到不能在执行时阻塞主线程,又要求在退出相关界面时停止加载。当使用协程进行耗时操作时,就应当使用 viewModelScope, ,它能在 ViewModel 销毁时 (onCleared()方法调用时) 退出。这样我们就可以在 ViewModel 的 viewModelScope 中启动各种协程,而不用担心任务泄漏。

示例如下:

class MyViewModel() : ViewModel() {

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

    suspend fun processLoadFile() = withContext(Dispatchers.Default) {
        // 在这里做耗时操作
    }
}

2.4 ViewModel 的 Saved State

  1. onSaveInstanceState 带来的挑战

我们知道 Activity 和 Fragment 通常会在下面三种情况下被销毁:

  1. 从当前界面永久离开: 用户导航至其他界面或直接关闭 Activity (通过点击返回按钮或执行的操作调用了 finish() 方法)。对应 Activity 实例被永久关闭;
  2. Activity 配置被改变: 例如,旋转屏幕等操作,会使 Activity 需要立即重建;
  3. 应用在后台时,其进程被系统杀死: 这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,Activity 也需要被重建。

在后两种情况中,我们通常都希望重建 Activity。ViewModel 会处理第二种情况,因为在这种情况下 ViewModel 没有被销毁;而在第三种情况下, ViewModel 被销毁了。所以一旦出现了第三种情况,便需要在 Activity 的 onSaveInstanceState 相关回调中保存和恢复 ViewModel 中的数据。

  1. Saved State 模块

ViewModel 保存和恢复的数据范围仅限于配置更改导致的重建,并不支持因为资源限制导致 Activity 重建的情况。但是,大家对此的呼声却从来没有停歇,Google 因此新增了一个 SavedStateHandle 类,用来满足我们的要求。该模块会在应用进程被杀死时恢复 ViewModel 的数据。在免除了与 Activity 繁琐的数据交换后,ViewModel 也真正意义上的做到了管理和持有所有自己的数据。

SavedStateHandle 和 Bundle 一样,以键值对形式存储数据,它包含在 ViewModel 中,并且可以在应用处于后台时进程被杀死的情况下幸存下来。诸如用户 id 等需要在 onSaveInstanceState 时得到保存下来的数据,现在都可以存在 SavedStateHandle 中。

  1. 使用Save State模块
  • 添加依赖

    SaveStateHandle 目前在一个独立的模块中,所以需要在依赖中添加:

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"

  • 修改调用 ViewModelProvider 的方式

创建一个 SaveStateHandle 的ViewModel,在 onCreate() 方法中将 ViewModelProvider 的调用修改为:

class MainActivity : AppCompatActivity(R.layout.activity_main) {

    val viewModel = ViewModelProvider(
        this,
        SavedStateViewModelFactory(application, this)
    ).get(MyViewModel::class.java)
}

创建 ViewModel 的类是 ViewModelFactory,而创建包含 SaveStateHandle 的 ViewModel 的工厂类是 SavedStateViewModelFactory。通过此工厂创建的 ViewModel 将持有一个基于传入 Activity 或 Fragment 的 SaveStateHandle。如果我们的 ViewModel 构造方法只带一个 SavedStateHandle 参数或者带有一个Application 参数和 SavedStateHandle 参数,可以直接使用 SavedStateViewModelFactory。如果构造方法还带有其他的参数,此时需要继承 AbstractSavedStateViewModelFactory 实现我们自己的工厂类。在使用AbstractSavedStateViewModelFactory 时,我们需要注意一点:create 方法带的 SavedStateHandle 参数一定传递到 ViewModel 里面去。

  • 调用SaveStateHandle

举一个保存用户 ID 的例:

class MyViewModel(state :SavedStateHandle) :ViewModel() {
    // 将Key声明为常量
    companion object {
        private val USER_KEY = "userId"
    }

    private val savedStateHandle = state

    fun saveCurrentUser(userId: String) {
        // 存储 userId 对应的数据
        savedStateHandle.set(USER_KEY, userId)
    }

    fun getCurrentUser(): String {
        // 从 saveStateHandle 中取出当前 userId
        return savedStateHandle.get(USER_KEY)?: ""
    }
}

保存: saveNewUser 方法展示了使用键值对的形式保存 USER_KEY 和 userId 到 SaveStateHandle 的例子。每当数据更新时,要保存新的数据到 SavedStateHandle;

获取: 调用 savedStateHandle.get(USER_KEY) 方法获取被保存的 userId。

现在,无论是第二还是第三种情况下,SavedStateHandle 都可以恢复界面数据。

3.why?


3.1 ViewModel 是如何创建的?

ViewModelProivder 有很多构造方法,不过最终都调到同一个地方:

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

这个方法中,mFactory 就是我们预期的工厂类,用来创建 ViewModel 对象;mViewModelStore 是一个什么东西呢?这个很好理解,mViewModelStore 就是用来存储的 ViewModel 对象的,比如同一个 Activity 的onCreate() 方法可能会多次回调,我们在 onCreate()方法初始化ViewModel,但是不可能每次 onCreate() 回调都会创建新的 ViewModel 对象,所以需要有一个东西用来存储的我们之前创建过的 ViewModel,这个就是ViewModelStore 的作用。而 ViewModel 生命周期比 Activity 的生命周期长也是因为这个类。

那么 mViewModelStore 对象是从哪里传过来,我们清楚的记得构造方法里面我们并没有传这个变量。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

我们可以看到从 ViewModelStoreOwner 获取的,代码如下

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner是一个接口,那么哪些类是这个借口的实现类呢?如你所料,我们熟悉的ComponentActivity 和 Fragment 都实现了这个接口。

我们再来看一下 get 方法,因为真正获取 ViewModel 对象就是通过这个方法的。

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

这个get方法没有做什么事情,构造了一个默认的 key,然后调用另一个 get 方法。代码如下:

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
}

这个 get 方法总的来说,主要分为以下2个过程:

  1. 先通过 key 从 ViewModelStore (缓存)获取 ViewModel 对象,如果缓存中存在,直接返回。Activity 经过横屏重建之后,返回 ViewMode 的对象就是这里返回。
  2. 如果缓存不存在,那么通过 Factory 创建一个对象,然后放在缓存中,最后返回。

3.2.ViewModel 如何做到配置更改时依然可以恢复数据?

在上面讲SaveState的时候,提到了Activity 和 Fragment 被销毁的三种情况,在这三种情况下的 ViewModel 的生命周期可以看下图:

image.png

从这张图里面,我们可以看出,ViewModel 的生命周期要比Activity长一点。ViewModel 存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。在此期间ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。

在前面的概述中,我们已经知道 ViewModel 的生命周期要比 Activity 长一点。那 ViewModel 是怎么做到的呢?对于这个问题,我猜大家首先想到的是缓存,并且这个缓存是被 static 关键字修饰的。正常来说,这个实现方案是没有问题的,我们也能找到具体的例子,比如 Eventbus 就是这么实现的。

那么在 ViewModel 中,这个是怎么实现的呢?我们都知道 ViewModel 是从一个 ViewModelStore 缓存里面的获取,我们看了 ViewModelStore 的源码,发现它的内部并没有通过静态缓存实现。那么它是怎么实现Activity 在 onDestroy 之后(重建),还继续保留已有的对象呢?

这个我们可以从 ComponentActivity 的 getViewModelStore 方法去寻找答案:

public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
}

getViewModeStrore 方法的目的很简单,就是获取一个 ViewModelStrore 对象。那么这个 ViewModelStore 可以从哪里获取呢?我们从上面的代码中可以找到两个地方:

  1. 从 NonConfigurationInstances 获取。
  2. 创建一个新的 ViewModelStore 对象。

第二点我们不用看,关键是 NonConfigurationInstances。NonConfigurationInstances 这是什么东西?

NonConfigurationInstances 其实就是一个 Wrapper,用来包装一下因为不受配置更改影响的数据,包括我们非常熟悉的 Fragment,比如说,一个 Activity 上面有一个 Fragment,旋转了屏幕导致 Activity 重新创建,此时Activity 跟之前的不是同一个对象,但是 Fragment 却是同一个,这就是通过 NonConfigurationInstances 实现的。也就是说在 getViewModelStore 方法里面,从 NonConfigurationInstances 获取的 ViewModelStore 对象其实就是上一个 Activity 的。同时,我们还可以在 ComponentActivity 里面看到一段代码:

getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {
               if (event == Lifecycle.Event.ON_STOP) {
                   Window window = getWindow();
                   final View decor = window != null ? window.peekDecorView() : null;
                   if (decor != null) {
                      decor.cancelPendingInputEvents();
                   }
             }
        }
});

从上面的代码中,我们可以到如果 Activity 是因为配置更改导致 onDestroy 方法的回调,并不会清空ViewModelStore 里面的内容,这就能保证当 Activity 因为配置更改导致重建重新创建的 ViewModel 对象跟之前创建的对象是同一个。反之,如果 Activity 是正常销毁的话,则不会保存之前创建的 ViewModel 对象,对应的是 ViewModelStore 的 clear 方法调用。其实这个 clear 方法还跟 kotlin 里面的协程有关,这里就不过多解释了,有兴趣的同学可以看看 ViewModel.viewModelScope。

现在我们来看一下 NonConfigurationInstances 为啥能保证 Activity 重建前后,ViewModeStore 是同一个对象呢?我们直接从ActivityThread的performDestroyActivity方法去寻找答案。我们知道,performDestroyActivity 方法最后会回调到 Activity 的 onDestroy 方法,我们可以通过这个方法可以找到ActivtyThread 在 Activity onDestroy 之前做了保存操作。

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    // ······
    performPauseActivityIfNeeded(r, "destroy");
    // Activity的onStop方法回调
    if (!r.stopped) {
        callActivityOnStop(r, false /* saveState */, "destroy");
    }
    if (getNonConfigInstance) {
        // ······
        // retainNonConfigurationInstances方法的作用就是创建一个对象
        r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();
        // ······
    }
    // ······
    // Activity的onDestroy方法回调
    mInstrumentation.callActivityOnDestroy(r.activity);
    // ······
    return r;
}

从上面的代码中看出,在 Activity 的 onStop 和 onDestroy之间,会回调 retainNonConfigurationInstances方法,同时记录到ActivityClientRecord中去。这里retainNonConfigurationInstances 方法返回的对象就是我们之前看到的 NonConfigurationInstances 对象。

那么又在哪里恢复已保存的 NonConfigurationInstances 对象呢?这个可以从 performLaunchActivity 方法找到答案。performLaunchActivity 方法的作用就是启动一个 Activity,Activity 重建肯定会调用这个方法。在performLaunchActivity方法里面,调用了Activity的attach方法,在这个方法,Google将已有的NonConfigurationInstances 赋值给了新的 Activity 对象。

到这里,我们就知道为啥 NonConfigurationInstances 能保证 ViewModelStore 在 Activity 重建前后是同一个对象,同时也知道为啥 ViewModel 的生命周期比 Activity 的生命周期要长一点。

总结

在本篇文章中我讲述了什么是 ViewModel,如何传递参数到 ViewModel 中去,以及 ViewModel一些使用场景,也相信大家对 ViewModel 都能立马上手了。接着我们又从源码的角度分析了 ViewModel 是如何创建的,是如何和 Activity 的生命周期绑定在一起的,这让我们能够更深入的理解 ViewModel,最后讲述了 ViewModel 在配置更改以及销毁重建时是如何保存和恢复数据的。ViewModel 作为数据的处理和分发者,在 MVVM 盛行的当下承扮演着越来越重要的角色,让我们把ViewModel深入提炼并应用到实际项目中吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345