0.前言

这可能是我拖延最严重的系列文章了,从第一篇《 RxJava之旅(1):RxJava思想基础》到这一篇的间隔差不多有一年,导致每想到这个系列都惭愧不已,甚至好几次冲动差点删了。事情是这样的,去年写完第一篇以后立刻投入到了公司的新项目中,等项目最繁忙的阶段过去已经是2017年初,彼时RxJava2已经发布,导致我觉得这个系列文章失去了继续下去的价值,另一方面由于太懒一些原因,始终没有去了解RxJava2。最近抽出空来看了一下第一篇的内容,突发想起来事实上RxJava1和RxJava2本质上都是响应式编程,所以继续这个系列是有意义的。接下来我会从RxJava1入手,然后通过RxJava1和RxJava2的不同对RxJava2进行介绍。因此,在RxJava2已经出现的时候,介绍RxJava1是有意义的。

这一篇文章我们会从响应式编程入手,从响应式编程和面向对象编程进行对比,加深响应式编程的理解,从而降低RxJava的学习曲线。

1.开始

作为入门,首先让我们来快速地浏览一下RxJava的使用,在你的模块(module)的build.gradle中添加依赖:

compile 'io.reactivex:rxjava:x.y.z'

其中x.y.z替换为最新的版本,关于版本号的查询,请点击这里。截止本文时,最新版本为1.3.0,因此配置如下:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])        
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'

    testCompile 'junit:junit:4.12'

    //RxJava2
    compile "io.reactivex.rxjava2:rxjava:2.1.2"
    //RxJava1
    compile 'io.reactivex:rxjava:1.3.0'
}

我们可以看到,这里同时配置了RxJava1和RxJava2。为了RxJava1到RxJava2的平稳过渡,在编写RxJava2时使用了不同的包名,这样即使将二者同时放入依赖中也不会造成依赖冲突。因此RxJava1和RxJava2是可以同时使用的,只是要小心查看一下所引入的包名是否正确,以免在应该使用RxJava1的地方引入了RxJava2的类。

2.RP是一种怎样的体验

在正式开始之前让我们先耐心地讨论一个问题。上一篇文章我们认真地讨论了什么是RP(Reactive Programming, 响应式编程,下同),以及响应式编程和面向过程与面向对象编程的特点。

我们这里用一小节的内容来看看同样一个需求用RP和OOP(Object Oriented Programing,面向对象编程,下同)是如何实现的,这样的对比可以帮助更好地理解响应式编程以及RxJava。

2.1一个简单的问题

我们的问题非常简单:在Android中有两个输入框用于数字的输入,分别输入数字a和数字b,并计算得到结果c,即a+b=c。

2.2界面与初始化

界面

这个界面非常简单,所以布局文件的代码就不贴出来了。

关于Activity的部分也没有难度,但是有必要贴出来说明每个控件的对应关系:

//输入框a
editTextA = (EditText) findViewById(R.id.et_a);
//输入框b
editTextB = (EditText) findViewById(R.id.et_b);
//结果c
textViewC = (TextView) findViewById(R.id.tv_c);
//计算按钮c
button = (Button) findViewById(R.id.btn_cal);

接下来我们分别用OOP和RP的方式来解决这个简单的问题。

2.3OOP的解决方案

首先让我们使用面向对象的方式来解决这个问题,很简单这里涉及到三个成员变量,分别是a,b和c,以及用于计算和返回结果的两个方法。我们定义了一个叫做Plus的类,代码如下:

    public class Plus {
        private int a;
        private int b;
        private int c;//c没有get或set方法

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public int getB() {
            return b;
        }

        public void setB(int b) {
            this.b = b;
        }
        //相加
        public void sum(){
            c = a + b;
        }
        //获取结果
        public int getResult(){
            return c;
        } 
    
    }

我们用OOP的方式将这个问题抽象成一个类,通过set方法为变量赋值,通过sum方法进行计算,最后通过getResult方法获取计算结果。

接下来我们考虑RP的方式是如何解决的。

2.4RP的解决方案

在第一篇中我们介绍了响应式编程的核心概念,即一切皆数据流的编程思维。那么使用数据流的方式考虑我们这个简单的问题,在a+b=c的公式中,a和b都是数据流,而c是对数据流a和数据流b的响应。就是说通过监听数据流a和数据流b的变化,从而对c进行变化。

问题已经变得清晰了,我们需要两个数据流来表示a和b,然后将二者最新值相加并显示在c的位置。

以下部分的代码涉及到了很多还未介绍的RxJava内容,在这里我们不会对此进行细致的介绍,在后面将会逐一讲解

2.4.1数据源的创建

首先来看数据流A和数据流B的创建:


    final Observable<TextViewAfterTextChangeEvent> observableA = RxTextView.afterTextChangeEvents(editTextA).filter(new Predicate<TextViewAfterTextChangeEvent>() {
            @Override
            public boolean test(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) throws Exception {
                return null != textViewAfterTextChangeEvent.editable() && !TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString());
            }
        }); 
        
    final Observable<TextViewAfterTextChangeEvent> observableB = RxTextView.afterTextChangeEvents(editTextB).filter(new Predicate<TextViewAfterTextChangeEvent>() {
            @Override
            public boolean test(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) throws Exception {
                return null != textViewAfterTextChangeEvent.editable() && !TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString());
            }
        });

这里用到了RxTextView,这是RxBinding库中的一个类,用于TextView相关事件的监听。而RxBinding是用于Android UI控件的响应式库,具体的内容我们会在后面的文章具体介绍。在这里我们所用到的RxTextView.afterTextChangeEvents()实际上对应着TextWatcher::afterTextChanged()方法,这样相当于使用editTextA.addTextChangedListener()方法添加了一个TextWatcher,然后监听其afterTextChanged()方法,将每次变化后的数据作为新产生的数据项,以供我们后续的处理。

对于RxBinding的引用,在module的build.gradledependencies代码块中配置如下依赖:

compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'

2.4.2数据流的订阅(监听)

我们已经定义了数据流,接下来可以去监听数据源的变化了。但是这里还有一个问题:由谁来监听数据流?对两个数据流分别进行监听并不是一个好的方案,这样会破坏RP的响应式设计,所以我们需要一个新的数据流用来将数据流a和数据流b组合在一起,形成一个新的数据流c,然后对数据流c进行订阅,将每次变化的值展示在界面上。

让我们看一下数据流c是如何创建的:

    Observable.combineLatest(observableA, observableB, new BiFunction<TextViewAfterTextChangeEvent, TextViewAfterTextChangeEvent, String>() {
            @Override
            public String apply(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent, @NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent2) throws Exception {
                int a = TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent.editable().toString());
                int b = TextUtils.isEmpty(textViewAfterTextChangeEvent2.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent2.editable().toString());
                return String.valueOf(a + b);

            }
        });

这里我们第一次用到了RxJava中的操作符(operator),操作符是RP中一个很重要的概念,往往用于数据流的转变。如果我们将数据流比作生产的流水线的话,操作符就是流水线上的某些仪器,可以对流水线上的物品进行转换,组合等操作。

这里我们简单介绍一个所用到的combineLatest,具体的在后面介绍。combineLatest可以将多个数据流(我们这里是两个)的最后一项数据组合在一起,然后生成一个新的数据流继续向下流动。图示如下:

我们这里取到a和b的最后一个int数值,然后将它们加在一起。

2.4.3订阅

在得到新生成的数据流c后,我们对数据流c进行订阅,将c的每次变化都展示在界面,代码如下:

    Observable.combineLatest(observableA, observableB, new BiFunction<TextViewAfterTextChangeEvent, TextViewAfterTextChangeEvent, String>() {
            @Override
            public String apply(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent, @NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent2) throws Exception {
                int a = TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent.editable().toString());
                int b = TextUtils.isEmpty(textViewAfterTextChangeEvent2.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent2.editable().toString());
                return String.valueOf(a + b);

            }
        }).subscribe(new Observer<String>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                Log.d(TAG, "onSubscribe");

            }

            @Override
            public void onNext(@NonNull String s) {
                Log.d(TAG, s);
                textViewC.setText(s);//展示结果c
            }

            @Override
            public void onError(@NonNull Throwable e) {
                Log.e(TAG, e.getMessage());
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "completed");
            }
        });

2.4.4 效果

可以注意到我们并没有使用按钮进行处理,而是根据数据的变化自动进行响应。这符合RP的概念,也是RxJava的优点之一。最后展现的结果如图:

3.Hello RxJava1!

我们已经看到了使用RxJava进行响应式编程是如何便捷,那么这一节我们终于可以开始学习RxJava1了,让我们从一段最简单的代码开始。

##3.1 一段简单的代码

    rx.Observable.just("Hello world")
                .subscribe(new rx.Observer<String>() {
                    @Override
                    public void onCompleted() {
                        Log.d(TAG,"completed.");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG,e.getMessage());
                    }

                    @Override
                    public void onNext(String s) {
                        Log.d(TAG,"next msg: " + s);
                    }
                });

这是一段最简单的RxJava代码,我们的入门就从这里开始。

##3.2 创建

just方法的完整定义如下:

public static <T> Observable<T> just(final T value)

just方法用于创建一个被观察者,该方法可以将任何对象转化为可观察对象,其返回类型为Observable<T>

关于创建的细节我们将在下一篇进行讨论。

##3.3订阅

回顾一下第一篇的内容,我们说过RxJava本质上是观察者模式。在观察者模式中存在观察者和被观察者,观察者订阅被观察者,当观察者发生变化时,被观察者进行响应。

RxJava和我们常见的观察者模式有一点不同:由被观察者去订阅观察者,而不是传统的观察者去订阅被观察者。当Observable调用subscribe方法时,其入参为Observer对象,即观察者。在Observer中有三个方法,分别为onNextonErroronComplete

观察者调用onNext对每一项流过的数据进行处理,如果处理失败则调用onError,当所有数据都处理完成且没有错误时,应当调用onComplete方法。具体过程我们在下一篇进行介绍。

4.总结

事实上这一篇我们依然没有对RxJava有什么实质上的介绍,更像是对第一篇的补充。但是这一切我认为是值得的,因为很多人包括我在学习RxJava的初期都是直接扑向写法和API,由于对RP的理解不够充分因此人为造成了较为陡峭的学习曲线。

在下一篇我们会详细介绍创建和订阅以及其源码细节。