RxJava之旅(2):RxJava入门基础(1)

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的理解不够充分因此人为造成了较为陡峭的学习曲线。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容