最近使用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还提供了retryWhen
、retryUntil
等类似的操作符,与retry意思差不多,就不做解释了,感兴趣的同学可以自己去看。
Demo
RxKotlin操作符使用大全(写了很多了,但还不全,以后会补)
来源:oschina
链接:https://my.oschina.net/u/1771562/blog/1860963