给初学者的RxJava2.0教程(三)

Outlinejava

[TOC]api

前言

上一节讲解了线程调度, 而且举了两个实际中的例子, 其中有一个登陆的例子, 不知你们有没有想过这么一个问题, 若是是一个新用户, 必须先注册, 等注册成功以后再自动登陆该怎么作呢. 网络

很明显, 这是一个嵌套的网络请求, 首先须要去请求注册, 待注册成功回调了再去请求登陆的接口.app

咱们固然能够想固然的写成这样:ide

private void login() {
        api.login(new LoginRequest())
                .subscribeOn(Schedulers.io())               //在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求结果
                .subscribe(new Consumer<LoginResponse>() {
                    @Override
                    public void accept(LoginResponse loginResponse) throws Exception {
                        Toast.makeText(MainActivity.this, "登陆成功", Toast.LENGTH_SHORT).show();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(MainActivity.this, "登陆失败", Toast.LENGTH_SHORT).show();
                    }
                });
    }

    private void register() {
        api.register(new RegisterRequest())
                .subscribeOn(Schedulers.io())               //在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求结果
                .subscribe(new Consumer<RegisterResponse>() {
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        Toast.makeText(MainActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
                        login();   //注册成功, 调用登陆的方法
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(MainActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
                    }
                });
    }复制代码

(其实能写成这样的代码的人也很不错了, 至少没写到一块儿...)函数

这样的代码可以工做, 但不够优雅, 经过本节的学习, 可让咱们用一种更优雅的方式来解决这个问题.学习

正题

先来看看最简单的变换操做符map吧this

Map

map是RxJava中最简单的一个变换操做符了, 它的做用就是对上游发送的每个事件应用一个函数, 使得每个事件都按照指定的函数去变化. 用事件图表示以下:spa

map.png

图中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 "This is result " + integer;
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.d(TAG, s);
            }
        });复制代码

在上游咱们发送的是数字类型, 而在下游咱们接收的是String类型, 中间起转换做用的就是map操做符, 运行结果为:

D/TAG: This is result 1 
 D/TAG: This is result 2 
 D/TAG: This is result 3复制代码

经过Map, 能够将上游发来的事件转换为任意的类型, 能够是一个Object, 也能够是一个集合, 如此强大的操做符你难道不想试试?

接下来咱们来看另一个广为人知的操做符flatMap.

FlatMap

flatMap是一个很是强大的操做符, 先用一个比较难懂的概念说明一下:

FlatMap将一个发送事件的上游Observable变换为多个发送事件的Observables,而后将它们发射的事件合并后放进一个单独的Observable里.

这句话比较难以理解, 咱们先通俗易懂的图片来详细的讲解一下, 首先先来看看总体的一个图片:

flatmap.png

先看看上游, 上游发送了三个事件, 分别是1,2,3, 注意它们的颜色.

中间flatMap的做用是将圆形的事件转换为一个发送矩形事件和三角形事件的新的上游Observable.

仍是不能理解? 别急, 再来看看分解动做:

flatmap1.png

这样就很好理解了吧 !!!

上游每发送一个事件, flatMap都将建立一个新的水管, 而后发送转换以后的新的事件, 下游接收到的就是这些新的水管发送的数据. 这里须要注意的是, flatMap并不保证事件的顺序, 也就是图中所看到的, 并非事件1就在事件2的前面. 若是须要保证顺序则须要使用concatMap.

说了原理, 咱们仍是来看看实际中的代码如何写吧:

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 {
                final List<String> list = new ArrayList<>();
                for (int i = 0; i < 3; i++) {
                    list.add("I am value " + integer);
                }
                return Observable.fromIterable(list).delay(10,TimeUnit.MILLISECONDS);
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.d(TAG, s);
            }
        });复制代码

如代码所示, 咱们在flatMap中将上游发来的每一个事件转换为一个新的发送三个String事件的水管, 为了看到flatMap结果是无序的,因此加了10毫秒的延时, 来看看运行结果吧:

D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 3
D/TAG: I am value 3
D/TAG: I am value 3
D/TAG: I am value 2
D/TAG: I am value 2
D/TAG: I am value 2复制代码

结果也确实验证了咱们以前所说.

这里也简单说一下concatMap吧, 它和flatMap的做用几乎如出一辙, 只是它的结果是严格按照上游发送的顺序来发送的, 来看个代码吧:

Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
            }
        }).concatMap(new Function<Integer, ObservableSource<String>>() {
            @Override
            public ObservableSource<String> apply(Integer integer) throws Exception {
                final List<String> list = new ArrayList<>();
                for (int i = 0; i < 3; i++) {
                    list.add("I am value " + integer);
                }
                return Observable.fromIterable(list).delay(10,TimeUnit.MILLISECONDS);
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.d(TAG, s);
            }
        });复制代码

只是将以前的flatMap改成了concatMap, 其他原封不动, 运行结果以下:

D/TAG: I am value 1   
D/TAG: I am value 1   
D/TAG: I am value 1   
D/TAG: I am value 2   
D/TAG: I am value 2   
D/TAG: I am value 2   
D/TAG: I am value 3   
D/TAG: I am value 3   
D/TAG: I am value 3复制代码

能够看到, 结果仍然是有序的.

好了关于RxJava的操做符最基本的使用就讲解到这里了, RxJava中内置了许许多多的操做符, 这里经过讲解mapflatMap只是起到一个抛砖引玉的做用, 关于其余的操做符只要你们按照本文的思路去理解, 再仔细阅读文档, 应该是没有问题的了, 若是你们有须要也能够将须要讲解的操做符列举出来, 我能够根据你们的需求讲解一下.

实践

学习了FlatMap操做符, 咱们就能够回答文章开头提出的那个问题了.

如何优雅的解决嵌套请求, 只须要用flatMap转换一下就好了.

先回顾一下上一节的请求接口:

public interface Api {
    @GET
    Observable<LoginResponse> login(@Body LoginRequest request);

    @GET
    Observable<RegisterResponse> register(@Body RegisterRequest request);
}复制代码

能够看到登陆和注册返回的都是一个上游Observable, 而咱们的flatMap操做符的做用就是把一个Observable转换为另外一个Observable, 所以结果就很显而易见了:

api.register(new RegisterRequest())            //发起注册请求
                .subscribeOn(Schedulers.io())               //在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求注册结果
                .doOnNext(new Consumer<RegisterResponse>() {
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        //先根据注册的响应结果去作一些操做
                    }
                })
                .observeOn(Schedulers.io())                 //回到IO线程去发起登陆请求
                .flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() {
                    @Override
                    public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
                        return api.login(new LoginRequest());
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求登陆的结果
                .subscribe(new Consumer<LoginResponse>() {
                    @Override
                    public void accept(LoginResponse loginResponse) throws Exception {
                        Toast.makeText(MainActivity.this, "登陆成功", Toast.LENGTH_SHORT).show();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(MainActivity.this, "登陆失败", Toast.LENGTH_SHORT).show();
                    }
                });复制代码

从这个例子也能够看到咱们切换线程是多么简单.

好了本次的教程就到这里了. 下一节咱们将会学到Flowable 以及理解Backpressure背压的概念. 敬请期待.

相关文章
相关标签/搜索