Android开发之RxJava 2.x

RxJava是什么

RxJava是一个实现异步操作的库,它可以通过链式调用使代码逻辑变得清晰简洁。它的github地址为:

  • https://github.com/ReactiveX/RxJava
  • https://github.com/ReactiveX/RxAndroid

在项目中可以通过以下方式引入依赖:

compile 'io.reactivex.rxjava2:rxjava:2.2.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.2'

API介绍及原理简析

RxJava是采用观察者模式来实现的。RxJava具有四个基本概念:observable(被观察者)、observer(观察者)、subscribe(订阅)和事件。observable与observer通过subscribe()方法实现订阅关系,从而observable可以在需要的时候发出事件来通知observer。

基本实现
  1. 创建Observer对象
    Observer即观察者,它决定事件触发的时候进行的操作。RxJava中Observer接口的实现方式:
        Observer observer = new Observer() {
            Disposable disposable;

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

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

            @Override
            public void onSubscribe(Disposable d) {
                Log.e("tag", "onSubscribe");
                disposable = d;
            }

            @Override
            public void onNext(String s) {
                Log.e("tag", "onNext=" + s);
                if ("!".equals(s)) {
                    disposable.dispose();
                }
            }
        };

上面代码中的Disposable对象可用于中止接收数据,如onNext方法中的判断,当接收到的数据为!时就不再接收数据了。除了Observer接口外,RxJava还有一个Consumer接口,即消费者,它的用法如下:

        Consumer consumer = new Consumer() {

            @Override
            public void accept(String s) throws Exception {

            }
        };

它只有一个accept方法,事件将会在这个方法内进行处理。

  1. 创建Observable
    Observable即被观察者对象,它决定什么时候触发事件以及触发怎么样的事件。RxJava使用create()方法创建Observable对象,并定义事件触发规则:
        Observable observable = Observable.create(new Observable.OnSubscribe() {
            @Override
            public void call(Subscriber subscriber) {
                subscriber.onNext("Hello");
                subscriber.onNext("World");
                subscriber.onCompleted();
            }
        });

此外,还可以通过just(T …)与fromArray(T [ ])方法获取Observable对象,它们的用法如下:

        //将依次调用onNext("Hello");onNext("World");onComplete();
        Observable observable1 = Observable.just("Hello", "World");

        //同just()
        Observable observable2 = Observable.fromArray(new String[]{"Hello", "World"});

just方法与fromArray方法的区别在于just只能发送不超过10个事件,而fromArray没有这个限制。

  1. Subscribe(订阅)
    创建完Observable和Observer后,通过subscribe()方法将它们联合起来,就可以开始工作了:
        observable.subscribe(observer);
        //或
        observable.subscribe(consumer);

线程控制–Scheduler

在不指定线程的情况下,RxJava遵循线程不变原则,即在哪个线程调用subscribe()方法,就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果要切换线程,就需要用到Scheduler(调度器)。在RxJava中,Scheduler相当于线程控制器,RxJava通过它来指定每一段代码应该运行在什么样的线程中。

Scheduler的API
  • Schedulers.immediate():直接在当前线程运行,相当于不指定线程,是默认的Scheduler。
  • Schedulers.newThread():总是启动新线程,并在新线程中执行操作。
  • Schedulers.io():I/O操作(读写文件、数据库、网络信息交互等)所使用的Scheduler。行为和newThread()差不多,区别在于io()内部实现是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下io()比newThread()更有效率。不要把计算工作放在io()中,可以避免创建不必要的线程。
  • Schedulers.computation():计算所使用的Scheduler。这个计算指的是CPU密集型计算,即不会被I/O等操作限制性能的操作,例如图形计算。这个Scheduler使用固定的线程池,大小为CPU核数。不要把IO操作放在computation()中,否则I/O操作的等待时间会浪费CPU。
  • Android有一个专用的AndroidSchedulers.mainThread(),它指定的操作将在Android主线程中运行。

有了这几个Scheduler,就可以使用subscribeOn()和observerOn()两个方法对线程进行控制了。subscribeOn()指定Observable(被观察者)所在的线程,或者叫做事件产生的线程;observerOn()指定Observer(观察者)所在的线程,或者叫做时间消费的线程。线程调度的使用代码示例如下:

 Observable.create(new ObservableOnSubscribe() {
            @Override
            public void subscribe(ObservableEmitter emitter) throws Exception {
                emitter.onNext("hello");
                emitter.onNext("world");
                emitter.onNext("!");
                emitter.onComplete();
                Log.e("tag", "发送线程为:" + Thread.currentThread().getName());
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(String s) {
                        Log.e("tag", s);
                        Log.e("tag", "接收线程为:" + Thread.currentThread().getName());
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });

上述代码打印结果为:

2019-03-20 15:06:08.113 24078-24162/com.example.study E/tag: 发送线程为:RxCachedThreadScheduler-1
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: hello
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: 接收线程为:main
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: world
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: 接收线程为:main
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: !
2019-03-20 15:06:08.144 24078-24078/com.example.study E/tag: 接收线程为:main

除了灵活的变换,RxJava另一个牛逼的地方,就是线程的自由控制。前面提到可以使用subscribeOn()与observerOn()来实现线程控制,那么能不能多切换几次线程呢?答案是:能!因为observerOn指定的是Subscriber的线程,而这个Subscriber并不一定是subscirbe()参数中的Subscriber,而是observerOn()执行时的当前Observable所对应的Subscriber,即它的直接下级Subscriber。换句话说,observerOn()指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次observerOn()即可。不同于observerOn(),subscribeOn()的位置放在哪里都可以,但是它只能调用一次,若有多个subscribeOn()时,只有第一个起作用。

RxJava常用操作符介绍

Filter()

过滤操作符,可以将被观察者发送过来的事件依据一定的条件进行筛选,只有筛选合格的事件才会传递给观察者处理,其用法如下:

        Observable.just("a", "b", "c", "d").filter(new Predicate() {
            @Override
            public boolean test(String s) throws Exception {
                return "d".equals(s);
            }
        }).subscribe(new Consumer() {
            @Override
            public void accept(String s) throws Exception {
                System.out.println("accept:"+s);
            }
        });

打印结果为:
accept:d

map()

可将被观察者发送的数据转换成其他类型的数据。如下代码可将一个图片资源Id转化为Bitmap对象:

        Observable.just(R.mipmap.ic_launcher).map(new Function() {
            @Override
            public Bitmap apply(Integer integer) throws Exception {
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), integer);
                return bitmap;
            }
        }).subscribe(new Consumer() {
            @Override
            public void accept(Bitmap bitmap) throws Exception {
                
            }
        });
flatMap()

flatMap也能实现map操作符的功能,可以完成数据类型的转换,不同之处在于map只能完成一对一的转换,而flatMap可以实现一对多的转换。比如我们有一个小组Group,Group对象内部有一个小组内部有多个Student,我们就可以用flatMap完成Group到Student转换。具体代码实现如下:

public class Group {
    private int id;
    private List students;

    public Group(int id) {
        this.id = id;
        students = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            String name = "Group" + id + "_" + i;
            Student student = new Student(name);
            students.add(student);
        }
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List getStudents() {
        return students;
    }

    public void setStudents(List students) {
        this.students = students;
    }
}

public class Student {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

        Observable.just(new Group(1))
                .flatMap(new Function>() {
                    @Override
                    public ObservableSource apply(Group group) throws Exception {
                        return Observable.fromIterable(group.getStudents());
                    }
                }).subscribe(new Observer() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(Student student) {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
        });

这样,我们就能将1个Group对象转换为多个Student对象发送给观察者了。

RxJava的背压机制

在RxJava中会遇到一种情况就是被观察者发送消息十分迅速以致于观察者不能及时响应这些消息,这可能造成事件的无限堆积,最后造成OOM,这个问题即Backpressure(背压)。所谓的背压就是这生产者的速度大于消费者的速度带来的问题。那么,应该怎么处理这个问题呢?

在原来的RxJava1.x版本中Backpressure问题是由Observable来处理的。在RxJava 2.x中对于Backpressure的处理进行了改动,为此将原来的Observable拆分成了新的Observable和Flowable,原先的Observable已经不具备背压处理的能力了。而Flowable是一个被观察者,与Subscriber配合使用,解决背压问题。

注意:处理背压的策略仅仅是处理Subscribe接收事件的方式,并不影响Flowable发送事件的方法。即使采用了处理Backpressure的策略,Flowable原来以什么速度产生事件,现在还是什么样的速度不变,主要处理的是Subscriber接收事件的方式。

处理背压的策略

什么样的情况下会产生背压问题呢?

  1. 如果生产者和消费者在一个线程的情况下,无论生产者的生成速度有多快,每生产一个事件都会通知消费者,等待消费者消费完毕,再生成下一个事件,在这种情况下,即同步情况下不会产生背压问题。
  2. 如果生产者与消费者不在同一个线程的情况下,如果生产者的速度大于消费者的速度,就会产生背压,即异步的情况下才有可能会产生背压问题。

处理背压一般有以下几种策略:

BackpressStrategy.ERROR

这种方式会在产生背压问题的时候直接抛出一个异常,这个异常是MissingBackpressureException。示例代码如下:

        Flowable.create(new FlowableOnSubscribe() {
            @Override
            public void subscribe(FlowableEmitter emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
                emitter.onNext(4);
                emitter.onComplete();
            }
        }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        Log.e("tag","onSubscribe");
                        s.request(Long.MAX_VALUE);
                    }

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

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

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

上述代码中Subscriber的onSubscribe()方法的参数不再是Disposable而是Subscription,我们可以通过Subscription.cancel()方法切断观察者与被观察者之间的联系。在onSubscribe()方法中,我们调用了s.request(Long.MAX_VALUE)方法,这个方法是用来向生成者申请可以消费的事件数,当调用此方法后,生产者便发送相应数量的事件供消费者消费。若不显式调用request()方法,则默认消费能力为0。

在异步调用时,RxJava中有一个缓存池用于缓存消费者处理不了暂时缓存下来的数据,缓存池的默认大小为128,即只能缓存128个事件。无论request()中传入的数字比128大或小,缓存池在一开始都会存入128个事件。当然如果本身事件不足128个,则不会存128个事件。

在ERROR策略下,如果缓存池溢出,就会立即抛出MissingBackpressureException异常。

BackpressStrategy.MISSING

不使用背压,没有缓存,等同于Flowable与Subscriber同一线程时的效果。

BackpressStrategy.BUFFER

BUFFER策略是缓存所有的数据,直到观察者处理。也就是不设置缓存大小,如果观察者处理不及时,也会出现OOM,等同于使用Observable。

BackpressStrategy.DROP

DROP策略在缓存池缓存不了事件后,就将后续的事件丢弃。消费者通过request()传入需求n,生产者把n个事件传递给消费者消费,其它消费不了的事件就丢掉。

BackpressStrategy.LATEST

LATEST策略超出缓存大小时,会用新的数据覆盖老的数据。

最后,我们如果不使用create()方法创建Flowable对象,而使用range()、interval等操作符创建时就不能配置BackpressStrategy了,这个时候RxJava提供了onBackpressureBuffer()、onBackpressureDrop、onBackpressureLatest()方法配置背压策略。使用方法如下:

        Flowable.range(0,300)
                .onBackpressureDrop()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        
                    }

                    @Override
                    public void onNext(Integer integer) {

                    }

                    @Override
                    public void onError(Throwable t) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });

参考资料:

  • 给 Android 开发者的 RxJava 详解
  • RxJava2 只看这一篇文章就够了
  • 手把手教你使用 RxJava 2.0(三)
  • 《Android应用开发进阶》

你可能感兴趣的:(Android开发之RxJava 2.x)