RxAndroid常用实践

RxJava/RxAndroid:是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。大家在项目中或多或少都可能用到这个库,本文我总结一下在我们的项目中常用的API。本文基于RxAndroid version 1.2

Do操作符

RxJava中有许多doXXX操作符。这些操作符一般是用来注册一个Action,这些Action会在XXX事件发生时调用。比如doOnSubscribe会在Observablesubscribe()时调用。下面这张图是官方描述的Do的执行时机:

doXXX.png

下面来介绍一下一些我常用到的Do:

doOnSubscribe和doOnUnsubscribe

这两个操作符我常用来操作loading view的状态,比如一个网络请求:

xxxApi
    .getUser()
    .doOnSubscribe{
        ..展示loading
    }
    .doOnUnsubscribe{
        ..隐藏loading
    }
    .subscribe(...)  

原因是 : 可以很正确的管理loading状态不会出错,不用考虑各种出错条件下loading的管理

doOnSubscribe/doOnUnsubscribe分别会在Observablesubscribe()unsubscribe()时调用。而ObservableonComplete()/onError()都会导致unsubscribe的调用,因此doOnSubscribe/doOnUnsubscribe完全可以作为一个Observable生命周期开始与结束的监听,确保两端的对应事件状态不会出错,比如loading状态。

doOnNext

我们一般会在ObserveronNext()回调中来对一个Observable做数据正常发射的处理。不过我们一般会在业务层写这个Observer。但如果我有一些操作不想写在业务层,而是想做一个统一的处理怎么办呢?更直白的说法是 : 我的统一操作不依赖于特定的数据类型,而只需要一些共有的参数。 比如我想对所有带图片link的数据做预加载操作:

这里我有两个不同类型的Observable:

    fun getVendors() : Observable<List<Vendor>>{
        return  xxxApi
                .getVendors()
                .doOnNext{vendors->
                    PreLoadImageHelper(vendors[0].image)
                }
    }

    fun getNotes():Observable<List<Note>>{
      return  xxxApi
                .getNotes()
                .doOnNext{notes->
                    PreLoadImageHelper(notes[0].image)
                }
    }

上面doOnNext会在ObservableonNext()之前调用。 关键是: PreLoadImageHelper不需要与任何特定类型绑定,只需要接收一个String即可

merge 与 zip

zip

zip可以把多种类型Observable的输出结果做一个组合,然和转化为另外一种类型的Observable继续发出。对于多个Observable的处理是按照顺序进行的。但是一旦有一个Observable出现error那么整个zip操作将无法继续,所以在使用时需要注意这种情况的发生,可以使用onErrorResumeNext()来防止一个接口爆掉导致其他接口受影响的case。

比如我要zip两个网络请求接口的数据:

    //true 代表处理出错, false代表处理正常。 onErrorResumeNext来处理接口异常情况。
    Observable<String> req1 = createSimpleObservable("1", true).onErrorResumeNext(new Func1<Throwable, Observable<? extends String>>() {
        @Override
        public Observable<? extends String> call(Throwable throwable) {
            return Observable.just(null);
        }
    });
    Observable<String> req2 = createSimpleObservable("2", false);

    Observable.zip(req1, req2 new Func3<String, String, String, Object>() {
            @Override
            public Object call(String s, String s2, String s3) {
                Log.d(TAG, "req1: " + s);
                Log.d(TAG, "req2 : " + s2);
                return s + s2;
            }
        }).subscribe(new MyObserver<>());

merge 与 mergeDelayError

merge类似于zip, 不过它不会做数据的转换操作,只是简单的按照顺序merge多个Observable。不过当任意一个Observable出现error时,都会终止merge操作。如果不希望这种处理,可以使用mergeDelayError,当一个Observable出现错误是不会
打断其他Observable的执行,并在事件的最后派发error事件,如下图:

mergeDelayError.png

比如发射3个网络请求:

    //true表示会error,  false 表示不会error

    Observable<String> req1 = createSimpleObservable("1", false, 1000L).doOnError{...};

    Observable<String> req2 = createSimpleObservable("2", true, 1000L).doOnError{...}; 

    Observable<String> req3 = createSimpleObservable("3", false, 1000L).doOnError{...};

    Observable.mergeDelayError(req1, req2, req3).subscribe(new MyObserver());

compose 与 transformer

来看一下它的声明:

public <R> Observable<R> compose(Transformer<? super T, ? extends R> transformer) {
    return ((Transformer<T, R>) transformer).call(this);
}

看方法可以理解为: compose使用一个Transformer将一种类型的Observable<T>装换为另一种类型Observable<R>。 通过compose我们可以实现一系列操作符的复用,并且还可以保证链式调用不被打断

比如在Android中常用的把网络请求放到后台的操作:

observable.subscribeOn(Schedulers.io).observeOn(AndroidSchedulers.mainThread())

如果对于每一个网络请求的Observable我们都写一遍这个会很烦。compose提供了一种不错的解决思路:

class BackgroundShcedulers<T> : Observable.Transformer<T, T> {
    override fun call(t: Observable<T>): Observable<T> {
        return t.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
    }
}

reqObservable.compose(BackgroundShcedulers<String>()) //不会断掉链式调用

其实除了复用多个操作符,还可以抽取一个Transformer来做某一个单一的工作,比如:

fun getNotes():Observable<List<Note>>{
    return repo.getNotes(..)
                .compose(PreLoadNoteImageTransformer()) //预加载图片
                .compose(PreLoadRichContentTransformer()) //预处理富文本
}

上面这个case还有很多其他的实现方式。

Transformermap/flatMap有什么不同呢? 其实关键的不同点是 : Transformer作用的对象是流,map/flatMap作用的对象是数据

transform 与 flatMap

比如下面这个例子:

   Observable.mergeDelayError(req1, req2).flatMap(new Func1<String, Observable<String>>() {
            @Override
            public Observable<String> call(String s) {
                Log.d(TAG, "flatMap origin data: " + s);
                return Observable.just("aaaa");
            }
        }).subscribe(new MyObserver<String>());

    Observable.mergeDelayError(req1, req2).compose(new Observable.Transformer<String, String>() {
            @Override
            public Observable<String> call(Observable<String> stringObservable) {
                Log.d(TAG, "compose call");
                return Observable.just("aaaa");
            }
        }).subscribe(new MyObserver<String>());

log日志如下:

D/RxTest: flatMap origin data: 1
D/RxTest: MyObserver next aaaa
D/RxTest: flatMap origin data: 2
D/RxTest: MyObserver next aaaa

D/RxTest: compose call
D/RxTest: MyObserver next aaaa
D/RxTest:  MyObserver  complete

Transformer作用的对象是流,map/flatMap作用的对象是数据

Subject

可以简单的把Subject理解为是一个既可以发送数据,又可以订阅自己发送的数据的对象。RxJava提供了多种不同类型的Subject,对于他们较详细的理解可以参考这篇文章:Subject使用及示例。下面主要看一下PublishSubject

PublishSubject

它与普通的Subject不同, 在订阅时并不立即触发订阅事件,而是允许我们在任意时刻手动调用onNext(),onError(),onCompleted()来触发事件。可以使用它来做一个模块事件触发的监听。

比如我在模块1中想监听模块2的Like事件:

模块2

class Module2{
    val eventListener = PublishSubject.create<String>()

    fun onLike(){
        eventListener.onNext("like")
    }
}

模块1

class Module1{
    fun listenModule2(){
        Module2().subscribe(object : CommonObserver<String>() {
            override fun onNext(eventType: String) {
                //do something
            }
        })
    }
}

简单点来说上面就是把Listener写的更优雅。

使用 PublishSubject 来构建 RxBus

除此之外还可以利用PublishSubject来实现一个RxBus:

object RxBus {

    private val mBus = SerializedSubject<>(PublishSubject.create())

    public void post(Object event) {
        mBus.onNext(event);
    }

    /**
    * 通过  mBus.ofType()来使订阅者可以指定接收哪种类型的事件
    **/
    public <T> Observable<T> toObservable(Class<T> eventType) {
        return mBus.ofType(eventType);
    }
}

使用方法:

//订阅事件
RxBusto.Observable(LikeEvent::class.java)
       .subscribe({})

//发射事件
RxBus.post(LikeEvent(true))

RxView

RxView这个类中封装了一系列的对View操作和方便监听View的事件回调,极大方便了UI的编写工作。比如下面这些方法:

view 的 addOnGlobalLayoutListener

RxView.globalLayouts(view).subscribe(MyObserver())

view 的 draw事件

RxView.preDraws(view).subscribe(MyObserver())

....

我最长使用的就是View的点击去抖:

去抖操作

利用kotlin的扩展函数,我们可以很方便的对一个ViewClick事件进行去抖:

fun View.throttleFirstClick(action: Action1<Any?>) {
    RxView.clicks(this).throttleFirst(500, TimeUnit.MILLISECONDS).subscribe(action, Action1 {})
}

定时操作 : interval

指定时间后执行一个任务:

fun delayExecute(time:Int, task:Runnable){
    Observable.interval(time, TimeUnit.SECONDS).take(1).subscribe(Action1<Long>() {
        @Override
        public void call(Long aLong) {
            task()
        }
    })
}

RxJava已经推出了2.0,带了了很多新的特性,可以去尝试使用一下。

最后:

欢迎关注我的Android进阶计划看更多干货

欢迎关注我的微信公众号:susion随心

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

推荐阅读更多精彩内容

  • 注:本系列文章主要用于博主个人学习记录,本文末尾附上了一些较好的文章提供学习。转载请附 原文链接RxJava学习系...
    黑丫山上小旋风阅读 2,141评论 1 5
  • 发现 关注 消息 RxSwift入坑解读-你所需要知道的各种概念 沸沸腾关注 2016.11.27 19:11*字...
    枫叶1234阅读 2,795评论 0 2
  • 在这Rx流行的时代,我也接触一段时间了,便通过这篇文章记录下Retrofit 2 + RxAndroid的一些实践...
    Jin丶破阅读 612评论 0 7
  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    huqj阅读 1,852评论 0 21
  • 最近在学习RxSwift相关的内容,在这里记录一些基本的知识点,以便今后查阅。 Observable 在RxSwi...
    L_Zephyr阅读 1,752评论 1 4