使用 RxJava 的正确姿势

最近在使用 RxJava 时遇到了一些比较诡异的问题,排查的过程中做了点研究,发现平时大家的用法多多少少都有些问题。有些地方存在隐患,有些用法不够简练,我把这些问题简单做一下分类和总结,供大家参考。

数据源类型选择

RxJava2 中的数据源类型有5种,分别是 Observable,Flowable,Single,Maybe 和 Completable,它们的区别如下,看到有些同学只用 Observable,其实这并不是个很好的习惯。

类别 特点
Observable 多个数据,不支持背压
Flowable 多个数据,支持背压
Single 一个数据
Maybe 一个或没有数据
Completable 没有数据,只有结束信号

我们举数据库操作的例子,例如在 Repository 中有一个方法,根据一本书的序列号去查询这本书的名称:

    private String getBookName(int serialNumber) throws InterruptedException {
        Thread.sleep(1000); // 模拟耗时操作
        return "Pride And Prejudice";
    }

尝试把它转化成一个 Rx 风格的方法,如果使用 Observable 是这样的:

    public Observable<String> getBookNameObservable(int serialNumber) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                String name = getBookName(serialNumber);
                if (name != null) {
                    emitter.onNext(name);
                    emitter.onComplete();
                } else {
                    emitter.onError(new NullPointerException("Not Found!"));
                }
            }
        });
    }

访问数据库确定有且只有一次返回结果,所以可以改成Single:

    public Single<String> getBookNameSingle(int serialNumber) {
        return Single.create(new SingleOnSubscribe<String>() {
            @Override
            public void subscribe(SingleEmitter<String> emitter) throws Exception {
                String name = getBookName(serialNumber);
                if (name != null) {
                    emitter.onSuccess(name);
                } else {
                    emitter.onError(new NullPointerException("Not Found!"));
                }
            }
        });
    }

这样写的好处是:

  • 语义明确,看到是 Single 类型就知道只有一个返回值
  • 防止遗漏 onComplete 调用,保证流正常结束
  • 写起来更简洁一些,对应的订阅者也是
  • Single 和 Completable 是使用最广泛的数据源,它们的语义简单,操作符更丰富

我们项目中会有这样一些常用的场景:

  • 持续监听某个组件状态(PUSH类型),监听航线执行状态,监听用户的点击事件等,这种情况会连续返回数据,一般使用 Observable 就可以了,如果有特殊情况需要支持背压可以考虑 Flowable,不过我目前还没有遇到,如果推送频率过高,使用 throttle 操作符过滤一下即可;
  • 主动获取某个传感器的某个状态值(GET类型),访问数据库查找数据,发起网络请求等,获得一个返回值或者失败信息,应该使用 Single;
  • 设置某个组件的状态、开关(SET类型),控制某个组件执行任务(ACTION类型),数据库增删改等,不需要返回值,只需知道是否执行完成或者失败,应该使用Completable;
  • 工作流比较复杂,需要一步一步完成任务,中间还需要线程转换等,这种情况大多只需要传递上一个操作完成的结果或者信号,视情况使用 Single 或者 Completable 就足够了。

装配过程

Single.just 引发的血案

先列举我遇到过的一个问题,和调用时序相关,类似这样:

public class SingleJustTest {
    private static int index = 0;

    public static void main(String[] args) {
        Single<Integer> single = Single.just(index);
        index++;
        single.subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                System.out.println("index from Single is : " + integer);
                System.out.println("real index is : " + index);
            }
        });
    }
}

原以为 integer 这个值应该和 index 相等,结果却出乎意料:

index from Single is : 0
real index is : 1

这是为什么呢?看一下 Single.just 的实现:

    public static <T> Single<T> just(final T item) {
        ObjectHelper.requireNonNull(item, "value is null");
        return RxJavaPlugins.onAssembly(new SingleJust<T>(item));
    }

SingleJust 这个类也很简单:

扫描二维码关注公众号,回复: 5640531 查看本文章
public final class SingleJust<T> extends Single<T> {

    final T value;

    public SingleJust(T value) {
        this.value = value;
    }

    @Override
    protected void subscribeActual(SingleObserver<? super T> observer) {
        observer.onSubscribe(Disposables.disposed());
        observer.onSuccess(value);
    }

}

这个对象保存了 Single.just 方法传入的参数,并且在订阅时传给下游,所以上面这个 integer 的值并不会随着 index 值变化。
要想达到目的,有两种解决方法,使用 Single.fromCallable 或者用 Single.defer:

public class SingleJustTest {
    private static int index = 0;

    public static void main(String[] args) {
        Single<Integer> single = Single.fromCallable(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return index;
            }
        });
        index++;
        single.subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                System.out.println("index from Single is : " + integer);
                System.out.println("real index is : " + index);
            }
        });
    }
}
public class SingleJustTest {
    private static int index = 0;

    public static void main(String[] args) {
        Single<Integer> single = Single.defer(new Callable<SingleSource<? extends Integer>>() {
            @Override
            public SingleSource<? extends Integer> call() throws Exception {
                return Single.just(index);
            }
        });
        index++;
        single.subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                System.out.println("index from Single is : " + integer);
                System.out.println("real index is : " + index);
            }
        });
    }
}

这两种方式都能得到正确的结果:

index from Single is : 1
real index is : 1

原理也很简单,Single.fromCallable 是通过方法调用的方式,订阅后执行传入的 Callable 取得 index 的值; Single.defer 是在订阅时才开始生成真正的数据源,所以也没有问题。
强调一下,just 操作符(包括 Single.just / Observalbe.just / Flowable.just / Maybe.just)虽然使用方便,但是存在上述问题,除非传入的参数是常量,否则应当避免使用。

正确创建数据源

下面是我们项目中经常看到的用法:

public class RxJavaTest {

    public static void main(String[] args) {
        getBookNameObservable(123)
                .subscribeOn(Schedulers.io())
                // 非 Android 环境演示线程调度需要使用 blockingSubscribe 方式订阅,否则当主线程执行完后就退出了
                .blockingSubscribe(new Observer<String>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        log("onSubscribe");
                    }

                    @Override
                    public void onNext(String s) {
                        log("onNext : s = " + s);
                    }

                    @Override
                    public void onError(Throwable e) {
                        log("onError " + e.getMessage());
                    }

                    @Override
                    public void onComplete() {
                        log("onComplete");
                    }
                });
    }

    private static Observable<String> getBookNameObservable(int serialNumber) {
        //先执行一系列运算,最后返回数据源
        String bookName = getBookNameFromDB(serialNumber);
        return Observable.fromCallable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                log("call");
                return bookName;
            }
        });
    }

    private static String getBookNameFromDB(int serialNumber) {
        try {
            log("getBookNameFromDB");
            Thread.sleep(1000); // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Pride And Prejudice";
    }

    private static void log(String msg) {
        System.out.println(msg + ", in thread: " + Thread.currentThread().getName());
    }
}

注意 getBookNameObservable 这个方法,先去查询数据库获得 bookName,然后返回了一个 Observable,我们执行一下:

getBookNameFromDB, in thread: main
onSubscribe, in thread: main
call, in thread: RxCachedThreadScheduler-1
onNext : s = Pride And Prejudice, in thread: main
onComplete, in thread: main

看起来好像没有什么问题,也得到了正确的结果,但是注意调用的顺序和每个方法运行的线程:getBookNameFromDB 这个方法是耗时操作,我们本意是把它放到 io 线程去执行,但是它却在 main 线程执行了,而且调用时机要早于 onSubscribe 方法。
为什么会这样呢?其实很好理解,需要分清楚上面这段代码中哪些是在装配过程中调用,哪些是在订阅之后调用的。getBookNameObservable 这个方法是在装配过程中被调用,应该返回一个未订阅的数据源,当用户订阅时才开始执行 getBookNameFromDB 获取并发射数据,但现在这种写法,getBookNameObservable 方法中 return 之前的语句都会在装配过程中被调用,这就能解释上面的 log 信息了。
总结一下这种写法会带来的问题:

  • 本该在流中执行的代码会在订阅之前执行,产生时序问题
  • 如果装配完成后没有马上订阅,那么订阅时接收到的数据可能已经与真实数据不同
  • 线程调度不起作用
  • 一旦这部分代码抛出了异常,订阅者无法在 onError 中接收到错误信息

所以虽然很多地方是这么用的,结果也没有影响,但这并不表示这种用法就是正确的,只是没有暴露问题而已。强调一下,实现返回数据源方法时,必须第一行就是return语句,所有的操作应当包含在操作流中,这样做能避免很多问题。

简便操作符

此处是要讲一些常用的操作符,能让我们把非 RxJava 代码快速转换过来。

just

前面已经提过,虽然好用,但是只有参数为常量时才能使用。

fromCallable

前面也有演示,一般 Single 操作符使用这个会很方便,直接在 call 方法中返回 onSuccess 的值即可。Observable 和 Flowable 在只有一次 onNext 的情况下也可以用。

fromAction

Completable 专用,相当于执行完成 Action 中的代码并且调用 onComplete,很方便。

flatMap

这个大家用的比较多了,多用于数据源类型转换。

andThen

Completable 因为没有返回值,所以也就没有 flatMap 操作符,可以用 andThen 来连接下一个数据源。

doOnSuccess / doAfterSuccess / doOnNext / doOnComplete

在特定位置插入操作,有些可以对发射的值做进一步处理。

暂时先讲这几个,RxJava 的操作符很丰富,不要只会用 create 和 just,可以多了解一下,有需要去查 官方 API 文档

猜你喜欢

转载自www.cnblogs.com/fengdianzhang/p/10588310.html