RxJava2的错误处理方案

不打扰是莪最后的温柔 提交于 2020-12-19 14:30:54

最近使用retrofit2 + rxKotlin2写接口访问,想尽量平铺代码,于是就想到当借口返回的状态码为「不成功」时(比如:code != 200),就连同网络错误一起,统一在onError方法中处理。想法总是好的,但是实际中却遇到onError无法捕获异常,造成应用崩溃的问题,终于在这个周末,我梳理清楚了RxJava的错误处理机制。

每一个后端接口基本都会有一个自定义的返回码,我们常常依据返回码来判断接口是否访问成功,是否成功返回我们想要的数据,当初作为一只菜鸟,我是这样来写的:

	fun login(account: String, pwd: String) {
        showProgress("登陆中")//展示菊花
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    if (it.code == 20000 && it.data != null) {
                        //登陆成功!
						//跳转到主页
                    } else {
						//登陆失败!
						//弹出提示
						//dismissProgress()关闭菊花
                    }
                }, {
                    dismissProgress()
                    //登陆失败!
					//弹出提示
						//dismissProgress()关闭菊花
                })
    }

这样写并没有什么问题,但是让大佬看到一定会笑,因为在onSuccess或onNext方法中,既有成功的逻辑,又有失败的逻辑,存在耦合性,并且处理接口访问失败的逻辑被分割成了两部分,那就等于失败的逻辑有一部分被混进了成功的逻辑,那么有没有什么办法能统一处理访问失败的逻辑呢?

巧用map操作符解偶

	fun login(account: String, pwd: String) {
        showProgress("登陆中")//展示菊花
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.map{ //注意这里的map
					if (it.code != 20000 || it.data == null) {
						throw Exception("登陆失败!")
					}
					it
				}
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                        //登陆成功!
						//跳转到主页
                }, {
                    dismissProgress()
                    //登陆失败!
					//弹出提示
						//dismissProgress()关闭菊花
                })
    }

我们看到,当在线程切换前,我们可以在map操作符逻辑中来判断返回码,如果返回码表示成功,那么就直接返回原数据,并且传递到onSuccess/onNext方法;如果返回码为失败的话,就手动抛出一个异常,然后在订阅中,onError方法能够捕获到这个异常,这样我们手动抛出的异常就可以与请求超时、404这种网络及的错误一起来处理,而onSuccess或者onNext方法中只需要处理访问成功的逻辑,成功完成了解偶,看起来是不是很清晰!

这里有一点要注意,在map逻辑必须要throw出exception,而不能直接返回exception,否则rxjava会认为你想把原数据转换成Exception类型的数据,并传递到onSuccess/onNext方法中

直调onError

有些时候业务逻辑可能会非常简单,接口也只包含了返回码或其他代表请求结果的信息,而没有给出具体的实际数据,而这时候聪明的你又想偷懒,那我们可以这样来写:

	fun login(account: String, pwd: String) {
        showProgress("登陆中")//展示菊花
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
					if (it.code != 20000 || it.data == null) {
						onError(Exception("登陆失败!"))//注意这里
					}
                    //登陆成功!
					//跳转到主页
                }, {
                    dismissProgress()
                    //登陆失败!
					//弹出提示
						//dismissProgress()关闭菊花
                })
    }

这里在返回码为失败的时候,直接调用了onError并且传递了一个exception,也是可以的。但是如果像map操作符中那样直接throw是不行。因为onError与onSuccess/onNoext平级,在onSuccess/onNext中直接抛出异常并没有其他方法来捕获,这样就很容易导致应用崩溃。

map配合onExceptionResumeNext

	fun login(account: String, pwd: String) {
        showProgress("登陆中")//展示菊花
        service.login(account,pwd)
                .subscribeOn(Schedulers.io())
				.map{ //注意这里的map
					if (it.code != 20000 || it.data == null) {
						throw Exception("登陆失败!")
					}
					it
				}
				.onExceptionResumeNext {
                    service.register(account,pwd,it)//把登陆操作转换成注册操作
                }
				.observeOn(AndroidSchedulers.mainThread())
                .subscribe({
					if (it.code != 20000 || it.data == null) {
						onError(Exception("失败!"))//注意这里
					}
                    //成功!
					//跳转到主页
                }, {
                    dismissProgress()
                    //失败!
					//弹出提示
						//dismissProgress()关闭菊花
                })
    }

onExceptionResumeNext操作符其实与flatmapflatmap操作很像,都是用来转换目标的,不同的是,onErrorResumeNext具有捕获异常的能力,而且仅当捕获到异常Exception后才会被调用。上例中我们就利用onErrorResumeNext达到了「登陆失败后自动注册」效果。

onErrorResumeNext

onErrorResumeNext操作符的用法与onExceptionResumeNext基本一样,但不同的是,后者仅捕获Exception,而前者只要是Throwable就捕获。,所以还是更推荐优先使用onExceptionResumeNext

onErrorReturn

private fun test5() {
        Single.just(0)
                .map {
                    if (it == 0) {
                        throw Exception("我擦")
                    }
                    1
                }
                .onErrorReturn {
                    2
                }
                .subscribe({
                    Log.d("测试", it.toString())
                }, {
                    Log.e("测试", it.message)
                })
    }

onErrorReturn本身与map很像,都是用来转换原数据的,不同的是,onErrorReturn也具有捕获异常的能力,而且仅当捕获到异常后才会被调用。onErrorReturn可以理解为:遭遇异常后返回一个默认值传递到onNext方法,然后调用onComplete方法终止发射。(这个操作符太好玩了)

retry

private fun test12() {
        Observable.range(0, 100)
                .map {
                    if (it == 0) {
                        throw Exception("我擦")
                    }
                    Log.d("测试","仍在发射${it}")
                    it
                }
                .retry { t1, t2 ->
                    t1 != 50
                }
                .subscribe({
                    Log.d("测试", it.toString())
                }, {
                    Log.e("测试", it.message)
                })
    }

retry操作符顾名思义就是用来重试,但是重试的条件是我们来定的,所以思维联想一下,就知道用这个操作符做轮询是很方便的!除了retry之外,rxjava还提供了retryWhenretryUntil等类似的操作符,与retry意思差不多,就不做解释了,感兴趣的同学可以自己去看。

Demo

本文例子

RxKotlin操作符使用大全(写了很多了,但还不全,以后会补)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!