RxJava-1 指南

本文基于扔物线的 给 Android 开发者的 RxJava 详解 提炼简化,感谢。

RxJava 是什么

一个词:异步。

说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。

RxJava 好在哪里

一个词:简洁。

能把什么复杂逻辑都能穿成一条线的简洁。

API 介绍和原理解析

1.概念:扩展的观察者模式

四个基本概念:Observable、Observer、subscribe、事件。

和传统的观察者模式不同,RxJava 的事件回调除了普通事件 onNext() (相当于 onClick()/onEvent()) 之外,还定义了两个特殊的事件 onCompleted() 和 onError()。

onCompleted():事件队列完结。当不再有 onNext() 发出时,需要触发 onCompleted() 作为结束。

onError():事件队列异常。在事件处理过程中出异常时,onError 会被触发,同时队列自动终止,不再有事件发出。

在一个正确运行的事件序列中,onCompleted 和 onError 有且只有一个,并且是事件序列中的最后一个。它们是互斥的。

2.基本实现

1)创建 Observer

Observer 即观察者,它决定事件触发时将有怎样的行为。

扫描二维码关注公众号,回复: 1684381 查看本文章
Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String s){
    }
    @Override
    public void onCompleted(){
    }
    @Override
    public void onError(Throwable e){
    }
}

除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。Subscriber 对 Observer 接口进行了一些扩展,但它们的基本使用方式完全一样。

Subscriber<String> observer = new Subscriber<String>() {
    @Override
    public void onNext(String s){
    }
    @Override
    public void onCompleted(){
    }
    @Override
    public void onError(Throwable e){
    }
}

在 RxJava 的 subscribe 过程中,observer 会先被转换成一个 Subscriber 再使用。它们的区别主要有两点:

  1. onStart

这是 Subscriber 新增的方法。他会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认实现为空。如果对准备工作的线程有要求(如弹 dialog,必须在主线程),onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubScribe() 方法,后面会提到。

  1. unsubscribe

这是 Subscriber 实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接受事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。unsubscribe() 这个方法很重要,因为在 subscribe() 之后,observable 会持有 Subscriber 的引用,如果不及时释放,可能会内存泄漏。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(onPause、onStop 等)调用 unsubscribe() 来解除引用关系,避免内存泄漏。

2)创建 Observable

Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:

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

Observable.OnSubscribe 即被观察的对象,上面是一个 String 对象。

call 方法里是订阅者订阅后的操作,在 subscribe 后会进行调用。

除了 create,RxJava 还提供了 just、from 等方法来简化操作:

just(T...): 将传入的参数依次发送出来。

Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

from(T[]) / from(Iterable<? extends T>): 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。

String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

3)Subscribe

创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:

observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):

// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
}

可以看到,subscriber() 做了3件事:

  1. 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
  2. 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
  3. 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().

4)场景示例

由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中。

Subscriber<Drawable> subscriber = new Subscriber<Drawable>() {
    @Override
    public void onNext(Drawable drawable){
        imageView.setImageDrawablw(drawable);
    }
    ...
};

Observable observable = Observerable.create(new Observable.OnSubscribe<Drawable>(){
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
});

observable.subscribe(subscriber);

3.线程控制——Scheduler(一)

在不指定线程的情况下,RxJava 线程不变,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在那个生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler。

1)Scheduler 的 API(一)

在 RxJava 中,Scheduler 相当于线程控制器,可以指定哪一段代码运行在哪个线程。RxJava 内置了几个 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。
  • Andoid 中有一个专用的 AndroidSchedulers.mainThread(),它在主线程运行。

Scheduler 的调用:

  • subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
  • observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。

如上面的取 Drawable 的例子:

Subscriber<Drawable> subscriber = new Subscriber<Drawable>() {
    @Override
    public void onNext(Drawable drawable){
        imageView.setImageDrawablw(drawable);
    }
    ...
};

Observable observable = Observerable.create(new Observable.OnSubscribe<Drawable>(){
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
});

observable.subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程
    .observeOn(AndroidSchedulers.mainThread())// // 指定 Subscriber 的回调发生在主线程
    .subscribe(subscriber);

这样,加载图片会发生在 IO 线程,设置图片在主线程。即使加载图片耗费了几十展示几百毫秒的世界,也不会造成界面的卡顿。

4.变换

RxJava 提供了对事件序列进行变换的支持。所谓“变换”,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。

1)API

Obaservable.just("images/logo.png")
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) {
            return getBitmapFromPath(filePath);
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) {
            showBitmap(bitmap);
        }
    });

Func1 类是 RxJava 的一个接口,用于包装含有一个参数的方法。FuncX 和 ActionX 的区别在于,FuncX 包装的是有返回值的方法。

map() 方法将参数中的 String 对象转换从一个 Bitmap 对象后返回,而在经过 map 之后,事件的参数类型也由 String 转为了 Bitmap。这是最常见也最容易理解的变换,但 RxJava 的变换不止如此,它不仅可以针对事件对象,也可以针对整个事件序列。

flatMap

这是一个很有用但很难理解的变换。

如要打印一串 Student 的名字

Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
    @Override
    public void onNext(String name){
        Log.d(tag, name);
    }
    ...
};

Observable.from(students)
    .map(new Func1<Student, String>() {
        @Override
        public String call(Student student){
            return student.getName();
        }
    })
    .subsrcibe(subscriber);

再假设:要打印出每个学生要修的所有课程的名字呢?

Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
    @Override
    public void onNext(Student student){
        List<Course> courses = student.getCourses();
        for (int i = 0; i < courses.size(); i++) {
            Course course = courses.get(i);
            Log.d(tag, course.getName());
        }
    }
    ...
};

Observable.from(students)
    .subsrcibe(subscriber);

如果不想在 onNext 里使用 for 循环,而是直接处理 Course 对象呢?

Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
    @Override
    public void onNext(Course course){
        Log.d(tag, course.getName);
    }
};

Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>(){
        @Override
        public Observable<Course> call(Student student){
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(subscriber);

map 和 flatMap 的区别

Observable.from(students)
    .map(new Func1<Student, Observable<Course>>(){
        @Override
        public Observable<Course> call(Student student){
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(subscriber);


Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>(){
        @Override
        public Observable<Course> call(Student student){
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(subscriber);

map

通过 Student–>Observable\ 的 map,将 Student 序列变成 Observable\ 序列。

flatMap

通过 Student–>Observable\ 的 map,将 Student 序列变成 Observable\ 序列。

再通过 flat(铺平),将 Observable\ 序列变成 course 序列。

所有其实应该叫 mapFlat。

2) 变换的原理 lift()

变换的功能虽然各有不同,但实质上都是针对事件序列的处理和再发送。在 RxJava 内部,他们基于同一个基础的变换方法:lift(Operator)。首先看一下 lift() 的内部实现(仅核心代码):

// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);
        }
    });
}

在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。

变换的具体效果通过 Operator 实现,Operator 的 call 方法接受一个 Subscriber 参数,返回一个新的 Subscriber,这个新的 Subscriber 订阅了原始的 Observable,并在原始的 Observable 变化时调用旧的 Subscriber 的处理方法。

总结:

要让 Subscriber\ 能够订阅 Observable\,就要通过 Observable\ 产生一个 Observable\,同时 Observable\ 能够订阅 Observable\,Observable\ 里就需要一个 Subscriber\

Operator 做的就是将 Subscriber\ 包装为 Subscriber\,通过 Subscriber\ 可以调用 Subscriber\

即使不用变换,Subscriber\ 直接订阅 Observable\,然后再做 A-B 的变换也是可以的,就像上面取课程名的 for 循环,变换的好处就在于可以将一系列操作转为链式,逻辑更加清晰。

sss

3) compose:对 Observable 整体的变换

除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。它和 lift() 的区别在于, lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable 自身进行变换。举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:

public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
    @Override
    public Observable<String> call(Observable<Integer> observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

使用 compose() 方法,Observable 可以利用传入的 Transformer 对象的 call 方法直接对自身进行处理,不必被包在方法的里面了。

5.线程控制——Scheduler(二)

1)Scheduler 的 API(二)

可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。

observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。

Observable.just(1, 2, 3, 4) // 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() 的位置放在哪里都可以,但它是只能调用一次的。

2)Scheduler 的原理(二)

其实, subscribeOn() 和 observeOn() 的内部实现,也是用的 lift()。

subscribeOn() 和 observeOn() 都做了线程切换的工作。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。

3) 延伸:doOnSubscribe()

然而,虽然超过一个的 subscribeOn() 对事件处理的流程没有影响,但在流程之前却是可以利用的。

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

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

示例代码:

Observable.create(onSubscribe)
    .subscribeOn(Schedulers.io())
    .doOnSubscribe(new Action0() {
        @Override
        public void call() {
            progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
        }
    })
    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);

如上,在 doOnSubscribe()的后面跟一个 subscribeOn() ,就能指定准备工作的线程了。

猜你喜欢

转载自blog.csdn.net/gdeer/article/details/80176750