【译文】RxAndroid and Kotlin (Part1)

原文作者:Ahmed Rizwan

原文链接:RxAndroid and Kotlin (Part1)

译文作者:Dimon

译者语:由于本人水平有限,翻译可能会存在偏差,望大大们谅解。如发现问题,可评论告知,好及时改正~翻译文章只为交流学习,谢谢观看。

文中的Subscriber我直接译为观察者了,只是为了方便自己理解,如有不悦,见谅。


我开始关注RxAndroid已经差不多一周时间了。一开始,我并没有真正意义上地理解它...我的意思是:我抓住了它的概念,但是我却不清楚该何时使用它。不过后来看了一些例子以及一些干货文章(链接在文章末)后,我才算了解RxAndroid!(到了个良好的程度)当时的内心如下:

Such Rx. Much Reactive. Wow!
Such Rx. Much Reactive. Wow!

简而言之,你几乎可以在任何地方使用Rx,但你不能这样做。你应该明智地揣摩哪儿更适合使用它,因为在某种情况下,Rx可以提升100倍以上的生产性或者比正常命令性编程更好,而在其他情况下,则是没必要使用它的。

那么以下,我将展示KotlinJava这两种语言关于Rx的一些例子。

如果你现在对Kotlin不那么熟悉,那么我建议您可以访问以下链接:

Official Kotlin Website

Getting Started on Android

Jake Wharton’s Paper on Kotlin

摘要: Kotlin is an awesometacular alternative to Java, that works amazingly well on Android. And oh, it's developed by JetBrains!

P.S. Kotlin 是没有分号的。:-)


Rx:概念

如果你已经对于Rx有了很好的概念,那么你可以跳过这一part。否则...请你继续吞了下面的这颗安利!!!

那么什么是Rx呢?(⊙v⊙)蒽...其实就是响应式编程reactive programming...用简单的话说,响应式编程reactive programming其实就是一种与观察者模式Observer Pattern密切相关的编程模式。
其中,由被观察者Observable调用了观察者Subscriber的回调方法,就实现了由被观察者Observable向观察者Subscriber的事件传递,即观察者模式Observer Pattern

Observer Pattern
Observer Pattern

Rx也是函数式编程的一个子集。所以经常被称为Functional Reactive Programming。因为...作为用户接收的数据,它们可以申请它们的转换序列。(类似于我们在Java8中的对流所做的)

转换为用户观察到的接收数据
转换为用户观察到的接收数据

我们甚至可以结合或者说合并不同的流streams。它就是这么的灵活!所以,现在我们只记得有一堆不同的、我们(观察者Subscriber)可以从被观察者Observable中接收的数据。

现在,概念开始有点清晰,让我们回过头来看看RxJava。

在Rx中,观察者subscriber的三个事件回调方法:

1.onNext(Data) :从被观察者Observale中接收数据(相当于 onClick() / onEvent())

2.onError(Exception) :如果抛出异常,这个方法将被调用

3.onCompleted() :将在事件队列完结时调用。

RxJava不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。

其实这就像IterablesJava里那样。但有所不同的是,Iterablespull型,而Rx中的被观察者们Observalespush型,这是被观察者Observable将数据推到了其观察者subscribers中。以下是他们之间的比较表格:

comparison table
comparison table

另外需要注意的是,Rx本质上是异步的,也就是说观察者subscribers不会等待其他观察者subscribers。他们其实“异步”的过程流。

所以被观察者Observables推出的数据流给他们的观察者subscribers,然后观察者subscribers可以处置这些流(在上面的方法的帮助下)。我们可以通过下面的 Marble Diagrams来更好地了解流:

两个不同的数据流
两个不同的数据流

这些圆圈代表着的是流上的数据对象。箭头代表该数据流动的一个方向,以有序的方式!再看看以下图:


A mapping of a stream.
A mapping of a stream.

就像我之前提到的,我们可以将类似于map、filter、zip等等使用operators转变成数据(以及流)。上图表示的是一种简单的映射。所以这一转变后,观察者subscriber这个流将得到转变后的流,怎么样?酷吧!

我想你现在应该对Rx是如何工作已经有了一个很好的概念,所以现在让我们开始实际操作吧。

  • 好,我们要做的第一件事就是打坐。(莫打趣,其实只是想思考代码)
meditate
meditate
  • 然后,创建一个被观察者Observable亦不是难事。

这有一些可以创建被观察者Observables的方式,我只是将其中三个罗列出来:

1.Observable.from() : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。

//Kotlin
Observable.from(listOf(1, 2, 3, 4, 5))
//Java
Observable.from(Arrays.asList(1, 2, 3, 4, 5));
//它将会发出以下这些数字 : 1 - 2 - 3 - 4 - 5
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

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

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

3.Observable.create() :创建一个 Observable,实现OnSubscribe接口,并告诉被观察者observable当它被订阅时,OnSubscribecall() 方法会自动被调用,将数据发送到观察者subscriber(s)

//Kotlin
Observable.create(object : Observable.OnSubscribe<Int> {
    override fun call(subscriber: Subscriber<in Int>) {
        for(i in 1 .. 5)
            subscriber.onNext(i)

        subscriber.onCompleted()
    }
})

相同代码的Java版本:

//Java
Observable.create(new Observable.OnSubscribe<Integer>() {
    @Override
    public void call(final Subscriber<? super Integer>
subscriber) {
        for (int i = 1; i <= 5; i++)
            subscriber.onNext(i);

        subscriber.onCompleted();
    }
});

上面写的代码就相当于我写了Observable.from()的例子,但你可以看到,我们可以充分控制发出什么东西、什么时候流可以结束。还可以使用subscribe.onError(e)发送捕获到的异常。

好了,到了现在,我们需要实现观察者subscriber


实现观察者subscriber

在我们已经实现被Observables之后,我们需要的一个subscriber!对于安卓而言,订阅一个被Observable我们首先要告诉observable有关我们打算订阅和观察的线程。RxAndroid给我们的Schedulers,通过它我们可以指定相应的线程。所以,让我们举一个简单的“Hello World”observable的例子,实现在worker thread完成订阅,在main thread上观察。

//Kotlin
Observable.just("Hello World")
          .subscribeOn(Schedulers.newThread())
          //each subscription is going to be on a new thread.
          .observeOn(AndroidSchedulers.mainThread()))
          //observation on the main thread
          //Now our subscriber!
          .subscribe(object:Subscriber<String>(){
            override fun onCompleted() {
             //Completed
            }

            override fun onError(e: Throwable?) {
             //TODO : Handle error here
            }

            override fun onNext(t: String?) {
             Log.e("Output",t);
            }
           })
//Java
Observable.just("Hello World")
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                //Completion
            }

            @Override
            public void onError(final Throwable e) {
                //TODO : Handle error here
            }

            @Override
            public void onNext(final String s) {
                Log.e("Output",s);
            }
        });

你可以在这里得到有关调度与线程的更多信息。

那,它是怎么工作的呢?

当你运行上面那段代码时,它会显示一个日志消息

Output: Hello World!

你看,“订阅”就是如此的简单。你可以点击这里获取更多关于订阅的相关详情。


一个简单的实例:Debounce!!!

好了,你现在知道了如何创建简单的observables了对不对?~

那么让我们来做我们自己喜欢的RxExamples~~

我呢~想实现这个:

RxExamples
RxExamples

在这个例子里,我将文本输入到一个EditText中,并且针对该文本:当我输入时,一个响应将被自动触发,从而打印出我输入的文本。现在这样的反应可能被称为一个API的调用。所以,如果我触发此要求输入了的每一个字符——这将是一种浪费,因为我只需要知道最后一个输入,这意味着在我停止打字的时候它应该只触发一个call——也就是在我打字后,静止一秒的说~!

那么该如何才能在non-reactive programming做到这一点呢?

Non-Reactive Solution

我用一个定时器,并且安排它在afterTextChanged()方法延时1000毫秒后调用run()方法。呃,不要忘记了也要在那添加runOnUiThread()。-_-

其实在概念上,实现起来并不难,但是却会让代码变得非常混乱,在Java中更是如此。

  • Java version
//Java
Timer timer = new Timer();

final TextView textView = (TextView) findViewById(R.id.textView);
final EditText editText = (EditText) findViewById(R.id.editText);

editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
                                  int after) {
    }

    @Override
    public void onTextChanged(final CharSequence s, int start, int before,
                              int count) {
        if (timer != null)
            timer.cancel();
    }

    @Override
    public void afterTextChanged(final Editable s) {
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("Output : " + editText.getText());
                    }
                });
            }

        }, 1000);
    }
});
  • Kotlin
//Kotlin
var timer: Timer? = Timer()

val editTextStop = findViewById(R.id.editText) as EditText
editTextStop.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit

    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
            timer?.cancel()
    }

    override fun afterTextChanged(s: Editable) {
        timer = Timer()
        timer!!.schedule(object : TimerTask() {
            override fun run() {
                runOnUiThread { textView.setText("Output : " + editTextStop.getText()) }
            }
        }, 1000)
    }
})

Reactive Solution

反应式的解决方案是一个非常自由的样板。而且只有3个步骤。

1.创建一个observable
2.添加Debounce operator,1000毫秒(1秒)的延迟
3.订阅它

  • 首先是Java代码
  Observable.create(new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(final Subscriber<? super String> subscriber) {
                        editText.addTextChangedListener(new TextWatcher() {
                            @Override
                            public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
                            }

                            @Override
                            public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
                                subscriber.onNext(s.toString());
                            }

                            @Override
                            public void afterTextChanged(final Editable s) {
                            }
                        });
                    }
                })
                .debounce(1000, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<String>() {
                    @Override
                    public void call(final String s) {
                        textView.setText("Output : " + s);
                    }
                });
  • Kotlin
 Observable.create(Observable.OnSubscribe<String> { subscriber ->
            editText.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(s: Editable?) = Unit

                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit

                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int)
                        = subscriber.onNext(s.toString())
            })
        }).debounce(1000, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    text ->
                    textView.text = "Output : " + text
                })

更少的样板——RxBindings!

我们可以使用RxBindings——这是RxJava结合了Android的UI组件的API。它适用于JavaKotlin!快去使用它吧!!

    //Java with Retrolambda and RxBinding
    RxTextView.afterTextChangeEvents(editText)
              .debounce(1000,TimeUnit.MILLISECONDS)
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(tvChangeEvent -> {
                 textView.setText("Output : " + tvChangeEvent.view()
                            .getText());
              });

    //Kotlin with RxBinding
    RxTextView.afterTextChangeEvents(editText)
              .debounce(1000, TimeUnit.MILLISECONDS)
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe { tvChangeEvent ->
                        textView.text = "Output : " + tvChangeEvent.view().text
              }

正如你所看到的,更少的代码。如果在几个月前看着段代码,我很难在一分钟你弄清楚这是怎么回事。这可真是无价之宝!


下面是一些我推荐的关于Rx的一些资源!我将会继续研究Rx+(kotlin&Java)和继续完成part2的,敬请关注!

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

推荐阅读更多精彩内容