Rxjava2 基本用法(1)

Rxjava官网中,给出了Rxjava的总结:

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.

翻译过来:RxJava - JVM响应式扩展Reactive Extensions 用于使用Java VM的可观察序列编写异步和基于事件的程序的库。

这句话包含着两个关键词,异步的和基于事件的。

  • 异步:非阻塞模式,且包含着观察者、被观察者和订阅关系,属于观察者设置模式。
  • 基于事件:响应式编程,由Event触发。对事件的分发的库。

本文的编写和分析的版本如下:

    compile 'io.reactivex.rxjava2:rxjava:2.0.1'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
常规操作符

一、简介

先从一个Demo引出:

        // 1、观察者
        final Observer<Integer> observer = new Observer<Integer>() {
            @Override
            public void onSubscribe(Disposable d) {
                Log.e(TAG, "subscribe");
            }

            @Override
            public void onNext(Integer value) {
                Log.e(TAG, "" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "error");
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "complete");
            }
        };

        ///2、被观察者
        Observable<Integer> observable =  Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                ///4、发射相应事件
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onComplete();
            }
        });

        ///3、订阅
        observable.subscribe(observer);
运行结果
  • Observer 观察者
    对于观察者而言,是对事件做出处理后的回调。Observer回调包含onSubscribe注册时回调,onNext事件到达时回调,onError抛出异常时回调,onComplete事件完成时回调。
  • Observable 被观察对象
    由该对象开始订阅,并对事件进行一系列的变换处理。最终该事件到达观察者的回调中。
  • subscribe() 订阅
    观察者订阅被观察者,订阅的时候会执行ObservableOnSubscribe中的subscribe订阅函数。
  • ObservableEmitter 发射器
    emitter.onNext(1);通过发射器发送一个事件,作为该事件的发起的源头。

Rxjava就如同工厂加工商品的流水线,事件就如同最初的商品,该商品可以进行加工(map等)将一个事件变成另外一个事件、过滤(filter)商品不合格过滤、组合(zip等)一个商品和另一个商品在一起加工成新的商品等等。

  • onSubscribe 就像流水线开启电源,流水线被打开
  • onNext 由最开始的材料,经过特殊的处理,生成了最终的商品
  • onError 流水线发生了意外,如断电等,导致流水线不能正常运转,只能停止
  • onComplete 流水线完成了所有的任务,比如计划生成100件商品,现在已经全部生产完毕,流水线就可以正常的断电了。
注意:

当调用了emitter.onComplete()、emitter.onError()回调后,剩下的事件就不会在发送了。可以按照流水线观念理解,工厂中的流水线都已经停电了,商品就不会再生产了,也就不会在发送事件了。

原因在于,发送之前会判断isDisposed()是否disposed,而onComplete和onError会设置该标志位。

二、Observable常见操作符

1)、创造型操作符

创造型操作符,就是用来创建一个Observable对象,是用来发送系列事件的源头。

①、create

用于创建一个带有发射器ObservableEmitter对象的Observable,可以用该发射器去发送一些事件。

代码如下,用create创建一个Observable,并使用ObservableEmitter发送了若干个事件。运行结果如上图。

       Observable<Integer> observable =  Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                ///4、发射相应事件
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onComplete();
            }
        });
②、fromArray

创建Observable对象,并将数组中的数据转换成一系列的事件发送。

函数的原型:

public static <T> Observable<T> fromArray(T... items)

Demo:分别发送1、2、3、4、5事件,发送完成会调用onComplete回调

Observable<Integer> observable =  Observable.fromArray(1,2,3,4,5);
③、just

同fromArray,底层也是调用fromArray函数实现的。

just重载方法

源码:从源码上看,就是调用fromArray函数。

    @SuppressWarnings("unchecked")
    @SchedulerSupport(SchedulerSupport.NONE)
    public static <T> Observable<T> just(T item1, T item2, T item3) {
        // 检测是否为空
        ObjectHelper.requireNonNull(item1, "The first item is null");
        ObjectHelper.requireNonNull(item2, "The second item is null");
        ObjectHelper.requireNonNull(item3, "The third item is null");
        // 通过fromArray实现
        return fromArray(item1, item2, item3);
    }
④、range

range的意思为范围,表示int型的范围。所range创建Observable对象,并将range范围中的数字当做事件发送。

Observable.range(1,5)

        // 1、观察者
        final Observer<Integer> observer = new Observer<Integer>() {
            @Override
            public void onSubscribe(Disposable d) {
                Log.e(TAG, "subscribe");
            }

            @Override
            public void onNext(Integer value) {
                Log.e(TAG, "" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "error");
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "complete");
            }
        };

        ///2、被观察者
        Observable<Integer> observable =  Observable.range(1,5);

        ///3、订阅
        observable.subscribe(observer);

结果:

subscribe
1
2
3
4
5
complete

方法原型:从start开始的连续count数

public static Observable<Integer> range(final int start, final int count) {
⑤、interval

每间隔一段时间去发送Long型消息:

如:Observable<Long> observable = Observable.interval(1,TimeUnit.SECONDS);

原型:period代表间隔时间,unit为时间单位。这样每个period时间就会发送一个从0开始的每次自增的Long型事件。

public static Observable<Long> interval(long period, TimeUnit unit)
⑥、timer

概念同interval类似,只不过interval会循环的发送,而timer只会推迟指定的事件发送。

如:Observable<Long> observable = Observable.timer(1,TimeUnit.SECONDS);

原型:delay代表推迟时间,unit为时间单位。

public static Observable<Long> interval(long delay, TimeUnit unit)

2)、变换型操作符

变换性操作符,往往事件一种类型的事件转换成另外一种。如Integer转换成String。

①、map
Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        }).map(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) throws Exception {
                return integer + "------";
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.e(TAG, s);
            }
        });

结果:

1------
2------
3------

emitter发射器发送Integer型的事件,通过map操作符将integer型加个后缀变成String类型的。

  • Function接口
    map变换中有个Function,其中的apply函数,就是变换的关键点,通过调用重写的apply,就实现了事件的变换。
public interface Function<T, R> {

    R apply(T t) throws Exception;
}
②、flatMap

flatMap如同map一样,也是事件的变换。不同的在于apply函数,flatMap是转换成ObservableSource对象,用ObservableSource对数据进行了包装。这样就可以用ObservableSource二次处理了。

flatMap并不保证,事件发送的顺序,例如先发送的数据后回调。

        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        }).flatMap(new Function<Integer, ObservableSource<String>>() {
            @Override
            public ObservableSource<String> apply(Integer integer) throws Exception {

                if (integer == 2)
                    return Observable.just("I am value " + integer).delay(100, TimeUnit.MILLISECONDS);
                else
                    return Observable.just("I am value " + integer).delay(10, TimeUnit.MILLISECONDS);
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.e(TAG, s);
            }
        });

该功能是发送三个事件,对第一个和第三个事件推迟10毫秒再继续发送,对第二个推迟100毫米再继续分发下去。

从结果来看并没有保证顺序。结果:

I am value 1
I am value 3
I am value 2

小结flatMap变换成的对象是ObservableSource类型的,但是并不保证先后顺序。

③、concatMap

使用方法同flatMap完全一样,唯一不同点在于concatMap是绝对保证顺序,先发射的事件是先被回调的。

将上述代码中的flatMap改成concatMap后执行的结果就是:

I am value 1
I am value 2
I am value 3
④、buffer

他会截取count的事件,并把该事件组合成List<T>事件发送,如果发送的事件没有达到传入的count个,就将全部事件组合成List集合。

        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        })
        .buffer(5)
        .subscribe(new Consumer<List<Integer>>() {
            @Override
            public void accept(List<Integer> list) throws Exception {
                for (Integer integer : list) {
                    Log.e(TAG, "accept: " + integer);
                }
            }
        });

3)、过滤型操作符

过滤型操作符,是过滤某些不合规则的事件。通过重写判断函数的标志来过滤的。

①、filter

通过判断发送的事件,从而过滤那些不符合规则的事件。

如:过滤不是偶数的事件

        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        })
        .filter(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) throws Exception {
                return integer % 2 == 0;
            }
        })
        .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                Log.e(TAG, "accept: " + integer);
            }
        });

结果:

accept: 2
②、take(int count)

从第一个事件开始计算,只允许发送前面count个事件。

③、takeLast(int count)

只允许发送一系列事件中的最后count个事件。

④、distinct

distinct是过滤那些重复的事件。

如:只会显示不重复的事件

        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(1);
                emitter.onNext(1);
            }
        })
        .distinct()
        .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                Log.e(TAG, "accept: " + integer);
            }
        });

4)、聚合操作符

用Observable发射的数据序列统计或者求值。

①、count

对传入的integer型数据求值。

如对1到10做加法运算,count后将求值后的结果继续传递。

        Observable
                .range(1,10)
                .count()
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long l) throws Exception {
                        Log.e(TAG, "accept: " + l);
                    }
                });
②、concat

将两个同事件类型的Observable组合成一个Observable

 Observable<Integer> firstObservable = Observable.range(1,2);
        Observable<Integer> secondObservable = Observable.range(3,2);


        Observable
                .concat(firstObservable,secondObservable)
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer l) throws Exception {
                        Log.e(TAG, "accept: " + l);
                    }
                });

结果:

accept: 1
accept: 2
accept: 3
accept: 4
③、toList

toList是将前面全部的事件或者count事件组合成为List<T>事件。

如:

        Observable
                .range(0,10)
                .toList()
                .subscribe(new Consumer<List<Integer>>() {
                    @Override
                    public void accept(List<Integer> list) throws Exception {
                        Log.e(TAG, "accept: " + list.size());
                    }
                });

5)、聚合操作符

①、zip

使用一个指定的函数将多个Observable发射的数据组合在一起,然后将这个函数的结果作为单项数据发射。

每个Observable都会发送若干个事件,zip会分别获取每个Observable中的第n个事件,将每个Observable的第n个事件一起传递给用户,再根据情况处理。

但每个Observable发送的长度不一样,那就按照木桶效应来处理,一律按照最短的截取。

如:

Observable<Integer> firstObservable = Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);

            }
        });

        Observable<String> secondObservable = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                emitter.onNext("-----1");
                emitter.onNext("-----2");
                emitter.onNext("-----3");
                emitter.onNext("------");
            }
        });

        Observable<String> resultObservable = firstObservable.zipWith(secondObservable, new BiFunction<Integer, String, String>() {
            @Override
            public String apply(Integer integer, String string) throws Exception {
                return integer + string;
            }
        });

        resultObservable.subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.e(TAG, "accept: " + s);
            }
        });

结果:

accept: 1-----1
accept: 2-----2
accept: 3-----3

分别将同一级的integer型、string型的事件一同交给用户同意处理。第一个Observable发送了三个事件,第二个Observable发送了四个事件,那就按照最小的来,只处理三个。

三、背压问题

1)、原因

一项技术或功能的产生,必定是由于出现了某个特定的问题,或者原来的技术已经不太满足现在的需求了。

背压问题,用三峡大坝比喻下。大坝的用处就是分为拦截上流流入的水,通过大坝释放到下游,如果上游的水流入的比释放的快,就可能出现问题。但也不一样,毕竟三峡的水位警戒线比较高,一般的流速肯定不在话下。最怕出现了那种暴雨天气,已经逼近了警戒线,大坝全力释洪,还不行该怎么办的问题。

其实这就是生产者和消费者问题,用上述的水比作事件,这就出现了生产和消费的不平衡现象。也就是背压问题。

如:ObservableEmitter不断的发送事件,另外一个线程处理事件。这就导致了处理速度比不上发送速度。

        Observable
                .create(new ObservableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                        for (int i = 0; ; ) {
                            emitter.onNext(i++);
                        }
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer integer) throws Exception {

                    }
                });

这样就最终会导致OOM内存溢出。

先看几个关键类:

  • Flowable
    Flowable用法和Observable类似,在这基础上多了上游和下游处理速度不平衡的交互功能,该功能需要Subscription类配合才能完成。
  • Subscriber
    注册的回调,使用Observable时注册的回调是Observer对象,而对于Flowable类则是Subscriber对象。

Observer和Subscriber不同点在于,这两个类的注册成功回调对象不一致。Observer对应着Disposable,而Flowable对应着Subscriber。Subscriber多了和上游交互的功能。

  • Subscription
    该对象会在注册成功后的注册回调中返回。
public interface Subscription {

    public void request(long n);

    public void cancel();
}

cancel()会切断整个流水线,达到关闭作用。重要的在于request,这会告诉发射器发射器可以发射几个事件。

下面看下例子:

        Flowable<Integer> flowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        }, BackpressureStrategy.ERROR);

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {

            @Override
            public void onSubscribe(Subscription s) {
                Log.e(TAG, "onSubscribe");
                // 告诉上游可以处理3个事件
                s.request(3);
            }

            @Override
            public void onNext(Integer integer) {
                Log.e(TAG, "onNext: " + integer);

            }

            @Override
            public void onError(Throwable t) {
                Log.e(TAG, "onError: ", t);
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "onComplete");
            }
        };

        flowable.subscribe(subscriber);

简单的用法,通过Subscription.request()来告诉上游可以处理多少个事件,但是这并不影响上游的发送速度,上游还是像傻瓜样,该怎么发这么发。最终会使缓冲池装满。

如果上述去除s.request(3);就会抛出异常,该异常和设置的BackpressureStrategy.ERROR策略有关。如果发送的数据超过了请求的大小,就会抛出如下的错误。

onError: 
 io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests

正确的使用就是和上游通过emitter.requested()来感知下游能够处理多少。如果==0就表示下游不在接收了,那我就不发了。

Flowable<Integer> flowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
               for (int i=0;;) {
                   while (!emitter.isCancelled() && emitter.requested() == 0) {
                       Thread.sleep(10);
                   }
                   if (emitter.isCancelled())
                       break;
                   emitter.onNext(i++);
               }
            }
        }, BackpressureStrategy.ERROR);

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            Subscription mSubscription = null;
            @Override
            public void onSubscribe(Subscription s) {
                Log.e(TAG, "onSubscribe");
                mSubscription = s;
                // 开始请求一个触发一次
                s.request(1);
            }

            @Override
            public void onNext(Integer integer) {
                Log.e(TAG, "onNext: " + integer);
                mSubscription.request(1);

            }

            @Override
            public void onError(Throwable t) {
                Log.e(TAG, "onError: ", t);
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "onComplete");
            }
        };

        flowable.subscribe(subscriber);

在上述的例子中,1、FlowableEmitter中只有获得的下游的request数大于0时才继续的发送事件。2、观察者收到了注册的方法中,主动的s.request(1);让发射器发一个数据。3、观察者在onNext中接收到事件后,再继续的s.request(1)请求相应的事件。

策略:

BackpressureStrategy中有相应的策略。它的枚举定义如下:

public enum BackpressureStrategy {
    /**
     * OnNext events are written without any buffering or dropping.
     * Downstream has to deal with any overflow.
     * <p>Useful when one applies one of the custom-parameter onBackpressureXXX operators.
     */
    MISSING,
    /**
     * Signals a MissingBackpressureException in case the downstream can't keep up.
     */
    ERROR,
    /**
     * Buffers <em>all</em> onNext values until the downstream consumes it.
     */
    BUFFER,
    /**
     * Drops the most recent onNext value if the downstream can't keep up.
     */
    DROP,
    /**
     * Keeps only the latest onNext value, overwriting any previous value if the
     * downstream can't keep up.
     */
    LATEST
}
  • BUFFER 当发送的事件有来不及处理的时候,会放在缓冲区里面,这个缓冲区会无限的增加,直到发生OOM
  • ERROR 当FlowableEmitter发射器在emitter.requested() == 0的时候发送就会抛出异常
  • DROP Rxjava默认的缓冲区为128,如果有来不及处理的事件,就会放到缓冲区,128个放满后,接下来的事件就会抛弃。
  • LATEST 与DROP策略类似,他会抛弃最开始的数据,缓冲最后的数据。
以上的分析都是在同一个线程中。现在看下不同线程的情况。

例如:FlowableEmitter发送128个事件,Subscription请求一个事件。

 Flowable<Integer> flowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                        Log.e(TAG, "subscribe: " + emitter.requested());

                        for (int i = 0; i < 128; i++) {
                            Log.e(TAG, "subscribe: "+i );
                            emitter.onNext(i);
                        }
            }
        }, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            Subscription mSubscription = null;
            @Override
            public void onSubscribe(Subscription s) {
                Log.e(TAG, "onSubscribe");
                mSubscription = s;
                // 负责请求一个事件
                s.request(1);
            }

            @Override
            public void onNext(Integer integer) {
                Log.e(TAG, "onNext: " + integer);
            }

            @Override
            public void onError(Throwable t) {
                Log.e(TAG, "onError: ", t);
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "onComplete");
            }
        };

        flowable.subscribe(subscriber);

结果:

subscribe: 128
subscribe: 0
subscribe: 1
subscribe: 2
.........
subscribe: 126
subscribe: 127
onNext:0

生产者和消费者在异步的情况下,它的事件容量池就默认是128个。这样就可以默认发送128个。但是却不能消费128个事件,只能消费1个事件。

原因是发送是在一个线程中,回调是在另一个线程。每个线程都会保存request,两个地方的request对象是不一样的。

目前有两个问题:

  • 1、明明回调中设置了s.request(1);,但是subscribe()打印的是128,并不是1,和之前同步的是不一样的
  • 2、既然subscribe()中是128,那为什么下游只能处理1个事件

subscribe()打印的是128,只有等最开始的128个事件,消费了96个,容量池才会重新的设置成回调中设置的1.

subscribe()中128,代表发射器可以发送128个事件,低于128是不会抛出异常的。而只能消费1个代表着消费者s.request(1)就请求消费一个。

四、线程变换

对于前面操作符介绍章节,全部都是在主线程中操作的。也就是说事件的处理和事件的回调全在同一个线程中。一般的模式,对于耗时的操作放在子线程中,最后再将处理的结果切换到主线程,进行结果的回调。

对于一个事件流,它的线程变换决定是变换该事件点之前的线程,或者变换该点之后的线程。

  • subscribeOn(Scheduler scheduler)
    改变该点之前的线程,可以设置多次,但只能是第一个才有用
  • observeOn(Scheduler scheduler)
    改变该点之后的线程,可以设置多次,但只有最后一次才有用

例子

 Observable
                .create(new ObservableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                        emitter.onNext(1);
                        Log.e(TAG, "subscribe: " + Thread.currentThread().getName());
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Function<Integer, String>() {
                    @Override
                    public String apply(Integer integer) throws Exception {
                        Log.e(TAG, "apply: " + Thread.currentThread().getName());
                        return String.valueOf(integer);
                    }
                })
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        Log.e(TAG, "accept: " + Thread.currentThread().getName());
                    }
                });

结果:

subscribe: main
apply: main
accept: RxCachedThreadScheduler-3

虽然subscribeOn设置了多次,但只会对第一次设置的有用。observeOn决定之后在哪个线程,但两个同时设置的observeOn,最后一个才会生效,它的作用范围是在两个observeOn之间。

Schedulers类型
  • AndroidSchedulers.mainThread() : 主线程
  • Schedulers.io() : io操作的线程, 通常用于网络,读写文件等io密集型的操作
  • Schedulers.computation() : CPU计算密集型的操作
  • Schedulers.newThread() : 新建一个线程

五、引用

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