Android Jetpack - LiveData 组件使用及解析

1. LiveData 简介

LiveData 是一种具有生命周期感知能力的可观察数据持有类;LiveData 可以保证屏幕上的显示内容和数据一直保持同步。

特点:

  • LiveData了解 UI 界面的状态,如果 activity 不在屏幕上显示,LiveData 不会触发没必要的界面更新,如果 activity 已经被销毁,会自动清空与 observer 的连接,意外的调用就不会发生。
  • LiveData 会自动在 DESTROYED 的状态下移除 Observer ,取消订阅,所以不用担心内存泄露;
  • LiveData 跟 LifecycleOwner 绑定,能感知生命周期变化,并且只会在 LifecycleOwner 处于 Active 状态(STARTED/RESUMED)下通知数据改变;

LiveData 的生命周期感知是由 Lifecycle 来实现的;不了解Lifecycle的可以查看Android Jetpack - Lifecycle 组件使用及解析

2. LiveData 使用方法

  1. 项目中 LiveData 一般都存放在ViewModel 中,以保证 app 配置变更时,数据不会丢失;在 ViewModel 创建具体的 LiveData 实例来存储数据,如下:
public class NameViewModel extends ViewModel {
  private MutableLiveData<String> currentName;

  public MutableLiveData<String> getCurrentName() {
    if (currentName == null) {
      currentName = new MutableLiveData<>();
    }
    return currentName;
  }
}

定义一个 MutableLiveData (LiveData 的一个常用子类),通过 observe 方法可以订阅修改数据的通知,通过 postValue()或者 setValue()方法可以更新数据,已经订阅的 Observer 能够得到数据更改的通知,也即回调 onChanged()方法。

  1. 使用 LiveData 对象的 observe 或 observeForever 方法将对应的 Activity 或 Fragment 等添加为该 LiveData 对象的观察者,如下:
    //需要一个观察者来观察数据
    Observer<String> observer = new Observer<String>() {

      @Override public void onChanged(String s) {
        nameTextView.setText(s);
      }
    };
    //获取到 viewmodel
    NameViewModel model = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(this.getApplication())).get(NameViewModel.class);
   // 取出 LiveData 完成订阅
    model.getCurrentName().observe(this, observer);
    // observeForever:无论何时,只要数据发生改变,就会触发 observer.onChanged()
    // model.getCurrentName().observeForever(observer);
  1. 使用 LiveData 的 setValue 或 postValue 更新数据,然后在观察者,也就是 Activity 或 Fragment 中就获取更新数据了,如下:
 btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        String anotherName = "name" + (i++);
        // LiveData 发送消息通知 observer 更新数据
        model.getCurrentName().setValue(anotherName);
      }
  });

注意:

setValue 只能在主线程运行
postValue 只能在子线程中运行

3. LiveData 核心原理

3.1 LiveData.observe()

LiveData 的使用流程从 observe() 开始

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    // 检查是否为ui线程
    assertMainThread("observe");
    //如当前UI的状态的是 DESTROYED 则忽略
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {// 1
        // ignore
        return;
    }
    //把 Observer 用 LifecycleBoundObserver 包装起来
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);// 2
    //缓存起来
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);// 3
    //如果已经 observe 过 并且两次的 owner 不同则报错
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    // 把 wrapper 与 Activity/Fragment 的生命周期,建立关系,
    // 当 UI 的生命周期发生变化的时候,就会去回调 wrapper 中的 onStateChanged
    owner.getLifecycle().addObserver(wrapper);// 4
}

注释1处的 owner 实际上就是注册时传进来来组件,比如 Activity,获取组件当前的状态,如果状态为 DESTROYED,那么直接 return,这说明 DESTROYED 状态的组件是不允许注册的。

注释2处新建了一个 LifecycleBoundObserver 包装类,将 owner 和 observer 传了进去。

可以看到 observe 方法里把我们传递的 observer 用 LifecycleBoundObserver 包装了起来,并且存入了 mObservers ,并且跟 owner 进行了关联。

注释3处将 observer 和 LifecycleBoundObserver 存储到SafeIterableMap<Observer<? super T>, ObserverWrapper>mObservers中,putIfAbsent 方法和 put 方法有区别,如果传入 key 对应的 value 已经存在,就返回存在的 value,不进行替换。如果不存在,就添加 key 和 value,返回null。

如果等于 null,在注释4处会将 LifecycleBoundObserver 添加到 Lifecycle 中完成注册,这样当我们调用 LiveData 的 observe 方法时,实际上是 LiveData 内部完成了 Lifecycle 的观察者的添加,这样 LiveData 自然也就有了观察组件生命周期变化的能力。

两个特殊处理:

  • 忽视处于 DESTROYED 的 owner 的注册行为;
  • 将一个 Observer 同时绑定两个 LifecycleOwner 的行为视为非法操作,也即一个 Observer 只能绑定一个 owner,而 owner 可以有多个 Observer;
3.2 LifecycleBoundObserver

LifecycleBoundObservers 是 LiveData 的内部类,代码如下所示:

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull
        final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            // 判断 owner 当前的状态是否是至少 STARTED
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        // 当生命周期发生变化时,会调用这个函数 
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            // 当UI的生命周期为DESTROYED,取消对数据变化的监听,移除回调函数
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
             // 改变数据,传递的参数是shouldBeActive(),它会计算看当前的状态是否是STARTED,也就是 onStart-onPause 期间生命周期
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }
    private abstract class ObserverWrapper {
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }
        // 是否是 active 状态
        abstract boolean shouldBeActive();

        boolean isAttachedTo(LifecycleOwner owner) {
            return false;
        }

        void detachObserver() {
        }

        void activeStateChanged(boolean newActive) {
            // 当前的生命周期和上一次的生命周期状态,是否发生变化,没有发生变化,就直接返回。
            // onStart-onPause 为 true  在这之外的生命周期为false
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                // 这是一个空函数,可在代码中根据需要进行重写
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                // 这是一个空函数,可在代码中根据需要进行重写
                onInactive();
            }
            // 如果 active 状态下,则发送数据更新通知
            if (mActive) {
                dispatchingValue(this);// 1
            }
        }
    }

LifecycleBoundObserver 是 抽象类 ObserverWrapper 的子类,重写了 shouldBeActive() 方法,在 owner 处于至少是 STARTED 的状态下认为是 Active 状态,Active 状态包括 STARTED 和 RESUMED 状态。

LifecycleBoundObserver 实现了 GenericLifecycleObserver 接口,可以监听 lifecycle 回调,并且在 onStateChanged() 方法里处理了生命周期改变的事件,当接收到 DESTROYED 的事件会自动解除跟 owner 的绑定,并且将下个流程交给了 activeStateChanged() 。
这就是为什么一个观察者(组件)处于DESTROYED状态时,它将不会收到通知的原因。

问题一:LiveData 是如何跟 LifecycleOwner 进行绑定,做到感知生命周期的?
LiveData 在 observe 方法中用 LifecycleBoundObserver 包装了 observer ,并且通过它绑定了LifecycleOwner。

问题二:LiveData 只在 LifecycleOwner active 状态发送通知,是怎么处理的?
LifecycleBoundObserver 在 onStateChanged() 方法里处理了生命周期改变的事件,当接收到 DESTROYED 的事件会自动解除跟 owner 的绑定。

注意:当我们调用 observe() 注册后,由于绑定了 LifecycleOwner,所以在 active 的情况下,LiveData 如果有数据,则 Observer 会立马接受到该数据修改的通知。

此时的流程是:

observe–>
onStateChanged–>
activeStateChanged–>
dispatchingValue–>
considerNotify–>
onChanged

可以称之为生命周期改变触发的流程,另外还有一种流程是 postValue&setValue 触发的流程,共两种。

3.2 activeStateChanged(boolean)

activeStateChanged 方法定义在抽象类 ObserverWrapper 中,它是 Observer 的包装类,activeStateChanged 方法会根据 Active 状态和处于 Active 状态的组件的数量,来对 onActive 方法和 onInactive 方法回调,这两个方法用于拓展LiveData 对象。注释1处,如果是 Active 状态,会调用 dispatchingValue 方法,并将自身传进去。

3.3 dispatchingValue(ObserverWrapper) 分析
     void dispatchingValue(@Nullable ObserverWrapper initiator) {
        //正在处于分发状态中
        if (mDispatchingValue) {
            //标记分发失效
            mDispatchInvalidated = true;// 1
            return;
        }
        //标记分发开始,
        //进入while 循环前,设置为true,如果此时另外一个数据发生变化,到了此函数中就直接在上面返回了
        mDispatchingValue = true;
        do {
            // 分发有效
            // 开始for循环前,设置为false,for循环完,也会退出while循环
            mDispatchInvalidated = false;
            // 生命周期改变调用的方法 initiator 不为 null
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                 // postValue/setValue 方法调用 传递的 initiator 为 null
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    // 这里mDispatchInvalidated 为true,表示在while循环未结束的时候,有其他数据发生变化,并调用了该函数
                    // 在上面的 if 判断中设置了 mDispatchInvalidated = true,
                    // 结束本次for循环,没有退出while循环,开始下一次for循环
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        // 标记不处于分发状态
        mDispatchingValue = false;
    }

considerNotify(ObserverWrapper)方法:

    private void considerNotify(ObserverWrapper observer) {
        // 检查状态 确保不会分发给 inactive 的 observer
        // 如果 ObserverWrapper 的 mActive 值不为 true,就直接 return
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            // 如果当前observer对应组件的状态不是Active,就会再次调用activeStateChanged方法,并传入false,
            // 其方法内部会再次判断是否执行onActive方法和onInactive方法回调
            observer.activeStateChanged(false);
            return;
        }
        // setValue 会增加 version ,初始 version 为-1
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //如果判断条件都满足会调用Observer的onChanged方法,这个方法正是使用LiveData的observe方法的回调。
        observer.mObserver.onChanged((T) mData);
    }

可以看到 dispatchingValue正是分发事件逻辑的处理方法,而 considerNotify 方法则确保了只将最新的数据分发给 active 状态下的 Observer

可以看到 LiveData 引入了版本管理来管理数据 (mData)以确保发送的数据总是最新的

dispatchingValue 这里分两种情况:

  • ObserverWrapper 不为 null
  • ObserverWrapper 为 null
3.3.1 ObserverWrapper 不为 null 的情况

LifecycleBoundObserver.onStateChanged 方法里调用了 activeStateChanged ,而该方法调用 dispatchingValue(this); 传入了 this ,也就是 LifecycleBoundObserver ,这时候不为 null 。

也就是说生命周期改变触发的流程就是这种情况,这种情况下,只会通知跟该 Owner 绑定的 Observer。

3.3.2 ObserverWrapper 为 null 的情况

除了生命周期改变触发的流程外,还有 postValue&setValue 流程,来看下这俩方法。

@MainThread // 说明setValue方法是运行在主线程中
protected void setValue(T value) {
    // 必须在主线程调用 否则会 crash
    assertMainThread("setValue");
    // mVersion 表示数据发生了变化
    mVersion++;// 增加版本号
    // 保存了这次变化的数据
    mData = value;
    // 分发数据变化,调用回调函数
    dispatchingValue(null); // 传入了 null
}

private final Runnable mPostValueRunnable = new Runnable() {
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        //noinspection unchecked
        // 调用 setValue
        setValue((T) newValue);
    }
};

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        // 上一个 post 后还没有执行的 runnable,所以就不需要再 post 了,
        // 但是注意,上面的 mPendingData 数据已经是新数据了 
        //用官方的话,就是  If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
        return;
    }
    // postValue 可以从后台线程调用,因为它会在主线程中执行任务
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

LiveData 的 postValue 方法其实就是把操作 post 到主线程,最后调用的还是 setValue 方法,注意 setValue 必须是在主线程调用。

setValue 方法调用了 dispatchingValue 方法,并传入了 null ,这个时候的流程则会通知 active 的 mObservers。

可以看到无论是 LiveData 的 observe 方法还是 LiveData 的 postValue/setValue 方法都会调用 dispatchingValue 方法。

LiveData 的两个流程都会走到 dispatchingValue 处理分发通知逻辑,并且在分发通知前会判断 owner 的状态,再加上 LiveData 本身内部的版本管理,确保了只会发送最新的数据给 active 状态下的 Observer

注意:LiveData 对同时多次修改数据做了处理,如果同时多次修改,只会修改为最新的数据。

4. LiveData 其它关联类及知识点

4.1 Sticky Event

LiveData 被订阅时,如果之前已经更改过数据,并且当前 owner 为 active 的状态,activeStateChanged() 会被调用,也即会立马通知到 Observer ,这样其实就类似 EventBus 的 sticky event 的功能,但很多时候我们并不需要该功能。

4.2 AlwaysActiveObserver

默认情况下,LiveData 会跟 LifecycleOwner 绑定,只在 active 状态下更新,如若想要不管在什么状态下都能接收到数据的更改通知的话,怎么办?这时候需要使用 AlwaysActiveObserver ,改调用 observe 方法为调用 LiveData.observeForever(Observer) 方法即可。

@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
    assertMainThread("observeForever");
    AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
        throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    wrapper.activeStateChanged(true);
}

private class AlwaysActiveObserver extends ObserverWrapper {

    AlwaysActiveObserver(Observer<? super T> observer) {
        super(observer);
    }

    @Override
    boolean shouldBeActive() {
        return true;
    }
}
4.3 MediatorLiveData

LiveData 还有一个子类是 MediatorLiveData,它允许我们合并多个 LiveData,任何一个 LiveData 有更新就会发送通知。比如我们的数据来源有两个,一个数据库一个网络,这时候我们会有两个 DataSource,也就是两个 LiveData,这个时候我们可以使用 MediatorLiveData 来 merge 这两个 LiveData。

示例一:
例如有一个需求:希望在ExitText中输入文字的同时,显示文字个数。

class MainViewModel : ViewModel() {
    val message: MutableLiveData<String> = MutableLiveData()
    val count: MediatorLiveData<Int> = MediatorLiveData()

    init {
        count.value = 0

        count.addSource(message) {
            val cnt = message.value?.length ?: 0
            count.postValue(cnt)
        }
    }

    fun postMessage(message: String) {
        this.message.postValue(message)
    }
}

EditText 的回调里通过 postMessage 更新 message,count 通过 addSource 监听 message 的变化后更新自己。

addSource可以保证被监视 Livedata 不会被重复订阅,如果我们手动 observe 的话,可能会因为重复订阅造成数据接受异常,通过addSrouce的源码可以清楚看到这一点

   @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }

示例二:

class SecondViewModel : ViewModel() {
   var data1: MutableLiveData<UserInfo> = MutableLiveData()
   var data2: MutableLiveData<UserInfo> = MutableLiveData()
   var mediatorLiveData: MediatorLiveData<UserInfo> = MediatorLiveData()

   val user1 = UserInfo("李四1", (1000..5000).random())
   val user2 = UserInfo("李四2", (1000..5000).random())

   data1.postValue(user1)
   data2.postValue(user2)

   mediatorLiveData.addSource(data1, object : Observer<UserInfo> {
       override fun onChanged(info: UserInfo) {
           mediatorLiveData.value = info
       }
   })

   mediatorLiveData.addSource(data2, object : Observer<UserInfo> {
       override fun onChanged(info: UserInfo) {
           mediatorLiveData.value = info
       }
   })
}

个我们定义了两个 MutableLiveData 表示正常的数据获取。MediatorLiveData 通过 addSource 方法将 data1 和 data2 合并一起组成新的 LiveData。onChanged 回调表示的是当 data1 和 data2 数据源数据发送变化的时候进行回调。通知界面UI进行数据刷新。

我们在Activity中使用:

secondViewModel.mediatorLiveData.observe(this, Observer {
    if (it.name.contains("1")) {
        mTvShow.text = "姓名:${it.name} \n薪水:${it.salary}"
    } else {
        mTvShowOther.text = "姓名:${it.name} \n薪水:${it.salary}"
    }
})
4.4 Transformations

Transformations 允许我们把一个 LiveData 进行处理,变化成另外一个 LiveData,目前支持 map 跟 switchMap 两个方法,跟 RxJava 的操作类似。

比如,用 map 把一个 String 类型的 LiveData 转换成 Integer 类型:

Transformations.map(liveString, new Function<String, Integer>() {
  @Override
  public Integer apply(final String input) {
    return Integer.valueOf(input);
  }
}).observe(this, new Observer<Integer>() {
  @Override
  public void onChanged(@Nullable final Integer integer) {

  }
});
4.4 LiveDataBus

EventBus 基于观察者模式,LiveData 也是,所以 LiveData 可以被用来做成 LiveDataBus。

基于 LiveData 封装 LiveDataBus 消息总线

5. 知识点梳理和汇总

  • LiveData 的实现基于观察者模式(reactive patterns);
  • LiveData 跟 LifecycleOwner 绑定,能感知生命周期变化,并且只会在 LifecycleOwner 处于 Active 状态(STARTED/RESUMED)下通知数据改变;如果数据改变发生在非 active 状态,数据会变化,但是不发送通知,等 owner 回到 active 的状态下,再发送通知;
  • 如果想要一直收到通知,则需要用 observeForever() 方法;
  • LiveData 会自动在 DESTROYED 的状态下移除 Observer ,取消订阅,所以不用担心内存泄露;
  • 在 LifecycleOwner 处于 DESTROYED 的状态下,不能订阅;
  • postValue 方法其实最后调用了 setValue 只不过把操作放到主线程,适合在异步线程里调用,setValue 必须在主线程里调用;
  • 如果同时多次调用 postValue 或 setValue 修改数据,只会修改成最新的那个数据,也即只会收到一次通知(set post混合调用则不一定);
  • 如果 LiveData 有数据,并且 owner 在 active 状态下,那么在订阅的时候,会立马收到一次通知
  • 一个 Observer 实例,只能绑定一个 LifecycleOwner,而一个 owner 可以绑定多个 Observer 实例;
  • LiveData 利用版本管理、绑定 Lifecycle 确保了只会发送最新的数据给 active 状态下的 Observer
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容