如何防止Rx订阅事件在onError()过程中奔溃
起因
最近在应用检测平台上看到一些奔溃报告,貌似是RxJava相关的:
OnErrorFailedException.png
定位到了OnErrorFailedException,Google一下,
throw OnErrorFailedException.png
RxJava注释上说,这是onError()里的代码报异常。接触过RxJava的都应该知道,onError()是我们订阅事件时,中途出现问题时会回调的方法(onNext()中出现的异常也会回调onError())。如果连onError()里的代码也出现异常,那么Rx框架是会抛出这个OnErrorFailedException,程序就会奔溃。
按道理,此时应该在onError()里找出那行有毒的代码。尴尬的是,项目中基本在执行网络请求的地方都用了Rx订阅事件,少说也有二三百个地方用到了onError(),奔溃报告并没有记录具体出错的地方, 一个个地方排查无疑是效率低的做法。而且,下次再在onError()写出产生异常的代码那怎么办?毕竟没人能保证自己一定不会再犯错。我想到比较好的办法是为onError()统一捕获异常!!幸好,在实现Rx订阅事件的每个地方,之前我并没有直接用到Subscriber这个Rx提供的抽象类,而是自己做了个中间层,SubscriberAdapter。这为统一为onError()捕获异常提供了一个入口。
public abstract class SubscriberAdapter<T> extends Subscriber<T> {
@Override
public void onCompleted() {
}
@Override
public void onNext(T t) {
onSuccess(t);
}
public abstract void onSuccess(T t);
解决思路
在这之前,我只简单处理了下onNext(),目的是,日后如果想在onNext()前后统一添加动作(没理解错的话,这是设计模式中的装饰者模式?),那就很方便了。没想到现在首先要添加动作的是onError()。在项目中,每处订阅事件的地方都实现了SubscriberAdapter,也写死了方法名onError()。此时就算我们在SubscriberAdapter类里面怎么重写onError()也不会对影响到订阅事件发生的地方,也就无法捕捉异常,怎么办呢?这里提供一个小技巧,我们先在SubscriberAdapter类里重写onError(),但是空实现,
public abstract class SubscriberAdapter<T> extends Subscriber<T> {
@Override
public void onError() {
}
}
此时,studio已经将这个空实现的方法和几百处具体实现的onError()关联上了。此时我们光标选中SubscriberAdapter的"onError"字符,同时按下shift+F6,就可以同时修改几百处方法名称,我在这改为onDispatchError()。然后我们再重写多一次onError(),这次就要捕捉异常了,具体代码是
public abstract class SubscriberAdapter<T> extends Subscriber<T> {
@Override
public void onCompleted() {
}
@Override
public void onNext(T t) {
onSuccess(t);
}
public abstract void onSuccess(T t);
@Override
public void onError(Throwable e) {
try {
onDispatchError(e);
} catch (Exception exception) {
Toast.makeText(IYourCarApplication.getAppContext(), "处理出现错误", Toast.LENGTH_LONG).show();
}
}
public void onDispatchError(Throwable e) {
}
好了,轻松为原来几百处实现了onError()接口的地方都统一捕捉了异常。现在就算在onError()里写了本来会导致奔溃的代码,现在也起码不会奔溃,给了用户提示,用户体验对比直接奔溃要好了不少。
效果演示,我在onNext()和onError()故意设置空指针异常后,
demo.png
可以看到,空指针后并没有奔溃,并且弹出了我们设置的toast。
后话
当引入大型的第三方框架时(例如Rx,retrofit,Glide),在项目代码和框架之间多加一层,不直接调用框架的API,是很有必要的。因为如果开源框架的API变动的时候,或者像本文这样,想统一添加一些操作的时候,如果没有中间这一层,那么就很可能要每个地方都要修改了,这种基础的框架,一改肯定就是一千几百处的修改了,这会是多么痛的领悟~~