Reactive programming - 响应式编程

Rx 整理

概要

Reactive programming,响应式编程,是一种关注异步以及事件流的编程范式。

异步

Rx 通过观察者模式来 push 事件,避免线程阻塞的问题。所以能够很好地支持异步编程。

迭代器模式的同步 pull 与观察者模式异步 push 的比较

event Iterable (pull) Observable (push)
retrieve data T next() onNext(T)
discover error throws Exception onError(Exception)
complete !hasNext() onCompleted()

Iterable:

getDataFromLocalMemory()
  .skip(10)
  .take(5)
  .map({ s -> return s + " transformed" })
  .forEach({ println "next => " + it })

Observable:

getDataFromNetwork()
  .skip(10)
  .take(5)
  .map({ s -> return s + " transformed" })
  .subscribe({ println "onNext => " + it })

数据流处理

通过引入函数式的操作符,可以对事件进行逐步、组合处理。函数式操作大大提高了抽象程度,避免不必要的实现细节,更容易理解。

ReactiveX diagram

Reactive framework

使用 Rx 框架最简步骤可分为:

  1. Observable:事件源
  2. Observer:处理事件
  3. Schedulers:指定线程
Observable.just(items)                                  // 1
    .subscribeOn(Schedulers.newThread())                // 3
    .observeOn(AndroidSchedulers.mainThread())          // 3
    .subscribe(observer);                               // 2

术语与约定

  • item:Observable 发出的事件
  • emit:Observable 分发出 item 的动作
  • transformation:item 的变换
  • emission:对 Observer onNext 的调用
  • notification:对 Observer onError 或 onComplete 的调用

每个 Operator 都配了 marble diagram 进行说明,下图是对这种图的解释:

marble diagrams 说明

Observer

Observer 对 item 有三种响应动作:

  • onNext:当 Observable 每次发出 item 时,都会得到调用。
  • onError:当过程中发生错误时被调用。
  • onCompleted:当 Observable 的所有 item 都得到处理后,并且没有发生错误时,会被调用。onCompleted 和 onError 只有一个被调用。

Subscriber

Subscriber 是对 Observer 的特殊实现,实现了 unsubscribe 方法。此方法可以解除对 Observable 的订阅。如果 Observable 没有任何 Observer 订阅,则会停止 item 的分发。但这个停止的过程并不是实时的。

Observable

Observable 分为 hot 和 cold 两种类型。Hot 类型会在创建后立刻开始分发 item;Cold 类型会等待一个 Observer 订阅后才开始分发 item。

一般来说 cold 类型的 Observable 会是数据库查询、网络请求等。Hot 类型一般是键鼠事件、系统事件等。

在一些 Rx 实现中,还有一种 Connectable 类型,无论当前有没有 Observer订阅,在调用 connect 方法后才开始分发 item。

Operator

Rx 提供了大量的操作符,大部分函数式方法都有对应的操作符。

应该浏览一下官网对于操作符分类的简短说明。同页面中对于如何选择操作符的决策树也非常有帮助。

大多数操作符的实现都配图进行了解释,对理解操作符的工作方式非常有帮助。而且 javadoc 中也配了图, IDEA 的 Quick Document 功能可以显示出 marble diagram,是非常方便的功能。(但可能由于网络缘故加载图片很慢。)

Scheduler

Scheduler 在多线程编程中用于指定 Observable 和 Observer 运行的线程。ObserveOn 指定 Observer 的运行线程;SubcribeOn 指定 Observable 的运行线程。

  • Schedulers.computation(): 用于计算任务,如事件循环或回调处理。不要在这个 Scheduler 中进行 IO 操作。
  • Schedulers.from(executor): 从指定的 Executor 创建。
  • Schedulers.immediate(): 在当前线程立即执行。
  • Schedulers.io(): 用于 IO 操作。这个 Scheduler 维护了一个线程池,会根据需求适应。
  • Schedulers.newThread(): 创建新线程。
  • Schedulers.test(): 用于测试。
  • Schedulers.trampoline(): 将任务在当前线程队列。

Single

Single 是 Observable 的变种。区别在于,Single 在它的 timeline 上只会分发一个 item。订阅了 Single 的 Observer 只有两种响应动作:onError 和 onSuccess。

Subject

Subject 既是 Observer 又是 Observable。如果在某些情况下不知道如何应用 Rx,那就考虑下使用 Subject。

AsyncSubject

AsyncSubject 会把从原 Observable 收到的最后一个 item 继续分发下去。如果原 Observable 发出的是错误 notification,那么 AsyncSubject 发出的也是 error。

BehaviorSubject

当 Observer 订阅了 BehaviorSubject 后,先会收到订阅前发出的最新的一个 item(如果没有,则发出默认设置的 item),然后正常收到接下来的 item。

PublishSubject

在创建后立刻开始发出 items,订阅的 Observer 只会收到订阅后发出的 items(,订阅前的 item 无法接收到)。

ReplaySubject

ReplaySubject 会将创建以来的所有 items 向订阅的 Observer 发送一遍,不管 Observer 是在什么时候订阅的。

应用

基本

Rx 适合使用的场景:

  1. 需要对数据(事件)进行流式的处理
  2. 需要异步,非线性操作
  3. 回调过多,产生 callback hell
  4. 将异常统一处理,避免过多 try catch

subscribeOn 操作符指定 Observable 运行的线程,observeOn 指定 Observer 的运行线程。

subscribe() 操作符的参数可以是 1~3 个 Action1,使用 lambda 来简化代码的情况下,可以取代 subscribe(observer) 方法。当 subscribe() 的参数为 Subscriber 时,可以多 Override 一个 onStart 的方法,执行顺序和线程为:onStart (observeOn) -> Observable (subscribeOn) -> onNext (observeOn) -> onComplete or onError (observeOn)

在 subscribe 调用后,会返回一个 Subscription 对象,需要在生命周期结束后调用其 unsubscribe 方法。如果有多个 Subscription,就使用 CompositeSubscription。

如果在某些情况下不知道如何应用 Rx,那就考虑下使用 Subject。

风格建议

Rx 的流式处理便于理解代码,所以不推荐在操作符中写太复杂的代码。代码块都尽可能短,隐藏具体的细节,只要表明进行了什么样的操作就可以。

理想情况是每个操作符的参数都限制在一行,超出的部分抽成方法。这个建议是建立在方法名能够明确表明意图的前提下的。读代码的时候,最好不需要四处跳转来查看具体的实现。

链式调用的最后一个操作符内可以适当放宽这个要求。

常用操作符

Ractive programming 是来自于函数式编程的,所以基本都包含了函数式的操作,比如:

  • range:创建一系列在这个区间内的 Observable。
  • map:对 item 进行变换。
  • flatMap:从 item 创建 Observable。
  • filter:按照指定规则过滤 item。
  • 其他:reduce、repeat、take、skip、takeWhile 等等

妥善使用自动补全以及官方文档。

just 和 from

  • just:从现有的 item 创建 Observable。
  • from:从现有的 item 列表创建 Observable。

create

从 Observer 创建 Observable。

doOnXXX

  • doOnNext:在 onNext 调用时会调用传入的方法
  • doOnError:在 doOnError 调用时会调用传入的方法
  • doOnCompleted:在 doOnCompleted 调用时会调用传入的方法

sample(throttleLast)

这个操作符会将指定的时间间隔内的 items 的最后一个分发出来,并且丢弃掉前面的 items。相似的还有 throttleFirst。

debounce(throttleWithTimeout)

指定一个时间段,如果在这个时间段内没有接收到下一个 item,就将前面接收到的 items 分为一组,然后将这个组的最后一个 item 分发出去,其余的丢弃。

buffer

指定时间段或者数量,将这些 items 收集成 List,然后将这个 List item 分发出去。

combineLatest

接收多个 Observable,每当从其中任意一个 Observable 接收到 item 时,会将每个 Observable 的最后一个 item 传递给指定的聚合方法,聚合方法负责处理这些 item。

RxAndroid

AppObservable 提供了对 activity 和 fragment 的 bind 支持。但需要注意的是,这样的方法只是确保了在生命周期结束后,不会有 notification 被传递给 activity 或 fragment(不会再调用 onNext、onComplete 或 onError)。还是需要在 onDestroy 中进行 unsubscribe。

调用 bindXXX 后,Observer 会默认在主线程进行。

简单示例

代替 AsyncTask

Observable.just(somethingBlockMainThread())
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(/* an Observer */);
AppObservable.bindActivity(activity, somethingBlockMainThread())
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(observer);

unsubcribe

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSubscription = AppObservable.bindActivity(activity, somethingBlockMainThread())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(observer);
}

@Override
public void onPause() {
    super.onPause();
    mSubscription.unsubcribe();
}

public static void unsubscribeIfNotNull(Subscription subscription) {
    if (subscription != null) {
        subscription.unsubscribe();
    }
}

public static CompositeSubscription getNewCompositeSubIfUnsubscribed(CompositeSubscription subscription) {
    if (subscription == null || subscription.isUnsubscribed()) {
        return new CompositeSubscription();
    }

    return subscription;
}

代替计时相关

使用 timer 操作符可以设置延迟进行或者间隔重复执行。

interval 操作符可设置间隔重复执行。

配合 take 等操作符,可以组合成重复执行固定次数的任务。

监听数据变化

WidgetObservable 提供了对 TextView、CompoundButton、AdapterView、AbsListView 的事件支持。

自定义监听的例子:

public static <T extends View> Observable<T> clicksFrom(T view) {
    PublishSubject publishSubject = PublishSubject.create();
    view.setOnClickListener((v) -> publishSubject.onNext(view));
    return publishSubject.asObservable();
}

使用 Rx 来监听事件的好处主要体现在其他操作符的支持。

比如当文字变化时,通常的做法是添加 TextWatcher,会在每一次变化时进行一些操作(校检数据合法性等)。但在用户快速输入文字时,会引起不必要的校验操作。

这种情况下,可以使用 Debounce 操作符,在一定的时间没有输入后,才会真正触发校验操作。

适合情景:

  1. 校验数据合法性
  2. Auto complete 相关的控件
  3. 搜索建议等
// 无输入 400 millis 之后才显示搜索建议
bindSupportFragment(this, WidgetObservable.text(searchText))
    .debounce(400, TimeUnit.MILLISECONDS)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(searchObserver());

表单多个输入项联合校验

使用 combineLatest 操作符,每次触发 item 分发时,会将每个控件的最后一个事件传递给指定的方法。

Observable.combineLatest(
                    WidgetObservable.text(email),
                    WidgetObservable.text(password),
                    WidgetObservable.text(number),
                    (onEmailChangeEvent, onPasswordChangeEvent, onNumberChangeEvent) -> {
                        return emailValid(onEmailChangeEvent) &&
                                passValid(onPasswordChangeEvent) &&
                                numValid(onNumberChangeEvent);
                    })
          .subscribe(aBoolean -> setValid(aBoolean));

错误处理

Observable
    .error(new RuntimeException("testing"))
    .retryWhen(new RetryWithDelay(5, 1000))
    .subscribe(observer);

retry 操作符会重新执行一次出错的 Observable。

retryWhen 操作符将延迟重新执行。

从 configuration change 中恢复

RxJava-Android-Samples 中提供了一种方法

深入浅出 RxJava 四-在 Android 中使用响应式编程 最后也提到了这个问题。

Retrofit

部分第三方库比如 Retrofit 对 Rx 进行了支持。

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

自己实现 Operator

如果需要自己实现 Operator,则实现 Observable.Operator<R, T> 接口即可。

在实现后,将自己实现的操作符传入 Observable 的 lift() 方法。

在实现时,需要注意的点:

  • 在发出任何的 item 或 notification 前,都需要检查 Subscriber 是否已经 unsubscribe 了。
  • 应当遵循的核心原则:
    • 可以调用多次 Subscriber 的 onNext 方法,但这些调用必须是非重叠性的(non-overlapping)。
    • 不能既调用 onComplete 又调用 onError,并且只调用一次。
    • 如果无法保障上面两条原则的实现,就给你的 Operator 加上 serialize() 操作符来强制实现正确的效果。
  • 在操作符中不要进行阻塞操作。
  • 推荐使用现有的操作符来组合出新的操作符。
  • 如果操作符接受 function 或 lambda 作为参数,注意这可能是错误的发生处,需要妥善处理。比如将它们 try catch 后,调用 onError 方法。
  • 一般而言,错误发生时,立即通知 Subscriber,而不是继续分发更多的 item 出去。
  • 某些情况下,你的操作符需要注意 backpressure 策略。

资料

推荐资料

异步编程与响应式框架

FRP on Android:全面简洁地演示了 RxAndroid 相关的内容。

RxJava Koans:几个简单的编程问题,演示了 RxJava 的基本,并且展示了如何利用 TestSubscriber 来进行单元测试。

Operators:ReactiveX 官方关于 operators 的文档。

其他

RxJava-Android-Samples:一些简单的示例。

The RxJava Android Module

草稿 - Reative Programming 基础

草稿 - Rx Framework

草稿 - Rx 应用

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/smzhangyang/article/details/47006663
个人分类: 安卓相关
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Reactive programming - 响应式编程

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭