RxJava 操作符系列二

Code4Android發表於2016-12-13

RxJava操作符原始碼傳送門


在上篇文章RxJava操作符系列一我們介紹的操作符幾乎都是建立被觀察者的操作符,那麼今天的這篇文章就介紹一下經常用到的轉換操作符。話不多說,開始上車。

Map

該操作符是對原始Observable發射的每一項資料運用一個函式,然後返回一個發射這些結果的Observable。

例如我們有一個整形陣列的資料,當大於5時輸出為true,則程式碼實現

Integer[] integers = {0, 9, 6, 4, 8};
        Observable.from(integers).map(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
                Log.e(TAG, "call: "+integer);
                return (integer > 5);
            }
        }).subscribe(new Subscriber<Boolean>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted: ");
            }

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

            @Override
            public void onNext(Boolean aBoolean) {
                Log.e(TAG, "onNext: "+aBoolean);
            }
        });複製程式碼

日誌輸出資訊

call: 0
onNext: false
call: 9
onNext: true
call: 6
onNext: true
call: 4
onNext: false
call: 8
onNext: true
onCompleted:複製程式碼

對於map,他可以將將資料來源變換為你想要的型別,比如,你想獲取有一個Student物件(裡面age,name屬性)那麼我們可以通過map只獲取name。接下來。我們再舉個例子,我們根據一個圖片路徑獲取圖片並將圖片設定到ImageView,然後將ImageView加的我們的佈局中。

String path = Environment.getExternalStorageDirectory()+ File.separator+"aaa.jpg";
        Observable.just(path)
                .subscribeOn(Schedulers.io())
                .map(new Func1<String, Bitmap>() {
                    @Override
                    public Bitmap call(String s) {
                        Bitmap bitmap = BitmapFactory.decodeFile(s);
                        Log.e(TAG, "call: Bitmap"+bitmap);
                        return bitmap;
                    }
                }).map(new Func1<Bitmap, ImageView>() {
            @Override
            public ImageView call(Bitmap bitmap) {
                Log.e(TAG, "call: ImageView");
                ImageView imageView = new ImageView(getActivity());
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
                imageView.setLayoutParams(params);
                imageView.setImageBitmap(bitmap);
                return imageView;
            }
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ImageView>() {
                    @Override
                    public void onCompleted() {
                        Log.e(TAG, "onCompleted: ");
                    }
                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "onError: ");
                    }

                    @Override
                    public void onNext(ImageView imageView) {
                        Log.e(TAG, "onNext: ");
                        layout.addView(imageView);
                    }
                });複製程式碼

Cast

該操作符就是做一些強制型別轉換操作的。例如,當我們在頁面跳轉時資料物件往往是序列化的,當我們在新的頁面收到資料後就要強制轉換為我們想要的型別。cast操作符也可以實現這樣的功能。如下

  Observable.just(serializable).cast(FileInfo.class).subscribe(new Subscriber<FileInfo>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted: " );
            }

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

            @Override
            public void onNext(FileInfo fileInfo) {
                Log.e(TAG, "onNext: "+fileInfo.toString());
                tv1.append("\n"+fileInfo.toString());
            }
        });複製程式碼

不過在該操作符實際用途並沒有那麼的廣泛,很少用到,當然這個操作符也可以達到java 中instanceof相同的作用,用於型別檢查,當不是該型別就會執行onError()方法。

FlatMap

該操作符與map操作符的區別是它將一個發射資料的Observable變換為多個Observables,然後將它們發射的資料合併後放進一個單獨的Observable.

 Integer[] integers = {1, 2, 3};
        Observable.from(integers).flatMap(new Func1<Integer, Observable<String>>() {
            @Override
            public Observable<String> call(final Integer integer) {
                return Observable.create(new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(Subscriber<? super String> subscriber) {
                        Log.e(TAG, "call: FlatMap " + Thread.currentThread().getName());
                        try {
                            Thread.sleep(200);
                            subscriber.onNext(integer + 100 + " FlatMap");
                            subscriber.onCompleted();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            subscriber.onError(e);
                        }
                    }
                }).subscribeOn(Schedulers.newThread());
            }
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {
                        Log.e(TAG, "onCompleted: FlatMap");
                    }

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

                    @Override
                    public void onNext(String s) {
                        Log.e(TAG, "onNext: FlatMap " + s);
                    }
                });複製程式碼

列印日誌資訊

call: FlatMap RxNewThreadScheduler-2
call: FlatMap RxNewThreadScheduler-3
call: FlatMap RxNewThreadScheduler-4
onNext: FlatMap 101 FlatMap
onNext: FlatMap 102 FlatMap
onNext: FlatMap 103 FlatMap
onCompleted: FlatMap複製程式碼

ConcatMap

該操作符是類似於最簡單版本的flatMap,但是它按次序連線而不是合併那些生成的Observables,然後產生自己的資料序列.將上述flatMap程式碼更改如下

Integer[] integers = {1, 2, 3};
        Observable.from(integers).concatMap(new Func1<Integer, Observable<String>>() {
            @Override
            public Observable<String> call(final Integer integer) {
                return Observable.create(new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(Subscriber<? super String> subscriber) {
                        Log.e(TAG, "call:2 ConcatMap " + Thread.currentThread().getName());
                        try {
                            Thread.sleep(200);
                            subscriber.onNext(integer + 100 + " ConcatMap");
                            subscriber.onCompleted();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            subscriber.onError(e);
                        }
                    }
                }).subscribeOn(Schedulers.newThread());
            }
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {
                        Log.e(TAG, "onCompleted: ConcatMap");
                    }

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

                    @Override
                    public void onNext(String s) {
                        Log.e(TAG, "onNext: ConcatMap " +s);
                    }
                });複製程式碼

輸出日誌資訊

call:2 ConcatMap RxNewThreadScheduler-5
onNext: ConcatMap 101 ConcatMap 
call:2 ConcatMap RxNewThreadScheduler-6
onNext: ConcatMap 102 ConcatMap 
call:2 ConcatMap RxNewThreadScheduler-7
onNext: ConcatMap 103 ConcatMap 
onCompleted: ConcatMap複製程式碼

通過該操作符和flatMap輸出的日誌資訊,很容易看出flatMap並沒有保證資料來源的順序性,但是ConcatMap操作符保證了資料來源的順序性。在應用中,如果你對資料的順序性有要求的話,就需要使用ConcatMap。若沒有要求,二者皆可使用。

SwitchMap

當原始Observable發射一個新的資料(Observable)時,它將取消訂閱並停止監視產生執之前那個資料的Observable,只監視當前這一個.

Integer[] integers = {1, 2, 3};
 Observable.from(integers).switchMap(new Func1<Integer, Observable<String>>() {
            @Override
            public Observable<String> call(Integer integer) {
                Log.e(TAG, "call: SwitchMap" + Thread.currentThread().getName());
                //如果不通過subscribeOn(Schedulers.newThread())在在子執行緒模擬併發操作,所有資料來源依然會全部輸出,也就是併發操作此操作符才有作用
                //若在此通過Thread。sleep()設定等待時間,則輸出資訊會不一樣。相當於模擬併發程度
                return Observable.just((integer + 100) + "SwitchMap").subscribeOn(Schedulers.newThread());
            }
        }).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted: SwitchMap");
            }

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

            @Override
            public void onNext(String s) {
                Log.e(TAG, "onNext: SwitchMap "+s);
            }
        });複製程式碼

輸出日誌資訊

call: SwitchMapmain
call: SwitchMapmain
call: SwitchMapmain
onNext: SwitchMap 106SwitchMap
onCompleted: SwitchMap複製程式碼

當資料來源較多時,並不一定是隻輸出最後一項資料,有可能輸出幾項資料,也可能是全部。

GroupBy

看到這個詞你就應該想到了這個操作符的作用,就是你理解的含義,他將資料來源按照你的約定進行分組。我們通過groupBy實行將1到10的資料進行就劃分,程式碼如下

 Observable.range(1, 10).groupBy(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
                return integer % 2 == 0;
            }
        }).subscribe(new Subscriber<GroupedObservable<Boolean, Integer>>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted:1 ");
            }

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

            @Override
            public void onNext(GroupedObservable<Boolean, Integer> booleanIntegerGroupedObservable) {
                booleanIntegerGroupedObservable.toList().subscribe(new Subscriber<List<Integer>>() {
                    @Override
                    public void onCompleted() {
                        Log.e(TAG, "onCompleted:2 " );
                    }

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

                    @Override
                    public void onNext(List<Integer> integers) {
                        Log.e(TAG, "onNext:2 "+integers);
                    }
                });
            }
        });複製程式碼

輸出日誌資訊

onNext:2 [1, 3, 5, 7, 9]
onCompleted:2 
onNext:2 [2, 4, 6, 8, 10]
onCompleted:2 
onCompleted:1複製程式碼

在上面程式碼中booleanIntegerGroupedObservable變數有一個getKey()方法,該方法返回的是分組的key,他的值就是groupBy方法call回撥所用函式的值,在上面也就是integer % 2 == 0的值,及true和false。有幾個分組也是有此值決定的。

Scan

操作符對原始Observable發射的第一項資料應用一個函式,然後將那個函式的結果作為自己的第一項資料發射。它將函式的結果同第二項資料一起填充給這個函式來產生它自己的第二項資料。它持續進行這個過程來產生剩餘的資料序列。
例如計算1+2+3+4的和

Observable.range(1,4).scan(new Func2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) {
                Log.e(TAG, "call: integer:"+integer+"  integer2 "+integer2);
                return integer+integer2;
            }
        }).subscribe(new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted: ");
            }

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

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

輸出日誌資訊

onNext: 1
call: integer:1  integer2 2
onNext: 3
call: integer:3  integer2 3
onNext: 6
call: integer:6  integer2 4
onNext: 10
onCompleted:複製程式碼

對於scan有一個過載方法,可以設定一個初始值,如上面程式碼,初始值設定為10,只需將scan加個引數scan(10,new Func2)。

Buffer

操作符將一個Observable變換為另一個,原來的Observable正常發射資料,變換產生的Observable發射這些資料的快取集合,如果原來的Observable發射了一個onError通知,Buffer會立即傳遞這個通知,而不是首先發射快取的資料,即使在這之前快取中包含了原始Observable發射的資料。
示例程式碼

Observable.range(10, 6).buffer(2).subscribe(new Subscriber<List<Integer>>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted: ");
            }

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

            @Override
            public void onNext(List<Integer> integers) {
                Log.e(TAG, "onNext: " + integers);
            }
        });複製程式碼

輸出日誌資訊

onNext: [10, 11]
onNext: [12, 13]
onNext: [14, 15]
onCompleted:複製程式碼

上面一次性訂閱兩個資料,如果設定引數為6,就一次性訂閱。buffer的另一過載方法buffer(count, skip)從原始Observable的第一項資料開始建立新的快取(長度count),此後每當收到skip項資料,用count項資料填充快取:開頭的一項和後續的count-1項,它以列表(List)的形式發射快取,取決於count和skip的值,這些快取可能會有重疊部分(比如skip < count時),也可能會有間隙(比如skip > count時)。具體執行結果,你可以設定不同的skip和count觀察輸出日誌,檢視執行結果及流程。

Window

Window和Buffer類似,但不是發射來自原始Observable的資料包,它發射的是Observables,這些Observables中的每一個都發射原始Observable資料的一個子集,最後發射一個onCompleted通知。

Observable.range(10, 6).window(2).subscribe(new Subscriber<Observable<Integer>>() {
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted1: ");
            }

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

            @Override
            public void onNext(Observable<Integer> integerObservable) {
                Log.e(TAG, "onNext1: ");
                tv1.append("\n");
                integerObservable.subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onCompleted() {
                        Log.e(TAG, "onCompleted2: ");
                    }

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

                    @Override
                    public void onNext(Integer integer) {
                        Log.e(TAG, "onNext2: "+integer);
                    }
                });
            }
        });複製程式碼

輸出日誌資訊

onNext2: 10
onNext2: 11
onCompleted2: 
onNext2: 12
onNext2: 13
onCompleted2: 
onNext2: 14
onNext2: 15
onCompleted2: 
onCompleted1:複製程式碼

window和buffer一樣也有不同的過載方法。這兩個操作符相對其他操作符不太容易理解,可以去RxJava GitHub理解,裡面有圖示解析。當然最好的理解方式就是通過更改變數的值,去觀察輸出的日誌資訊。

好了,這篇文章就介紹到這裡。若文中有錯誤的地方,歡迎指正。謝謝。

相關文章