RxJava 谨慎串联Observable

问题

RxJava提供了flatMap和switchMap两个操作符用于让我们进行Observable的串联,比如我们可以使用RxView.clicks()创建一个会发送点击事件的Observable,同时我们还有一个用于请求网络数据的Observable:

Observable<Void> loginPress(){
  return RxView.clicks(findViewById(R.id.login));
}

Observable<LoginInfo> login() {
  return httpApi.login();
}

需求希望在login按钮点下之后,调用login()方法进行登陆。此时有两种写法:

  1. 直接串联:
loginPress().flatMap(aVoid -> login()).subscribe(loginInfo -> {
  // 处理登录逻辑
}, throwable -> {
  // 处理错误情况
});
  1. 分别调用:
loginPress().subscribe(aVoid -> {
  login().subscribe(loginInfo -> {
    // 处理登录逻辑
  }, throwable -> {
    // 处理错误情况
  });
});

从代码上看,第一种方式显然是Rx更为推荐的——不打破链式调用 的方式。但在有些时候,这种方法会出现比较严重的问题:原因是,subscriber在接受到错误以后,就无法接受到之后的事件了。

举上面的第一个使用例子来说有两个问题:

  1. 如果处理登录逻辑里发生了一些意料不到的错误(比如服务器有时候成功返回了数据,但有些数据为空导致了处理逻辑出现空指针),发生错误时,错误会回调到throwable->{}中。之后再进行按钮点击,数据返回subscriber都接收不到了。

  2. 如果login()方法里有错误,比如网络访问异常。那么当第一次点击按钮时,subscriber会收到网络异常的错误。但如果用户再点击登录按钮,无论是否成功,我们都没有办法再次接受到登录信息,页面也无法发生跳转。

前者在使用flatMap或者switchMap会发生,而后者在任何情况下都有可能出现。

解决方案

使用方案2,分别调用不会产生相应的问题。但打破了RxJava的链式调用。

对于使用方案1,最简单的解决方案是:在遇到错误重新绑定。但这种方式的成本比较高。每个处理订阅的地方都需要进行特殊处理。

首先是第一个问题:

  1. 如果处理登录逻辑里发生了一些意料不到的错误(比如服务器有时候成功返回了数据,但有些数据为空导致了处理逻辑出现空指针),发生错误时,错误会回调到throwable->{}中,但之后的任何数据返回subscriber都接不到了。

这种情况出现的其实比较少。对于这种不可意料的错误,我们可以使用一个大大的try-catch把subscriber包起来,比如实现一个类似这样的类:

public class ErrorHandlerSubscriber<T> extends Subscriber<T> {
  private Action1<T> onNext;
  private Action1<Throwable> onError;

  public ErrorHandlerSubscriber(Action1<T> onNext, Action1<Throwable> onError) {
    this.onNext = onNext;
    this.onError = onError;
  }

  @Override
  public void onCompleted() {}

  @Override
  public void onError(Throwable e) {
    if (onError != null) {
        onError.call(e);    
    }
  }

  @Override
  public void onNext(T t) {
    try {
        if (onNext != null) {
            onNext.call(t);
        }
    } catch (Exception e) {
        if (onError != null) {
            onError.call(e);
        } else {
          // log it
        }
    }}
}

在使用时:

login().subscribe(new ErrorHandlerSubscriber(loginInfo -> {
    // 处理登录逻辑
  } , throwable -> {
    // 处理失败
  }));

这样一来,错误实际上不会被转发到Subscriber内,而只是会传到我们自定义的throwable -> {}里。也就不会影响实际Subscriber后续事件的接受。

同时,建议在// log it的地方将错误日志打出来,方便调试。


对于第二个问题:

2.如果login()方法里有错误,比如网络访问异常。那么当第一次点击按钮时,subscriber会收到网络异常的错误。但如果用户再点击登录按钮,无论是否成功,我们都没有办法再次接受到登录信息,页面也无法发生跳转。

这种情况出现出现会十分频繁,尤其在进行网络请求时。解决方案有N种

1.如果你不关心错误,可以使用switchMapDelayError

这个关键字可以起到忽略错误的作用,但大部分情况下,我们希望在遇到错误对用户进行提示。所以如果你不关心错误是否发生的情况下,使用这个关键字进行串联是最简单的。

2.使用materialize()将next和error都包装到notification中:

http://stackoverflow.com/questions/32084824/rxjava-rxbinding-how-to-handle-errors-on-rxview

loginPress().flatMap(aVoid -> login().materialize()).subscrber(notification -> {
  if(notification.hasValue()){
    // 处理登录逻辑
  } else if(notification.isOnError()) {
    // 处理失败逻辑 
  }
});

3.使用doOnError处理错误,同时使用onErrorResumeNext忽略错误:

loginPress().flatMap(aVoid -> login().doOnError(throwable -> {
    // 处理失败逻辑 
  }).onErrorResumeNext(throwable -> Observable.empty())
  ).subscriber(loginInfo -> {
    // 处理登录逻辑
  } , throwable -> {
    // 处理失败
  });

这样一来,flatMap里的Observable实际上就不会发生错误,也就不会造成相应的问题了。

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

推荐阅读更多精彩内容

  • 在正文开始之前的最后,放上GitHub链接和引入依赖的gradle代码: Github: https://gith...
    苏苏说zz阅读 671评论 0 2
  • 文章转自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物线在正...
    xpengb阅读 7,007评论 9 73
  • 在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码: Github: https://...
    松江野人阅读 5,845评论 0 1
  • 使用体验 描述 举个例子:用户开启程序,不用等待就能看到课程信息。向下滑动,后台自动获取更多,不用等待加载过程…这...
    liangtong阅读 297评论 0 0
  • 改变的第一步,增加自己渴望的第一步就是学会对别人发出哇,如果你每次看到别人好,你都发出呸,每次看到别人有贵人的帮忙...
    77天赋臻月阅读 286评论 0 0