16.RxJava用法

引入依賴: implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
// (see https://github.com/ReactiveX/RxJava/releases for latest 2.x.x version)
implementation 'io.reactivex.rxjava2:rxjava:2.x.x'

接口變化

RxJava 2.x 擁有了新的特性,其依賴于4個基礎接口,它們分別是

  • Publisher
  • Subscriber
  • Subscription
  • Processor

其中最核心的莫過于 PublisherSubscriberPublisher 可以發出一系列的事件,而 Subscriber 負責和處理這些事件。

其中用的比較多的自然是 PublisherFlowable,它支持背壓。關于背壓給個簡潔的定義就是:

背壓是指在異步場景中,被觀察者發送事件速度遠快于觀察者的處理速度的情況下,一種告訴上游的被觀察者降低發送速度的策略。

簡而言之,背壓是流速控制的一種策略。有興趣的可以看一下官方對于背壓的講解

可以明顯地發現,RxJava 2.x 最大的改動就是對于 backpressure 的處理,為此將原來的 Observable 拆分成了新的 ObservableFlowable,同時其他相關部分也同時進行了拆分,但令人慶幸的是,是它,是它,還是它,還是我們最熟悉和最喜歡的 RxJava。

觀察者模式

大家可能都知道, RxJava 以觀察者模式為骨架,在 2.0 中依舊如此

不過此次更新中,出現了兩種觀察者模式:

  • Observable ( 被觀察者 ) / Observer ( 觀察者 )
  • Flowable (被觀察者)/ Subscriber (觀察者)

在 RxJava 2.x 中,Observable 用于訂閱 Observer,不再支持背壓(1.x 中可以使用背壓策略),而 Flowable 用于訂閱 Subscriber , 是支持背壓(Backpressure)的。

Observable

在 RxJava 1.x 中,我們最熟悉的莫過于 Observable 這個類了,筆者在剛剛使用 RxJava 2.x 的時候,創建了 一個 Observable,瞬間一臉懵逼有木有,居然連我們最最熟悉的 Subscriber 都沒了,取而代之的是 ObservableEmmiter,俗稱發射器。此外,由于沒有了Subscriber的蹤影,我們創建觀察者時需使用 Observer。而 Observer 也不是我們熟悉的那個 Observer,又出現了一個 Disposable 參數帶你裝逼帶你飛。

廢話不多說,從會用開始,還記得 RxJava 的三部曲嗎?

** 第一步:初始化 Observable **
** 第二步:初始化 Observer **
** 第三步:建立訂閱關系 **

Observable.create(new ObservableOnSubscribe<Integer>() { // 第一步:初始化Observable
            @Override
            public void subscribe(@NonNull ObservableEmitter<Integer> e) throws Exception {
                Log.e(TAG, "Observable emit 1" + "\n");
                e.onNext(1);
                Log.e(TAG, "Observable emit 2" + "\n");
                e.onNext(2);
                Log.e(TAG, "Observable emit 3" + "\n");
                e.onNext(3);
                e.onComplete();
                Log.e(TAG, "Observable emit 4" + "\n" );
                e.onNext(4);
            }
        }).subscribe(new Observer<Integer>() { // 第三步:訂閱

            // 第二步:初始化Observer
            private int i;
            private Disposable mDisposable;

            @Override
            public void onSubscribe(@NonNull Disposable d) {      
                mDisposable = d;
            }

            @Override
            public void onNext(@NonNull Integer integer) {
                i++;
                if (i == 2) {
                    // 在RxJava 2.x 中,新增的Disposable可以做到切斷的操作,讓Observer觀察者不再接收上游事件
                    mDisposable.dispose();
                }
            }

            @Override
            public void onError(@NonNull Throwable e) {
                Log.e(TAG, "onError : value : " + e.getMessage() + "\n" );
            }

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

不難看出,RxJava 2.x 與 1.x 還是存在著一些區別的。首先,創建 Observable 時,回調的是 ObservableEmitter ,字面意思即發射器,并且直接 throws Exception。其次,在創建的 Observer 中,也多了一個回調方法:onSubscribe,傳遞參數為DisposableDisposable 相當于 RxJava 1.x 中的 Subscription, 用于解除訂閱。可以看到示例代碼中,在 i 自增到 2 的時候,訂閱關系被切斷。

當然,我們的 RxJava 2.x 也為我們保留了簡化訂閱方法,我們可以根據需求,進行相應的簡化訂閱,只不過傳入對象改為了 Consumer

Consumer 即消費者,用于接收單個值,BiConsumer 則是接收兩個值,Function 用于變換對象,Predicate 用于判斷。這些接口命名大多參照了 Java 8 ,熟悉 Java 8 新特性的應該都知道意思,這里也不再贅述。

線程調度

關于線程切換這點,RxJava 1.x 和 RxJava 2.x 的實現思路是一樣的。這里簡單的說一下,以便于我們的新司機入手。

subScribeOn

同 RxJava 1.x 一樣,subscribeOn 用于指定 subscribe() 時所發生的線程,從源碼角度可以看出,內部線程調度是通過 ObservableSubscribeOn來實現的。

    @SchedulerSupport(SchedulerSupport.CUSTOM)
    public final Observable<T> subscribeOn(Scheduler scheduler) {
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
    }

ObservableSubscribeOn 的核心源碼在 subscribeActual 方法中,通過代理的方式使用 SubscribeOnObserver 包裝 Observer 后,設置 Disposable 來將 subscribe 切換到 Scheduler 線程中。

observeOn

observeOn 方法用于指定下游 Observer 回調發生的線程。

   @SchedulerSupport(SchedulerSupport.CUSTOM)
    public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        ObjectHelper.verifyPositive(bufferSize, "bufferSize");
        return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
    }

線程切換需要注意的

RxJava 內置的線程調度器的確可以讓我們的線程切換得心應手,但其中也有些需要注意的地方。

  • 簡單地說,subscribeOn() 指定的就是發射事件的線程,observerOn 指定的就是訂閱者接收事件的線程。
  • 多次指定發射事件的線程只有第一次指定的有效,也就是說多次調用 subscribeOn() 只有第一次的有效,其余的會被忽略。
  • 但多次指定訂閱者接收線程是可以的,也就是說每調用一次 observerOn(),下游的線程就會切換一次。
Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<Integer> e) throws Exception {
                Log.e(TAG, "Observable thread is : " + Thread.currentThread().getName());
                e.onNext(1);
                e.onComplete();
            }
        }).subscribeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnNext(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Log.e(TAG, "After observeOn(mainThread),Current thread is " + Thread.currentThread().getName());
                    }
                })
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Log.e(TAG, "After observeOn(io),Current thread is " + Thread.currentThread().getName());
                    }
                });

輸出:

07-03 14:54:01.177 15121-15438/com.nanchen.rxjava2examples E/RxThreadActivity: Observable thread is : RxNewThreadScheduler-1
07-03 14:54:01.178 15121-15121/com.nanchen.rxjava2examples E/RxThreadActivity: After observeOn(mainThread),Current thread is main
07-03 14:54:01.179 15121-15439/com.nanchen.rxjava2examples E/RxThreadActivity: After observeOn(io),Current thread is RxCachedThreadScheduler-2

實例代碼中,分別用 Schedulers.newThread()Schedulers.io() 對發射線程進行切換,并采用 observeOn(AndroidSchedulers.mainThread()Schedulers.io() 進行了接收線程的切換。可以看到輸出中發射線程僅僅響應了第一個 newThread,但每調用一次 observeOn() ,線程便會切換一次,因此如果我們有類似的需求時,便知道如何處理了。

RxJava 中,已經內置了很多線程選項供我們選擇,例如有:

  • Schedulers.io() 代表io操作的線程, 通常用于網絡,讀寫文件等io密集型的操作;
  • Schedulers.computation() 代表CPU計算密集型的操作, 例如需要大量計算的操作;
  • Schedulers.newThread() 代表一個常規的新線程;
  • AndroidSchedulers.mainThread() 代表Android的主線程

這些內置的 Scheduler 已經足夠滿足我們開發的需求,因此我們應該使用內置的這些選項,而 RxJava 內部使用的是線程池來維護這些線程,所以效率也比較高。

操作符

關于操作符,在官方文檔中已經做了非常完善的講解,并且筆者前面的系列教程中也著重講解了絕大多數的操作符作用,這里受于篇幅限制,就不多做贅述,只挑選幾個進行實際情景的講解。

map

map 操作符可以將一個 Observable 對象通過某種關系轉換為另一個Observable 對象。在 2.x 中和 1.x 中作用幾乎一致,不同點在于:2.x 將 1.x 中的 Func1Func2 改為了 FunctionBiFunction

采用 map 操作符進行網絡數據解析

想必大家都知道,很多時候我們在使用 RxJava 的時候總是和 Retrofit 進行結合使用,而為了方便演示,這里我們就暫且采用 OkHttp3 進行演示,配合 mapdoOnNext ,線程切換進行簡單的網絡請求:
1)通過 Observable.create() 方法,調用 OkHttp 網絡請求;
2)通過 map 操作符集合 gson,將 Response 轉換為 bean 類;
3)通過 doOnNext() 方法,解析 bean 中的數據,并進行數據庫存儲等操作;
4)調度線程,在子線程中進行耗時操作任務,在主線程中更新 UI ;
5)通過 subscribe(),根據請求成功或者失敗來更新 UI 。

Observable.create(new ObservableOnSubscribe<Response>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<Response> e) throws Exception {
                Builder builder = new Builder()
                        .url("http://api.avatardata.cn/MobilePlace/LookUp?key=ec47b85086be4dc8b5d941f5abd37a4e&mobileNumber=13021671512")
                        .get();
                Request request = builder.build();
                Call call = new OkHttpClient().newCall(request);
                Response response = call.execute();
                e.onNext(response);
            }
        }).map(new Function<Response, MobileAddress>() {
                    @Override
                    public MobileAddress apply(@NonNull Response response) throws Exception {
                        if (response.isSuccessful()) {
                            ResponseBody body = response.body();
                            if (body != null) {
                                Log.e(TAG, "map:轉換前:" + response.body());
                                return new Gson().fromJson(body.string(), MobileAddress.class);
                            }
                        }
                        return null;
                    }
                }).observeOn(AndroidSchedulers.mainThread())
                .doOnNext(new Consumer<MobileAddress>() {
                    @Override
                    public void accept(@NonNull MobileAddress s) throws Exception {
                        Log.e(TAG, "doOnNext: 保存成功:" + s.toString() + "\n");
                    }
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<MobileAddress>() {
                    @Override
                    public void accept(@NonNull MobileAddress data) throws Exception {
                        Log.e(TAG, "成功:" + data.toString() + "\n");
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.e(TAG, "失敗:" + throwable.getMessage() + "\n");
                    }
                });

concat

concat 可以做到不交錯的發射兩個甚至多個 Observable 的發射事件,并且只有前一個 Observable 終止(onComplete) 后才會訂閱下一個 Observable

采用 concat 操作符先讀取緩存再通過網絡請求獲取數據

想必在實際應用中,很多時候(對數據操作不敏感時)都需要我們先讀取緩存的數據,如果緩存沒有數據,再通過網絡請求獲取,隨后在主線程更新我們的UI。

concat 操作符簡直就是為我們這種需求量身定做。

利用 concat 的必須調用 onComplete 后才能訂閱下一個 Observable 的特性,我們就可以先讀取緩存數據,倘若獲取到的緩存數據不是我們想要的,再調用 onComplete() 以執行獲取網絡數據的 Observable,如果緩存數據能應我們所需,則直接調用 onNext(),防止過度的網絡請求,浪費用戶的流量。

Observable<FoodList> cache = Observable.create(new ObservableOnSubscribe<FoodList>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<FoodList> e) throws Exception {
                Log.e(TAG, "create當前線程:"+Thread.currentThread().getName() );
                FoodList data = CacheManager.getInstance().getFoodListData();

                // 在操作符 concat 中,只有調用 onComplete 之后才會執行下一個 Observable
                if (data != null){ // 如果緩存數據不為空,則直接讀取緩存數據,而不讀取網絡數據
                    isFromNet = false;
                    Log.e(TAG, "\nsubscribe: 讀取緩存數據:" );
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRxOperatorsText.append("\nsubscribe: 讀取緩存數據:\n");
                        }
                    });

                    e.onNext(data);
                }else {
                    isFromNet = true;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRxOperatorsText.append("\nsubscribe: 讀取網絡數據:\n");
                        }
                    });
                    Log.e(TAG, "\nsubscribe: 讀取網絡數據:" );
                    e.onComplete();
                }

            }
        });

        Observable<FoodList> network = Rx2AndroidNetworking.get("http://www.tngou.net/api/food/list")
                .addQueryParameter("rows",10+"")
                .build()
                .getObjectObservable(FoodList.class);

        // 兩個 Observable 的泛型應當保持一致

        Observable.concat(cache,network)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<FoodList>() {
                    @Override
                    public void accept(@NonNull FoodList tngouBeen) throws Exception {
                        Log.e(TAG, "subscribe 成功:"+Thread.currentThread().getName() );
                        if (isFromNet){
                            mRxOperatorsText.append("accept : 網絡獲取數據設置緩存: \n");
                            Log.e(TAG, "accept : 網絡獲取數據設置緩存: \n"+tngouBeen.toString() );
                            CacheManager.getInstance().setFoodListData(tngouBeen);
                        }

                        mRxOperatorsText.append("accept: 讀取數據成功:" + tngouBeen.toString()+"\n");
                        Log.e(TAG, "accept: 讀取數據成功:" + tngouBeen.toString());
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.e(TAG, "subscribe 失敗:"+Thread.currentThread().getName() );
                        Log.e(TAG, "accept: 讀取數據失敗:"+throwable.getMessage() );
                        mRxOperatorsText.append("accept: 讀取數據失敗:"+throwable.getMessage()+"\n");
                    }
                });

有時候我們的緩存可能還會分為 memory 和 disk ,實際上都差不多,無非是多寫點 Observable ,然后通過 concat 合并即可。

flatMap 實現多個網絡請求依次依賴

想必這種情況也在實際情況中比比皆是,例如用戶注冊成功后需要自動登錄,我們只需要先通過注冊接口注冊用戶信息,注冊成功后馬上調用登錄接口進行自動登錄即可。

我們的 flatMap 恰好解決了這種應用場景,flatMap 操作符可以將一個發射數據的 Observable 變換為多個 Observables ,然后將它們發射的數據合并后放到一個單獨的 Observable,利用這個特性,我們很輕松地達到了我們的需求。

Rx2AndroidNetworking.get("http://www.tngou.net/api/food/list")
                .addQueryParameter("rows", 1 + "")
                .build()
                .getObjectObservable(FoodList.class) // 發起獲取食品列表的請求,并解析到FootList
                .subscribeOn(Schedulers.io())        // 在io線程進行網絡請求
                .observeOn(AndroidSchedulers.mainThread()) // 在主線程處理獲取食品列表的請求結果
                .doOnNext(new Consumer<FoodList>() {
                    @Override
                    public void accept(@NonNull FoodList foodList) throws Exception {
                        // 先根據獲取食品列表的響應結果做一些操作
                        Log.e(TAG, "accept: doOnNext :" + foodList.toString());
                        mRxOperatorsText.append("accept: doOnNext :" + foodList.toString()+"\n");
                    }
                })
                .observeOn(Schedulers.io()) // 回到 io 線程去處理獲取食品詳情的請求
                .flatMap(new Function<FoodList, ObservableSource<FoodDetail>>() {
                    @Override
                    public ObservableSource<FoodDetail> apply(@NonNull FoodList foodList) throws Exception {
                        if (foodList != null && foodList.getTngou() != null && foodList.getTngou().size() > 0) {
                            return Rx2AndroidNetworking.post("http://www.tngou.net/api/food/show")
                                    .addBodyParameter("id", foodList.getTngou().get(0).getId() + "")
                                    .build()
                                    .getObjectObservable(FoodDetail.class);
                        }
                        return null;

                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<FoodDetail>() {
                    @Override
                    public void accept(@NonNull FoodDetail foodDetail) throws Exception {
                        Log.e(TAG, "accept: success :" + foodDetail.toString());
                        mRxOperatorsText.append("accept: success :" + foodDetail.toString()+"\n");
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.e(TAG, "accept: error :" + throwable.getMessage());
                        mRxOperatorsText.append("accept: error :" + throwable.getMessage()+"\n");
                    }
                });

善用 zip 操作符,實現多個接口數據共同更新 UI

在實際應用中,我們極有可能會在一個頁面顯示的數據來源于多個接口,這時候我們的 zip 操作符為我們排憂解難。

zip 操作符可以將多個 Observable 的數據結合為一個數據源再發射出去。

Observable<MobileAddress> observable1 = Rx2AndroidNetworking.get("http://api.avatardata.cn/MobilePlace/LookUp?key=ec47b85086be4dc8b5d941f5abd37a4e&mobileNumber=13021671512")
                .build()
                .getObjectObservable(MobileAddress.class);

        Observable<CategoryResult> observable2 = Network.getGankApi()
                .getCategoryData("Android",1,1);

        Observable.zip(observable1, observable2, new BiFunction<MobileAddress, CategoryResult, String>() {
            @Override
            public String apply(@NonNull MobileAddress mobileAddress, @NonNull CategoryResult categoryResult) throws Exception {
                return "合并后的數據為:手機歸屬地:"+mobileAddress.getResult().getMobilearea()+"人名:"+categoryResult.results.get(0).who;
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(@NonNull String s) throws Exception {
                        Log.e(TAG, "accept: 成功:" + s+"\n");
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.e(TAG, "accept: 失敗:" + throwable+"\n");
                    }
                });

采用 interval 操作符實現心跳間隔任務

想必即時通訊等需要輪訓的任務在如今的 APP 中已是很常見,而 RxJava 2.x 的 interval 操作符可謂完美地解決了我們的疑惑。

這里就簡單的意思一下輪訓。

private Disposable mDisposable;
    @Override
    protected void doSomething() {
        mDisposable = Flowable.interval(1, TimeUnit.SECONDS)
                .doOnNext(new Consumer<Long>() {
                    @Override
                    public void accept(@NonNull Long aLong) throws Exception {
                        Log.e(TAG, "accept: doOnNext : "+aLong );
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(@NonNull Long aLong) throws Exception {
                        Log.e(TAG, "accept: 設置文本 :"+aLong );
                        mRxOperatorsText.append("accept: 設置文本 :"+aLong +"\n");
                    }
                });
    }

    /**
     * 銷毀時停止心跳
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDisposable != null){
            mDisposable.dispose();
        }
    }

RxJava 1.x 如何平滑升級到 RxJava 2.x?

由于 RxJava 2.x 變化較大無法直接升級,幸運的是,官方為我們提供了 RxJava2Interrop 這個庫,可以方便地把 RxJava 1.x 升級到 RxJava 2.x,或者將 RxJava 2.x 轉回到 RxJava 1.x。

從16年11月份推出RxJava 2.0 ,到現在差不多大半年的時間里,RxJava已經來到了2.x時代,RxJava 1.x 可能也慢慢地被2.x 代替。RxJava 2.x在Reactive-Streams規范的基礎上從頭開始完全重寫,雖然操作符基本沒有發生變化,但是因為Reactive-Streams具有不同的架構,因此對一些眾所周知的RxJava類型進行了更改。

RxJava 2.x相對與1.x還是有很多的不同,RxJava的類型更改了,很多類的命名和方法的命名發生了變化(可能功能與1.x相同),要想從RxJava 1.x 順利地過渡到2.x, 就得了解這些變化。因此本教程就帶你了解這些變化,從而能更快地上手RxJava2.x。

本篇文章就來先了解一下RxJava 2.x的5種響應類型。

[圖片上傳失敗...(image-55bb3b-1531139819666)]

一、RxJava 2.x中5種類型介紹

1 . Observable and Flowable

關于在RxJava 0.x版本引入背壓的一個小的遺憾是,沒有設計一個單獨的基礎反應類,而是對Observable本身進行了改裝。背壓的主要問題在于熱源(如:UI事件),不能合理地反壓并導致不可預料的異常MissingBackpressureException,這是初學者不期望看到的。

在RxJava 2.x版本中修復了這種情況,將o.reactivex.Observable作為非背壓,引入新的io.reactivex.Flowable作為啟用背壓基礎反應類。

好消息是,在2.x版本中,主要的操作符保持不變(同1.x版本),壞消息是,在導入的時候應當小心,它可能會無意的選擇非背壓的o.reactivex.Observable.

我們應該選哪種?

當構建數據流(作為RxJava的最終消費者)或考慮您的2.x兼容庫應該采取和返回什么類型時,您可以考慮幾個因素,以幫助您避免諸如MissingBackpressureException或OutOfMemoryError之類的問題。

Observable使用場景:

  • 數據流最長不超過1000個元素,即隨著時間的流逝,應用沒有機會報OOME(OutOfMemoryError)錯誤。
  • 處理諸如鼠標移動或觸摸事件之類的GUI事件

Flowable使用場景:

  • 處理超過10K+ 的元素流
  • 從磁盤讀取(解析文件)
  • 從數據庫讀取數據
  • 從網絡獲取數據流
2 . Single 使用介紹

Single是2.x版本中的一種基礎響應類型,Single是從頭開始重新設計的,能單獨發射一個onSuccess或者onError事件,它現在的架構來自于the Reactive-Streams設計。它的消費者類型已經從接受rx.Subscriptionrx.Single.SingleSubscriber<T>變為了io.reactivex.SingleObserver<T>,有3個方法:

interface SingleObserver<T> {
    void onSubscribe(Disposable d);
    void onSuccess(T value);
    void onError(Throwable error);
}

3 . Completable使用介紹

Completeble類型基本保持不變,1.x的版本已經沿著Reactive-Streams風格設計,所以沒有用戶級別的更改。

相似地命名改變,rx.Completable.CompletableSubscriber變為帶有onSubscribe(Disposable)方法的io.reactivex.CompletableObserver:

interface CompletableObserver<T> {
    void onSubscribe(Disposable d);
    void onComplete();
    void onError(Throwable error);
}

4 . Maybe 使用介紹

RxJava 2.0.0-RC2 介紹了一個新的基礎響應類型,它叫做Maybe。從概念上來將,它像是 Single和Completable的結合,它可能發射0個或者1個項目,或者一個error信號。

Maybe類通過依賴MaybeSource作為它的基礎接口類型MaybeObserver<T>作為信號響應接口并且遵循協議onSubscribe (onSuccess | onError | onComplete)?因為最多可能發射1個元素,所以Maybe類型沒有背壓的概念(因為它沒有像Flowable和Observable一樣有未知長度的可膨脹緩沖區)

這意味著onSubscribe(Disposable)的調用潛在地跟隨著其他onXXX方法之一的調用,不同于Flowable,如果這兒只有一個信號值發射信號,那么只有onSuccess被調用,而不會調用complete

二、RxJava 2.x中5種類型使用示例

1 . Observable示例

在寫示例之前,我們先來回顧一下 1.x 版本是如何創建Observable和如何訂閱的:
RxJava 1.x :

  //創建 observable
       Observable observable =  Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext("hello world");
                subscriber.onCompleted();
            }
        }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());

       //訂閱方式一
        observable.subscribe(new Subscriber() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Object o) {

            }
        });

        // 訂閱方式二
         observable.subscribe(new Action1() {
             @Override
             public void call(Object o) {
                 // onNext
             }
         });

通過create方法創建Observable,接收一個OnSubscribe接口,其中有一個回調方法call,參數為Subscriber,我們用Subscriber來發射數據。通過subscribe方法來訂閱,可以接收一個Subscriber 實現全部方法,也可以接收一個Action1只實現onNext方法。

RxJava 2.x :

       //創建Observable
        Observable observable = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
                Log.e(TAG,"start emitter data");
                e.onNext("Hello");
                e.onNext("world");
                e.onComplete();
            }
        })
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());

          // 訂閱方式一:下游消費者 Observer
          observable.subscribe(new Observer<String>() {
              @Override
             public void onSubscribe(@NonNull Disposable d) {
            // onSubscribe 是2.x新添加的方法,在發射數據前被調用,相當于1.x的onStart方法
                 Log.e(TAG,"onSubscribe");
              }

              @Override
              public void onNext(@NonNull String s) {
                  Log.e(TAG,"onNext");
                  Log.e(TAG,s);
              }

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

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

         // 訂閱方式二:Consumer
         observable.subscribe(new Consumer<String>() {
             @Override
             public void accept(@NonNull String o) throws Exception {
                  Log.e(TAG,"consumer:"+o);
             }
         });

打印結果如下:

06-25 14:31:35.435 21505-21505/com.zhouwei.demoforrxjava2 E/MainActivity: onSubscribe
06-25 14:31:35.437 21505-21853/com.zhouwei.demoforrxjava2 E/MainActivity: start emitter data
06-25 14:31:35.438 21505-21505/com.zhouwei.demoforrxjava2 E/MainActivity: onNext:Hello
06-25 14:31:35.438 21505-21505/com.zhouwei.demoforrxjava2 E/MainActivity: onNext:world
06-25 14:31:35.438 21505-21505/com.zhouwei.demoforrxjava2 E/MainActivity: onComplete

其實我們可以對比一下,1.x 和 2.x 方法都試一樣的,只是它們所接收的響應接口改變了,對應變化如下:

 RxJava 1.x       ->   RxJava 2.x
 ---------------------------------------
 OnSubscribe<T>   ->   ObservableOnSubscribe<T>
 Subscriber<T>    ->   Observer<T>
 Subscriber<T>    ->   ObservableEmitter<T>
 Action1<T>       ->   Consumer<T>

RxJava 2.x 中對這些接口進行了重新設計,讓一個接口的職責更加單一,類的命名和方法命名與它的功能更佳符合(見名知意)。如在1.x 中,Subscriber 既能發射數據,又能消費數據,充當觀察者和被觀察者。在2.x 中 把它拆解成了2個接口。ObservableEmitter<T>專門用來發射數據,Consumer 專門用來消費數據。 除此之外,在RxJava 2.x 中,多了一個void onSubscribe(@NonNull Disposable d)回調方法,參數為Disposable,Disposable是用來解除訂閱關系的,這讓我們的解除訂閱變得更佳容易(比起1.x 通過subscribe返回 Subscription)。

上面對比了在RxJava 1.x 和2.x 版本創建Observable的方式,其實在RxJava 2.x中,這5種類型的用法是非常相似的,它們的接口命名規則相同,只要你知道其中一種,就知道其他幾種類型該如何在上游發射數據和在下游消費數據。create接收的類型都為xxxOnSubscrible(xxx為5種類型對應的名字),發射器為xxxEmitter,具體如下表:

RxJava 2.x 類型 create參數(響應接口) 發射器 Observer
Observable ObservableOnSubscribe ObservableEmitter Observer
Flowable FlowableOnSubscribe FlowableEmitter FlowableSubscriber
Single SingleOnSubscribe SingleEmitter SingleObserver
Completable CompletableOnSubscribe CompletableEmitter CompletableObserver
Maybe MaybeOnSubscribe MaybeEmitter MaybeObserver
2 . Flowable示例

上面對比了RxJava 1.x 和 2.x 創建使用Observable的方式,并且總結了2.x 相關類的改變,如上面表。那么使用Flowable的方式和Observable是很相似的,看一下代碼:

Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(@NonNull FlowableEmitter<Integer> e) throws Exception {
                Log.e(TAG,"start send data ");
                for(int i=0;i<100;i++){
                    e.onNext(i);
                }
                e.onComplete();
            }
        }, BackpressureStrategy.DROP)//指定背壓策略
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new FlowableSubscriber<Integer>() {
            @Override
            public void onSubscribe(@NonNull Subscription s) {
               //1, onSubscribe 是2.x新添加的方法,在發射數據前被調用,相當于1.x的onStart方法
               //2, 參數為  Subscription ,Subscription 可用于向上游請求發射多少個元素,也可用于取笑請求
               //3,  必須要調用Subscription 的request來請求發射數據,不然上游是不會發射數據的。
               Log.e(TAG,"onSubscribe...");
               subscription = s;
                s.request(100);
            }

            @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和Observable的使用基本相同,只不過Observable不支持背壓,而Flowable支持背壓。使用的時候,還是要注意幾個小細節:
1,創建Flowable的時候需要指定一個背壓策略,本文使用的是PBackpressureStrategy.DROP(丟棄策略),RxJava 2.x中,內置了5種背壓策略,由于篇幅有限,背壓和背壓策略下一篇拿出來單獨講。
2,onSubscribe 回調方法中,參數是Subscription而不是Disposable,前文說過,RxJava 2.x 中,訂閱的管理換成了Disposable,但是Flowable使用的是Subscription,這個Subscription不是1.x 版本中的Subscription,雖然它有取消訂閱的能力。主要用于請求上游元素和取消訂閱。
3,在使用Flowable的時候,必須調用Subscription 的requsest方法請求,不然上游是不會發射數據的。看request的方法解釋:

[圖片上傳失敗...(image-ae901f-1531139819664)]

3 . Single、Completable 和 Maybe 示例

Single、Completable和Maybe就比較簡單,Single用于只發射一個數據,Completable不發送數據,它給下游發射一個信號。而Maybe則是Single和Completable的結合,根據名字就可以看出,它的結果是不確定的,可能發發射0(Completable)或1(Single) 個元素,或者收到一個Error信號。

Single示例:

Single.create(new SingleOnSubscribe<Boolean>() {
            @Override
            public void subscribe(@NonNull SingleEmitter<Boolean> e) throws Exception {
                Log.e(TAG,"subscribe...");
                e.onSuccess(true);
            }
        })
        .observeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new SingleObserver<Boolean>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                Log.e(TAG,"onSubscribe...");
            }

            @Override
            public void onSuccess(@NonNull Boolean aBoolean) {
                Log.e(TAG,"onSuccess...");
            }

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

Single只發射一個元素,所以沒有complete 方法,不像Observable或者Flowable,數據發射完成之后,需要調用complete告訴下游已經完成。

Completable示例:

 Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(@NonNull CompletableEmitter e) throws Exception {
               Log.e(TAG,"start send data");
               e.onComplete();
            }
        }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new CompletableObserver() {
              @Override
              public void onSubscribe(@NonNull Disposable d) {
                  Log.e(TAG,"onSubscribe");
              }

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

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

Completable 不會發射數據,只會給下游發送一個信號。回調 onComplete方法。

Maybe示例:

 Maybe.create(new MaybeOnSubscribe<Boolean>() {
            @Override
            public void subscribe(@NonNull MaybeEmitter<Boolean> e) throws Exception {
                Log.e(TAG,"start send data");
                 e.onSuccess(true);
                 e.onComplete();

            }
        }).subscribeOn(Schedulers.io())

           .observeOn(AndroidSchedulers.mainThread())

           .subscribe(new MaybeObserver<Boolean>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                Log.e(TAG,"onSubscribe");
            }

            @Override
            public void onSuccess(@NonNull Boolean aBoolean) {
                Log.e(TAG,"1->onSuccess:"+aBoolean);
            }

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

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

Maybe是Single和Completable的結合,需要注意的是onSuccessonComplete方法只會執行其中一個,這不同于Observable和Flowable最后是以onComplete()結尾.

如上面的代碼,先調用onSuccess發射一個元素,再調用onComplete

e.onComplete();
e.onSuccess(true);

最后打印結果如下:

E/MainActivity: onSubscribe
E/MainActivity: start send data
E/MainActivity: onComplete

可以看到只回調了 onComplete,我們把調用的順序調換一下:

e.onSuccess(true);
e.onComplete();

打印結果如下:

E/MainActivity: onSubscribe
E/MainActivity: start send data
E/MainActivity: 1->onSuccess:true

可以看到調換了之后打印OnSucces()而沒有打印onComplete(),這也印證了只會回調其中之一。

三、總結

RxJava 2.x 相比于 1.x 還是有很大的變化,雖然操作符基本不變,但是很多類和接口都是基于Reactive-Streams 規范重新設計的,命名也發生了變換,要想玩轉RxJava 2.x ,你得了解這些變化和使用場景,本文介紹了RxJava 2.x 的5種基礎響應類型,希望對才開始學習RxJava 2.x 的同學有所幫助。

作者:依然范特稀西
鏈接:http://www.lxweimin.com/p/a2aa585ff6fd
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

RxJava 到底是什么

一個詞:異步

RxJava 在 GitHub 主頁上的自我介紹是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 Java VM 上使用可觀測的序列來組成異步的、基于事件的程序的庫)。這就是 RxJava ,概括得非常精準。

然而,對于初學者來說,這太難看懂了。因為它是一個『總結』,而初學者更需要一個『引言』。

其實, RxJava 的本質可以壓縮為異步這一個詞。說到根上,它就是一個實現異步操作的庫,而別的定語都是基于這之上的。

RxJava 好在哪

換句話說,『同樣是做異步,為什么人們用它,而不用現成的 AsyncTask / Handler / XXX / ... ?』

一個詞:簡潔

異步操作很關鍵的一點是程序的簡潔性,因為在調度過程比較復雜的情況下,異步代碼經常會既難寫也難被讀懂。 Android 創造的 AsyncTaskHandler ,其實都是為了讓異步代碼更加簡潔。RxJava 的優勢也是簡潔,但它的簡潔的與眾不同之處在于,隨著程序邏輯變得越來越復雜,它依然能夠保持簡潔。

舉個例子
舉個例子

假設有這樣一個需求:界面上有一個自定義的視圖 imageCollectorView ,它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來任意增加顯示的圖片。現在需要程序將一個給出的目錄數組 File[] folders 中每個目錄下的 png 圖片都加載出來并顯示在 imageCollectorView 中。需要注意的是,由于讀取圖片的這一過程較為耗時,需要放在后臺執行,而圖片的顯示則必須在 UI 線程執行。常用的實現方式有多種,我這里貼出其中一種:

new Thread() {
    @Override
    public void run() {
        super.run();
        for (File folder : folders) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.getName().endsWith(".png")) {
                    final Bitmap bitmap = getBitmapFromFile(file);
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            imageCollectorView.addImage(bitmap);
                        }
                    });
                }
            }
        }
    }
}.start();

而如果使用 RxJava ,實現方式是這樣的:

Observable.from(folders)
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })
    .filter(new Func1<File, Boolean>() {
        @Override
        public Boolean call(File file) {
            return file.getName().endsWith(".png");
        }
    })
    .map(new Func1<File, Bitmap>() {
        @Override
        public Bitmap call(File file) {
            return getBitmapFromFile(file);
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) {
            imageCollectorView.addImage(bitmap);
        }
    });

那位說話了:『你這代碼明明變多了啊!簡潔個毛啊!』大兄弟你消消氣,我說的是邏輯的簡潔,不是單純的代碼量少(邏輯簡潔才是提升讀寫代碼速度的必殺技對不?)。觀察一下你會發現, RxJava 的這個實現,是一條從上到下的鏈式調用,沒有任何嵌套,這在邏輯的簡潔性上是具有優勢的。當需求變得復雜時,這種優勢將更加明顯(試想如果還要求只選取前 10 張圖片,常規方式要怎么辦?如果有更多這樣那樣的要求呢?再試想,在這一大堆需求實現完兩個月之后需要改功能,當你翻回這里看到自己當初寫下的那一片迷之縮進,你能保證自己將迅速看懂,而不是對著代碼重新捋一遍思路?)。

另外,如果你的 IDE 是 Android Studio ,其實每次打開某個 Java 文件的時候,你會看到被自動 Lambda 化的預覽,這將讓你更加清晰地看到程序邏輯:

Observable.from(folders)
    .flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
    .filter((Func1) (file) -> { file.getName().endsWith(".png") })
    .map((Func1) (file) -> { getBitmapFromFile(file) })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });

如果你習慣使用 Retrolambda ,你也可以直接把代碼寫成上面這種簡潔的形式。而如果你看到這里還不知道什么是 Retrolambda ,我不建議你現在就去學習它。原因有兩點:1. Lambda 是把雙刃劍,它讓你的代碼簡潔的同時,降低了代碼的可讀性,因此同時學習 RxJava 和 Retrolambda 可能會讓你忽略 RxJava 的一些技術細節;2. Retrolambda 是 Java 6/7 對 Lambda 表達式的非官方兼容方案,它的向后兼容性和穩定性是無法保障的,因此對于企業項目,使用 Retrolambda 是有風險的。所以,與很多 RxJava 的推廣者不同,我并不推薦在學習 RxJava 的同時一起學習 Retrolambda。事實上,我個人雖然很欣賞 Retrolambda,但我從來不用它。

在Flipboard 的 Android 代碼中,有一段邏輯非常復雜,包含了多次內存操作、本地文件操作和網絡操作,對象分分合合,線程間相互配合相互等待,一會兒排成人字,一會兒排成一字。如果使用常規的方法來實現,肯定是要寫得欲仙欲死,然而在使用 RxJava 的情況下,依然只是一條鏈式調用就完成了。它很長,但很清晰。

所以, RxJava 好在哪?就好在簡潔,好在那把什么復雜邏輯都能穿成一條線的簡潔。

API 介紹和原理簡析

這個我就做不到一個詞說明了……因為這一節的主要內容就是一步步地說明 RxJava 到底怎樣做到了異步,怎樣做到了簡潔。

1. 概念:擴展的觀察者模式

RxJava 的異步實現,是通過一種擴展的觀察者模式來實現的。

觀察者模式

先簡述一下觀察者模式,已經熟悉的可以跳過這一段。

觀察者模式面向的需求是:A 對象(觀察者)對 B 對象(被觀察者)的某種變化高度敏感,需要在 B 變化的一瞬間做出反應。舉個例子,新聞里喜聞樂見的警察抓小偷,警察需要在小偷伸手作案的時候實施抓捕。在這個例子里,警察是觀察者,小偷是被觀察者,警察需要時刻盯著小偷的一舉一動,才能保證不會漏過任何瞬間。程序的觀察者模式和這種真正的『觀察』略有不同,觀察者不需要時刻盯著被觀察者(例如 A 不需要每過 2ms 就檢查一次 B 的狀態),而是采用注冊(Register)或者稱為訂閱(Subscribe)的方式,告訴被觀察者:我需要你的某某狀態,你要在它變化的時候通知我。 Android 開發中一個比較典型的例子是點擊監聽器 OnClickListener 。對設置 OnClickListener 來說, View 是被觀察者, OnClickListener 是觀察者,二者通過 setOnClickListener() 方法達成訂閱關系。訂閱之后用戶點擊按鈕的瞬間,Android Framework 就會將點擊事件發送給已經注冊的 OnClickListener 。采取這樣被動的觀察方式,既省去了反復檢索狀態的資源消耗,也能夠得到最高的反饋速度。當然,這也得益于我們可以隨意定制自己程序中的觀察者和被觀察者,而警察叔叔明顯無法要求小偷『你在作案的時候務必通知我』。

OnClickListener 的模式大致如下圖:

OnClickListener 觀察者模式

如圖所示,通過 setOnClickListener() 方法,Button 持有 OnClickListener 的引用(這一過程沒有在圖上畫出);當用戶點擊時,Button 自動調用 OnClickListeneronClick() 方法。另外,如果把這張圖中的概念抽象出來(Button -> 被觀察者、OnClickListener -> 觀察者、setOnClickListener() -> 訂閱,onClick() -> 事件),就由專用的觀察者模式(例如只用于監聽控件點擊)轉變成了通用的觀察者模式。如下圖:

通用觀察者模式

而 RxJava 作為一個工具庫,使用的就是通用形式的觀察者模式。

RxJava 的觀察者模式

RxJava 有四個基本概念:Observable (可觀察者,即被觀察者)、 Observer (觀察者)、 subscribe (訂閱)、事件。ObservableObserver 通過 subscribe() 方法實現訂閱關系,從而 Observable 可以在需要的時候發出事件來通知 Observer

與傳統觀察者模式不同, RxJava 的事件回調方法除了普通事件 onNext() (相當于 onClick() / onEvent())之外,還定義了兩個特殊的事件:onCompleted()onError()

  • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標志。

  • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。

  • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

  • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標志。

  • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。

  • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

  • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標志。

  • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。

  • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

  • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標志。

  • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。

  • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

  • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標志。

  • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。

  • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容