RxJava处理业务异常的几种方式

关于异常

Java的异常可以分为两种:运行时异常和检查性异常。

运行时异常:
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。

检查性异常:
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查性异常。检查性异常必须被显式地捕获或者传递。当程序中可能出现检查性异常时,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。

处理业务异常

业务异常:
指的是正常的业务处理时,由于某些业务的特殊要求而导致处理不能继续所抛出的异常。在业务层或者业务的处理方法中抛出异常,在表现层中拦截异常,以友好的方式反馈给使用者,以便其可以依据提示信息正确的完成任务功能的处理。

1. 重试

不是所有的错误都需要立马反馈给用户,比如说在弱网络环境下调用某个接口出现了超时的现象,也许再请求一次接口就能获得数据。那么重试就相当于多给对方一次机会。

在这里,我们使用retryWhen操作符,它将错误传递给另一个被观察者来决定是否要重新给订阅这个被观察者。

听上去有点拗口,直接上代码吧。

    /**
     * 获取内容
     * @param fragment
     * @param param
     * @param cacheKey
     * @return
     */
    public Maybe<ContentModel> getContent(Fragment fragment, ContentParam param, String cacheKey) {

        if (apiService == null) {
            apiService = RetrofitManager.get().apiService();
        }

        return apiService.loadContent(param)
                .retryWhen(new RetryWithDelay(3,1000))
                .compose(RxLifecycle.bind(fragment).<ContentModel>toLifecycleTransformer())
                .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey));
    }

这个例子是一个网络请求,compose的内容可以忽略。如果网络请求失败的话,会调用retryWhen操作符。RetryWithDelay实现了Function接口,RetryWithDelay是一个重试的机制,包含了重试的次数和重试时间隔的时间。

import com.safframework.log.L;

import org.reactivestreams.Publisher;

import java.util.concurrent.TimeUnit;

import io.reactivex.Flowable;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;

/**
 * 重试机制
 * Created by tony on 2017/11/6.
 */

public class RetryWithDelay implements Function<Flowable<? extends Throwable>, Publisher<?>> {

    private final int maxRetries;
    private final int retryDelayMillis;
    private int retryCount;

    public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
        this.maxRetries = maxRetries;
        this.retryDelayMillis = retryDelayMillis;
        this.retryCount = 0;
    }

    @Override
    public Publisher<?> apply(@NonNull Flowable<? extends Throwable> attempts) throws Exception {

        return attempts.flatMap(new Function<Throwable, Publisher<?>>() {
            @Override
            public Publisher<?> apply(Throwable throwable) throws Exception {
                if (++retryCount <= maxRetries) {

                    L.i("RetryWithDelay", "get error, it will try after " + retryDelayMillis
                            + " millisecond, retry count " + retryCount);
                    // When this Observable calls onNext, the original
                    // Observable will be retried (i.e. re-subscribed).
                    return Flowable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);

                } else {

                    // Max retries hit. Just pass the error along.
                    return Flowable.error(throwable);
                }
            }
        });
    }
}

如果运气好重试成功了,那用户在无感知的情况下可以继续使用产品。如果多次重试都失败了,那么必须在onError时做一些异常的处理,提示用户可能是网络的原因了。

2. 返回一个默认值

有时出错只需返回一个默认值,有点类似Java 8 Optional的orElse()

RetrofitManager.get()
                .adService()
                .vmw(param)
                .compose(RxLifecycle.bind(fragment).<VMWModel>toLifecycleTransformer())
                .subscribeOn(Schedulers.io())
                .onErrorReturn(new Function<Throwable, VMWModel>() {
                    @Override
                    public VMWModel apply(Throwable throwable) throws Exception {
                        return new VMWModel();
                    }
                });

上面的例子使用了onErrorReturn操作符,表示当发生错误的时候,发射一个默认值然后结束数据流。所以 Subscriber 看不到异常信息,看到的是正常的数据流结束状态。

跟它类似的还有onErrorResumeNext操作符,表示当错误发生的时候,使用另外一个数据流继续发射数据。在返回的被观察者中是看不到错误信息的。

使用了onErrorReturn之后,onError是不是就不做处理了?onErrorReturn的确是返回了一个默认值,如果onErrorReturn之后还有类似doOnNext的操作,并且doOnNext中出错的话,onError还是会起作用的。

曾经遇到过一个复杂的业务场景,需要多个网络请求合并结果。这时,我使用zip操作符,让请求并行处理,等所有的请求完了之后再进行合并操作。某些请求失败的话,我使用了重试机制,某些请求失败的话我给了默认值。

3. 使用onError处理异常

现在的Android开发中,网络框架是Retrofit的天下。在接口定义的返回类型中,我一般喜欢用Maybe、Completable来代替Observable,不了解它们的同学可以看之前的文章RxJava的Single、Completable以及Maybe

我们知道RxJava在使用时,观察者会调用onNext、onError、onComplete方法,其中onError方法是事件在传递或者处理的过程中发生错误后会调用到。

下面的代码,分别封装两个基类的Observer,都重写了onError方法用于处理各种网络异常。这两个基类的Observer是在使用Retrofit时使用的。

封装一个BaseMaybeObserver

import android.accounts.NetworkErrorException
import android.content.Context

import com.safframework.log.L
import io.reactivex.observers.DisposableMaybeObserver
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * Created by Tony Shen on 2017/8/8.
 */
abstract class BaseMaybeObserver<T> : DisposableMaybeObserver<T>() {

    internal var mAppContext: Context

    init {
        mAppContext = AppUtils.getApplicationContext()
    }

    override fun onSuccess(data: T) {
        onMaybeSuccess(data)
    }

    abstract fun onMaybeSuccess(data: T)

    override fun onError(e: Throwable) {
        var message = e.message
        L.e(message)

        when(e) {

            is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)
            is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)
            is UnknownHostException -> message = mAppContext.getString(R.string.network_error)
            is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)
            else -> message = mAppContext.getString(R.string.something_went_wrong)
        }

        RxBus.get().post(FailedEvent(message))
    }

    override fun onComplete() {}
}

封装一个BaseCompletableObserver

import android.accounts.NetworkErrorException
import android.content.Context

import com.safframework.log.L
import io.reactivex.observers.ResourceCompletableObserver
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * Created by Tony Shen on 2017/8/8.
 */
abstract class BaseCompletableObserver : ResourceCompletableObserver() {

    internal var mAppContext: Context

    init {
        mAppContext = AppUtils.getApplicationContext()
    }

    override fun onComplete() {
        onSuccess()
    }

    abstract fun onSuccess()

    override fun onError(e: Throwable) {
        var message = e.message
        L.e(message)

        when(e) {

            is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)
            is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)
            is UnknownHostException -> message = mAppContext.getString(R.string.network_error)
            is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)
            else -> message = mAppContext.getString(R.string.something_went_wrong)
        }

        RxBus.get().post(FailedEvent(message))
    }
}

在这里用到了Kotlin来写这两个基类,使用Kotlin的目的是因为代码更加简洁,避免使用switch或者各种if(XX instancof xxException)来判断异常类型,可以跟Java代码无缝结合。

下面的代码展示了如何使用BaseMaybeObserver,即使遇到异常BaseMaybeObserver的onError也会做相应地处理。如果有特殊的需求,也可以重写onError方法。

                model.getContent(VideoFragment.this,param, cacheKey)
                        .compose(RxJavaUtils.<ContentModel>maybeToMain())
                        .doFinally(new Action() {
                            @Override
                            public void run() throws Exception {
                                refreshlayout.finishRefresh();
                            }
                        })
                        .subscribe(new BaseMaybeObserver<ContentModel>(){

                    @Override
                    public void onMaybeSuccess(ContentModel data) {
                        adpter.addDataToFront(data);
                    }
                });

4. 内部异常使用责任链模式来分发

这是微信中一位网友提供的方法,他做了一个很有意思的用于异常分发的一个库,github地址:https://github.com/vihuela/Retrofitplus

内部异常使用责任链分发,分发逻辑为:

  • 自定义异常->网络异常->服务器异常->内部程序异常->未知异常
  • 除了以上自定义异常之外,此库包含其它异常分发,默认适应场景为:Rx+Json
  • 自定义异常使用请调用,ExceptionParseMgr类的addCustomerParser方法添加业务异常

这个库对原先的代码无侵入性。此外,他还提供了另一种思路,结合compose来处理一些特定的业务异常。

总结

本文仅仅是总结了个人使用RxJava遇到业务异常的情况,并对此做了一些相应地处理,肯定是不能覆盖开发的方方面面,仅作为抛砖引玉,如果有更好、更优雅的处理方式,一定请告知。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • RxJava RxJavaGithub的wiki的介绍: RxJavais a Java VM implement...
    DorisSunny阅读 754评论 0 3
  • 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和...
    爱码士平头哥阅读 1,750评论 0 9
  • 我开始回忆起你的时候,恰好是那个俗套节目播出的时候,在荧幕剧烈跳动到了最高点,我终于还是一把关掉了它。我开始讨厌它...
    tc大叔阅读 287评论 1 3