RxJava日常使用总结(五)错误处理

说到异常处理,Java有try-catach,那在RxJava的世界里,怎么捕获和处理异常呢?一起来看下吧~

onErrorReturn操作符

让Observable遇到错误时发射一个特殊的项并且正常终止。其实就是当发生错误时,提供一个回调,返回一个出错时使用的值。

image.png
  • 例如订单列表,需要设置一个价格文本,price字段,为了保证价格在json中不会丢失精度,一般会用String类型去存在,再转换为Double等数据类型,但是可能在后端出现了某种原因返回了非数字时,想Double.valueOf(price)肯定会抛出异常,那么RxJava如何处理呢?
Observable.just(itemModel.getAskMoney())
                .map(new Function<String, Double>() {
                    @Override
                    public Double apply(String priceStr) throws Exception {
                        return Double.valueOf(priceStr);
                    }
                })
                .onErrorReturn(new Function<Throwable, Double>() {
                    @Override
                    public Double apply(Throwable throwable) throws Exception {
                        return 0.0;
                    }
                })
                .subscribe(new Consumer<Double>() {
                    @Override
                    public void accept(Double price) throws Exception {
                        //价格设置
                        String priceStr = context.getResources().getString(R.string.base_money_text, price);
                        holder.vPrice.setText(priceStr);
                    }
                });
  • 在map操作符转换数据类型时,可能会抛出异常,RxJava2在map中已经直接加了try-catch捕获,所以这里直接抛出,我们不用做处理,onErrorReturn()操作符,要求返回一个异常后的返回值,例如我们直接返回0.0,代表默认值。

onErrorResumeNext操作符

方法返回一个镜像原有Observable行为的新Observable,后者会忽略前者的onError调用,不会将错误传递给观察者,作为替代,它会开始镜像另一个备用的Observable。一句话总结,onErrorReturn操作符返回的是具体值,而onErrorResumeNext则是返回Observable包裹的值,相当于转换成了另外一个Observable,简单理解就是类似map和flatMap。

image.png
  • 像到淘宝的首页,条目类型很多,而且样式也不一致,顶部轮播图,中间网格,底部瀑布流。一般后端不会一个接口给完(因为他们查的表,一般会不一样),所以在使用RxJava时,一般都会使用concatMap操作符连接多个顺序接口,再toList聚合在一起,一次性发送,再渲染界面。

  • 例如项目中一个模块首页,顶部轮播图,在一个分类网格,再是一个广告列表,最后是一个老师列表。因为RxJava的链式串联,任何一个串联的Observable数据源出现error都会整个链断裂,整个数据就会没了,像广告这种接口,如果发生错误也不应该影响整个界面,所以使用onErrorResumeNext操作符,返回Observable.empty(),让发生error的这条数据源正常结束,即可不影响整个串联调用链。

Observable.concatDelayError(
                Arrays.asList(
                        getMainBanner(context, tag),
                        getTeacherServerCategory(context, tag),
                        //广告接口,不太重要,如果错误,就不显示
                        AskTeacherAdManager.getAdList(context).onErrorResumeNext(Observable.empty())
                        , getAllTeacherList(context, tag, page)
                ))
                .collect(new Callable<List<HttpModel<?>>>() {
                    @Override
                    public List<HttpModel<?>> call() throws Exception {
                        return new ArrayList<>();
                    }
                }, new BiConsumer<List<HttpModel<?>>, HttpModel<?>>() {
                    @Override
                    public void accept(List<HttpModel<?>> list, HttpModel<?> listModel) throws Exception {
                        list.add(listModel);
                    }
                }).toObservable();

onExceptionResumeNext操作符

和onErrorResumeNext类似,也是接收一个备用的Observable,但是和onErrorResumeNext的区别是,如果产生的异常对象不是Exception,不会进行捕获,也不会使用备用的Observable,会将异常依然传入到原来的Observable,调用它的onError。所以如果异常对象是Throwable,则不会被捕获到,例如我们的OutOfMemoryError,不是Exception的子类而是Throwable的子类。

image.png
  • 使用和onErrorResumeNext一毛一样的,例如我们需要加载一个图片Url,生成Bitmap,当这个Bitmap内存占用真的超过的可分配的内存时,产生OOM了,使用onErrorResumeNext来不捕获异常,让原来的Observable数据源进行onError回调的处理。其他异常,例如可能在拉取图片途中,网速或者某种原因,发生了TimeoutException,连接超时异常,这时候就会被捕获调,再进行相应的处理,例如重试。
Observable.create(new ObservableOnSubscribe<Bitmap>() {
    @Override
    public void subscribe(ObservableEmitter<Bitmap> emitter) throws Exception {
        try {
            //可能抛出OutOfMemoryError
            Bitmap bitmap = loadImageUrlToBitmap();
            if (bitmap != null) {
                emitter.onNext(bitmap);
            } else {
                emitter.onError(new NullPointerException("load image url bitmap is null"));
            }
        } catch (TimeoutException e) {
            //正常是不需要try-catch再抛出的,subscribe是允许抛出Exception被内部调用时捕获调用onError(),这里只是演示
            e.printStackTrace();
            emitter.onError(e);
        }
    }
})
        //这里只会处理抛出是Exception的情况,OutOfMemoryError是Throwable,不会被捕获,会抛出到源Observable
        .onExceptionResumeNext(Observable.create(new ObservableOnSubscribe<Bitmap>() {
            @Override
            public void subscribe(ObservableEmitter<Bitmap> emitter) throws Exception {
                //这里加载占位图drawable,生成Bitmap
                Bitmap placeholderBitmap = BitmapFactory.decodeResource(mContext.getResources().getDrawable(R.drawable.error_placeholder));
                emitter.onNext(placeholderBitmap);
            }
        }));

retry操作符

如果原始Observable遇到错误,重新订阅它期望它能正常终止。就是Observable发生异常时(发送onError),捕获,并重新调用Observable的subject订阅方法,重新订阅。

image.png
  • retry(),只要发生错误,就重新订阅,无限重试。

例如WebSocket连接,发生异常抛出异常,或我们手动调用发送onError(),因为retry操作符的存在,Observable会被重订阅,重订阅

Observable.create(new WebSocketOnSubscribe(url))
        //无限重试
        .retry()
        //将回调都放置到主线程回调,外部调用方直接观察,实现响应回调方法做UI处理
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
        
     /**
     * 组装数据源
     */
    private final class WebSocketOnSubscribe implements ObservableOnSubscribe<WebSocketInfo> {
        private String mWebSocketUrl;
        private WebSocket mWebSocket;
        private boolean isReconnecting = false;

        public WebSocketOnSubscribe(String webSocketUrl) {
            this.mWebSocketUrl = webSocketUrl;
        }

        @Override
        public void subscribe(ObservableEmitter<WebSocketInfo> emitter) throws Exception {
            initWebSocket(emitter);
        }

        private Request createRequest(String url) {
            return new Request.Builder().get().url(url).build();
        }

        /**
         * 初始化WebSocket
         */
        private synchronized void initWebSocket(ObservableEmitter<WebSocketInfo> emitter) {
            if (mWebSocket == null) {
                mWebSocket = mClient.newWebSocket(createRequest(mWebSocketUrl), new WebSocketListener() {
                    @Override
                    public void onOpen(WebSocket webSocket, Response response) {
                        super.onOpen(webSocket, response);
                        //连接成功
                        if (!emitter.isDisposed()) {
                            mWebSocketPool.put(mWebSocketUrl, mWebSocket);
                            //重连成功
                            if (isReconnecting) {
                                emitter.onNext(createReconnect(webSocket));
                            } else {
                                emitter.onNext(createConnect(webSocket));
                            }
                        }
                        isReconnecting = false;
                    }
                    
                    //...省略其他重写方法

                    @Override
                    public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
                        super.onFailure(webSocket, throwable, response);
                        isReconnecting = true;
                        mWebSocket = null;
                        //移除WebSocket缓存,retry重试重新连接
                        removeWebSocketCache(webSocket);
                        if (!emitter.isDisposed()) {
                            emitter.onNext(createPrepareReconnect());
                            //失败发送onError,让retry操作符重试
                            emitter.onError(new ImproperCloseException());
                        }
                    }
                });
            }
        }
    }
  • retry(int count),设置一个重试次数,超过次数就不重试了。

如果要限定重试次数,retry操作符传入次数即可。例如retry(100),重试100次后不重试,将onError()发送给Observable。

Observable.create(new WebSocketOnSubscribe(url))
        //重试100后停止重试
        .retry(100)
        //将回调都放置到主线程回调,外部调用方直接观察,实现响应回调方法做UI处理
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

//后面数据源订阅部分一致,就补贴了...
  • retry(new Predicate<Throwable>()),接收一个Predicate谓词接口实现,相当于设置一个重试条件,返回true则重试,false则不重试。

如果需要判断指定异常才捕获,就可以使用retry(new Predicate<Throwable>())

Observable.create(new WebSocketOnSubscribe(url))
        //重试100后停止重试
        .retry(new Predicate<Throwable>() {
                        @Override
                        public boolean test(Throwable throwable) throws Exception {
                            return throwable instanceof ImproperCloseException;
                        }
                    })
        //将回调都放置到主线程回调,外部调用方直接观察,实现响应回调方法做UI处理
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

retryUntil

和retry(new Predicate<Throwable>())类似,也是提供一个重试条件,但是返回值是相反的,返回true不重试,返回false重试。

Observable.create(new WebSocketOnSubscribe(url))
        //重试100后停止重试
        .retryUntil(new BooleanSupplier() {
                        @Override
                        public boolean getAsBoolean() throws Exception {
                            //有网络,则停止重试,无网络则一直重试
                            return NetworkUtil.hasNetWorkStatus(mContext, false);
                        }
                    })
        //将回调都放置到主线程回调,外部调用方直接观察,实现响应回调方法做UI处理
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

retryWhen

和上面的retry和retryUntil不一样,retryWhen需要返回一个Observable数据源,这个Observable发出onError则会继续走重试,除非是onNext或者onComplete。使用Observable则可以做一系列转换的事情。例如出错了,返回的Observable中去判断网络环境,或者调用结果查询或查询数据库等操作。(Token过期的401也可以使用retryWhen去处理,flatMap去用RefreshToken刷新AssessToken)

image.png
Observable.create(new WebSocketOnSubscribe(url))
         .retryWhen(new Function<Observable<Throwable>, ObservableSource<WebSocketInfo>>() {
                        @Override
                        public ObservableSource<WebSocketInfo> apply(Observable<Throwable> throwableObservable) throws Exception {
                            return throwableObservable.flatMap(new Function<Throwable, ObservableSource<WebSocketInfo>>() {
                                @Override
                                public ObservableSource<WebSocketInfo> apply(Throwable throwable) throws Exception {
                                    //非正常关闭的异常,重试
                                    if (throwable instanceof ImproperCloseException) {
                                        return Observable.just(createPrepareReconnect());
                                    } else {
                                        //其他异常,继续将异常交给原Observable去处理
                                        return Observable.error(throwable);
                                    }
                                }
                            });
                        }
                    })
        //将回调都放置到主线程回调,外部调用方直接观察,实现响应回调方法做UI处理
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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