RxJava再回首

废话几句

很早前就看了RxJava,当时就觉得好牛掰,但是公司项目一直没有用起来,知识不用就会忘,前段时间突然要写RxJava,发现已经不会写了。所以今天再回头整理一下RxJava的头绪,一方面给其它想了解RxJava的人提供参考,另一方面也是给自己将来再遗忘时回来翻阅。

1、RxJava是什么?

RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。看不懂是正常的,因为官方的总结往往都是要等到全部学完后再回头看才能恍然大悟。简单的解释,RxJava就是一个基于观察者模式的异步框架。

在Android中实现异步操作并不复杂。我们有Handler、AsyncTask等,都是简单好用的,为什么我们还要学习这么复杂的RxJava呢,原因就是RxJava更强,我总结了一下几点:

  • 流式代码,书写和阅读都很流畅
  • 线程切换方便快捷,支持5种线程选择
  • 变换函数功能十分强大,去除冗长的逻辑嵌套,代码逻辑清晰明了
  • 丰富的操作符可以用最简单的代码实现功能
  • 和Retrofit一起使用更配哦

2、观察者模式

观察者模式我们并不陌生,Android种到处在用的OnClickListener就是观察这模式,View是被观察者,listener是观察者,listener会监听着View的一举一动,当View被点击时,listener会立刻收到回调。RxJava也是基于这样的观察者模式,只不过RxJava自己定义了一些概念。

  • 观察者 Observer
  • 被观察者 Observable 英文翻译叫可观察者,就是被观察者的意思
  • 订阅 subscribe 观察者和被观察者发生关联的动作称为订阅

另外,RxJava的事件比起一般的观察者模式要稍微复杂一点点。

  • onNext() 每次发出事件时回调
  • onError() 事件处理过程中出现异常时回调
  • onComplete() 事件队列完结,不会再触发onNext()时回调

基本概念很简单,下面就是怎么写代码了,和OnClickListener一样,我们要构建观察者和被观察者,然后把两者关联。

观察者 构建观察者我们可以new一个Observer的对象,并实现三个回调方法

Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String item) {
        Log.d(tag, "Item: " + item);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

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

被观察者 RxJava提供了create方法来构建一个Observable对象,传入一个onSubscribe对象

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("111");
        subscriber.onNext("222");
        subscriber.onNext("333");
        subscriber.onCompleted();
    }
});

OnSubscribe是Observable的内部类(其实是一个interface),它有一个回调call,在观察者和被观察者发生订阅时会回调,在这个回调里可以触发一系列事件。

订阅 观察者和被观察者准备好了,下面就把两者关联起来。

observable.subscribe(observer);

到这里代码就已经写完了,observer和observable关联后,OnSubscribe的call方法会回调,call方法中我们连续发了3个onNext()事件和1个onComplete()事件,observer在相应的回调里处理这两个事件。

3、Subscriber

上面的代码涉及到了一个陌生的类Subscriber,call回调返回的是Subscriber对象,这个类是什么呢?

Subscriber是Observer的抽象类,Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的,不仅基本使用方式一样,实质上,在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用。所以如果你只想使用基本功能,选择 Observer 和 Subscriber 是完全一样的。它们的区别对于使用者来说主要有两点:

  • onStart():这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法。
  • unsubscribe():这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。

4、快捷方式

create() 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列

  • just(T...): 将传入的参数依次发送出来
Observable observable = Observable.just("111", "222", "333");
// 相当于依次调用
// onNext("111");
// onNext("222");
// onNext("333");
// onCompleted();
  • from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来
String[] array = {"111", "222", "333"};
Observable observable = Observable.from(array);
// 相当于依次调用
// onNext("111");
// onNext("222");
// onNext("333");
// onCompleted();
  • 完整写法就是这样
String[] array = {"111", "222", "333"};
Observable.from(array)
        .subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String s) {

            }
        });

5、ActionX

除了 subscribe(Observer) 和 subscribe(Subscriber) ,subscribe() 还支持不完整定义的回调,RxJava 会自动根据定义创建出 Subscriber 。形式如下:

Action1<String> onNextAction = new Action1<String>() {
    @Override
    public void call(String s) {
        Log.d(tag, s);
    }
};

Action1<Throwable> onErrorAction = new Action1<Throwable>() {
    @Override
    public void call(Throwable throwable) {
        // Error handling
    }
};

Action0 onCompletedAction = new Action0() {
    @Override
    public void call() {
        Log.d(tag, "completed");
    }
};

// 一个参数,只回调onNext()
observable.subscribe(onNextAction);

// 两个参数,第一个回调onNext(),第二个回调onError()
observable.subscribe(onNextAction, onErrorAction);

// 三个参数,第一个回调onNext(),第二个回调onError(),第三个回调onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);

Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2、Action3、Action4) 的,它们可以被用以包装不同的无返回值的方法。

6、线程调度

这是RxJava的一个强大的地方,在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler线程调度器。

RxJava 通过Scheduler它来指定每一段代码应该运行在什么样的线程。RxJava 已经内置了4个 Scheduler ,RxAndroid也提供了一个主线程Scheduler,它们已经几乎适用所有的场景:

线程

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

控制 有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。 * subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。observeOn(): 指定 Subscriber 所运行在的线程,或者叫做事件消费的线程。

Observable.just("111", "222", "333")
    .subscribeOn(Schedulers.io()) // 指定事件产生在 IO 线程
    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
    .subscribe(new Action1<String>() {
        @Override
        public void call(String str) {
            Log.d(tag, "String:" + str);
        }
    });

案例 我们用一个加载图片的案例来说明下Scheduler具体的使用,假设有一组图片资源id,我们要依次加载出图片显示在ImageView上。

Observable.create(new OnSubscribe<Drawable>() {
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
    @Override
    public void onNext(Drawable drawable) {
        imageView.setImageDrawable(drawable);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。

多次切换线程 observeOn()可以用来指定事件消费的线程,其实它是可以多次切换线程的。因为 observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 并不是subscribe() 参数中的 Subscriber ,而是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。

Observable.just("111", "222", "333") // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

如上,通过 observeOn() 的多次调用,程序实现了线程的多次切换。不过,不同于 observeOn() , subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。

7、变换

RxJava 提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava 真是太好用了』的最大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。

map()

Observable.just("images/logo.png") // 输入类型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 参数类型 String
            return getBitmapFromPath(filePath); // 返回类型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 参数类型 Bitmap
            showBitmap(bitmap);
        }
    });

这里出现了一个叫做 Func1 的类。它和 Action1 非常相似,也是 RxJava 的一个接口,用于包装含有一个参数的方法。 Func1 和 Action 的区别在于, Func1 包装的是有返回值的方法。另外,和 ActionX 一样, FuncX 也有多个,用于不同参数个数的方法。FuncX 和 ActionX 的区别在 FuncX 包装的是有返回值的方法。

可以看到,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由 String 转为了 Bitmap。这种直接变换对象并返回的,是最常见的也最容易理解的变换。不过 RxJava 的变换远不止这样,它不仅可以针对事件对象,还可以针对整个事件队列。

flatMap()

首先假设这么一种需求:有一个学生数组,每个学生有一个课程数组,我们要遍历数组,打印每个学生所修的课程。

Student[] students = ...;
Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>() {
        @Override
        public Observable<Course> call(Student student) {
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(new Subscriber<Course>() {
        @Override
        public void onNext(Course course) {
            Log.d(tag, course.getName());
        }
        ...
    });

从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。 flatMap() 的原理是这样的:1. 使用传入的事件对象创建一个 Observable 对象;2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

8、doOnSubscribe()

前面讲 Subscriber 的时候提到过 Subscriber 的 onStart() 可以用作流程开始前的初始化。然而 onStart() 由于在 subscribe() 发生时就被调用了,因此不能指定线程,而是只能执行在 subscribe() 被调用时的线程。这就导致如果 onStart() 中含有对线程有要求的代码(例如在界面上显示一个 ProgressBar,这必须在主线程执行),将会有线程非法的风险,因为有时你无法预测 subscribe() 将会在什么线程执行。

而与 Subscriber.onStart() 相对应的,有一个方法 Observable.doOnSubscribe() 。它和 Subscriber.onStart() 同样是在 subscribe() 调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下, doOnSubscribe() 执行在 subscribe() 发生的线程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的话,它将执行在离它最近的 subscribeOn() 所指定的线程。

Observable.just("111", "222").subscribeOn(Schedulers.io())
                .subscribeOn(Schedulers.io())
                .doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        Log.e("TAG", "doOnSubscribe:" + Thread.currentThread().getName());
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(String s) {
                        Log.e("TAG", "onNext:" + Thread.currentThread().getName());
                    }
                });

9、操作符

RxJava很强大的地方就是提供了丰富的操作符,可以用最简洁的链式代码实现丰富的功能。

  • map()、flatMap()前面讲过了
  • concatMap 和flatMap()差不多,但是它可以避免flatMap()的交叉问题,下面的示意图看的很明显

flatMap()

concatMap()

  • scan 对一个序列的数据应用一个函数,并将这个函数的结果发射出去作为下个数据应用函数时的第一个参数使用,看一个例子:
Observable.just(1, 2, 3, 4, 5)
        .scan(new Func2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }).subscribe(new Action1<Integer>() {
    @Override
    public void call(Integer integer) {
        System.out.print(integer+“ ”);
    }
});

输出结果为:1 3 6 10 15

  • Filter 用来过滤观测序列中我们不想要的值,只返回满足条件的值

filter

Observable.just("111", "222", "333")
        .filter(new Func1<String, Boolean>() {
            @Override
            public Boolean call(String s) {
                return s.equals("222");
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String str) {
                Log.d("TAG", "str:" + str);
            }
        });

输出结果s:222

  • Take 用一个整数n作为一个参数,从原始的序列中发射前n个元素

Take

Observable.from("111", "222", "333")
        .take(2)
        .subscribe(new Action1<String>() {
            @Override
            public void call(String string) {
                Log.d("TAG", "str:" + str);
            }
        });

输出111,222

  • TakeLast 和Take一样,只是取后几个
  • Skip 忽略Observable发射的前n项数据
Observable.from("111", "222", "333", "444", "555")
        .skip(2)
        .subscribe(new Action1<String>() {
            @Override
            public void call(String string) {
                Log.d("TAG", "str:" + str);
            }
        });

输出333,444,555

  • SkipLast 和Skip一样,只是忽略末尾的几项数据
  • ElementAt 用来获取元素Observable发射的事件序列中的第n项数据,并当做唯一的数据发射出去
Observable.from("111", "222", "333", "444", "555")
        .elementAt(3)
        .subscribe(new Action1<String>() {
            @Override
            public void call(String string) {
                Log.d("TAG", "str:" + str);
            }
        });

输出444

  • Distinct distinct()的过滤规则是只允许还没有发射过的数据通过,所有重复的数据项都只会发射一次

Distinct

Observable.just(2, 1, 2, 2, 3, 4, 3, 4, 5, 5)
        .distinct()
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer i) {
                Log.d("TAG", "i:" + i);
            }
        });

输出结果:2 1 3 4 5

  • First Observable只发送观测序列中的第一个数据项
  • Last Observable只发射观测序列中的最后一个数据项

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏刘君君

很方便的密码加密算法BCrypt

2K50
来自专栏QQ音乐技术团队的专栏

RxJava的一些入门学习分享

最近在Android编程领域,RxJava这个框架的技术热度相当的高。团队对使用RxJava改进目前的项目代码也表示很有兴趣,所以我特地和团队伙伴一起对这套框...

355100
来自专栏武军超python专栏

2018年7月21日python中的加密和解密

·在函数调用执行过程中: 如果出现return,return中的函数执行完则本函数就运行结束,return下面的语句不会再继续执行,所以return使 用时...

29950
来自专栏数据结构与算法

11:潜伏者

个人感觉这道题在字符串里面算是一个比较综合比较难的题目了 对于选手的数据查找能力有很强的要求 我在做这道题时运用了桶排的思想 不多说了,看代码吧,全网最简 11...

30560
来自专栏编程之旅

iOS开发——32位MD5加密

在IOS开发过程中,为了保证数据的安全,我们通常要采取一些加密方法,常见的加密有Base64加密和MD5加密。Base64加密是可逆的,MD5加密目前来说一般是...

20020
来自专栏Hongten

lucene(全文搜索)_建立索引_根据关键字全文搜索_源码下载

=========================================================

19720
来自专栏蘑菇先生的技术笔记

qt5中信号和槽的新语法

31670
来自专栏Android开发与分享

【Android】RxJava的使用(二)Action

27460
来自专栏刘晓杰

observeOn()与subscribeOn()的区别

33080
来自专栏Python小屋

Python暴力破解凯撒加密的文本

凯撒加密是一种经典加密算法,把文本中的每个英文字母替换为字母表中后面第k个字母。该加密算法的抗攻击能力比较弱,因为密钥(也就是k)的取值范围是固定的,很容易暴力...

15320

扫码关注云+社区

领取腾讯云代金券