简单粗暴一点的解决方式:
在Application的onCreate方法中加入以下代码:
kotlin写法:
RxJavaPlugins.setErrorHandler {
//异常处理
}
java写法:
RxJavaPlugins.setErrorHandler(new Consumer() {
@Override
public void accept(Throwable throwable) {
//异常处理
}
});
错误分析
正常环境下错误很难复现,但是当项目上线用户基数变大,app的使用场景丰富后,可能出现各种网络状况,在bugly上就会收到这样的报错信息:
错误定位到27行,这是 Retrofit的一个拦截器:
class CacheInterceptor :Interceptor{
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 有网络时 设置缓存超时时间1个小时
val maxAge = 60 * 60
// 无网络时,设置超时为1天
val maxStale = 60 * 60 * 24
var request = chain.request()
request = if (NetworkUtils.isNetworkAvailable(DcoinApp.appContext)) {
//有网络时只从网络获取
request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
} else {
//无网络时只从缓存中读取
request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build()
}
var response = chain.proceed(request)
response = if (NetworkUtils.isNetworkAvailable(DcoinApp.appContext)) {
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + maxAge)
.build()
} else {
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build()
}
return response
}
}
报错代码为:
var response = chain.proceed(request)
处理响应的时候报错,因为是统一的拦截处理,无法定位到具体位置
那么换一种思路,从OnErrorNotImplementedException异常的源头找起:
这个异常出现在RxJava源码的Observable.subscribe的一个重载方法中:
/**
* Subscribes to an ObservableSource and provides a callback to handle the items it emits.
*
* If the Observable emits an error, it is wrapped into an
* {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException}
* and routed to the RxJavaPlugins.onError handler.
*
* - Scheduler:
* - {@code subscribe} does not operate by default on a particular {@link Scheduler}.
*
*
* @param onNext
* the {@code Consumer} you have designed to accept emissions from the ObservableSource
* @return a {@link Disposable} reference with which the caller can stop receiving items before
* the ObservableSource has finished sending them
* @throws NullPointerException
* if {@code onNext} is null
* @see ReactiveX operators documentation: Subscribe
*/
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Disposable subscribe(Consumer super T> onNext) {
return subscribe(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION, Functions.emptyConsumer());
}
可以看到:这个方法只处理了onNext的情况,没有处理onError的情形,注释里是这样解释的:
如果被观察者发送一个error事件,它会被包装成一个OnErrorNotImplementedException,然后被导向到RxJavaPlugins.onError,但是比较坑爹的是RxJava不会帮我们处理这个异常,如果不自己处理的话,程序就崩溃掉。
因为RxJava2的一个重要的设计理念就是:不吃掉任何一个异常
One of the design goals of 2.x was that no errors can be lost. Sometimes, the sequence ends or gets cancelled before the source could emit an onError which has nowhere to go at that point and gets routed to the RxJavaPlugins.onError.
解决方案有2种:
- 使用RxJavaPlugins进行统一处理:
在Applicatin的onCreate方法中调用
RxJavaPlugins.setErrorHandler {
Logger.d("onRxJavaErrorHandler ---->: $it")
}
- 默认重写onError,提供处理的接口:
public abstract class NetObserver implements Observer> {
public static final String TAG = "NetObserver";
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(HttpResult value) {
String response = value.toString();
Log.i(TAG, "onNext:" + response);
if (value.isSuccess()) {
T t = value.getData();
onHandleSuccess(t);
onHandleSuccess(t, value.getMsg());
} else if (value.getCode() == AppConstant.TOKEN_EXPIRE || value.getCode() == AppConstant.TOKEN_SQUEEZE) {
onHandleError("");
LoginManager.getInstance().clearLoginState();
RxBus.INSTANCE.post(new TokenInvalidEvent(value.getCode()));
RxBus.INSTANCE.post(new LoginStateChangeEvent());
} else if (value.isAssetsPwdInvalid()) {
//资金密码时间问题
onHandleAssetsPwd();
} else {
onHandleError(value.getMsg());
onHandleError(value.getCode(), value.getMsg());
}
}
@Override
public void onError(Throwable e) {
LogUtil.d(TAG, "onerror:" + e.getMessage());
if (e instanceof SocketException) {
onHandleError("网络异常");
} else if (e instanceof TimeoutException || e instanceof SocketTimeoutException) {
onHandleError("请求超时");
} else if (e instanceof JsonParseException) {
onHandleError("数据解析失败");
}
if (!NetworkUtils.isNetworkAvailable(DcoinApp.appContext)) {
onHandleError("网络连接断开");
}
}
@Override
public void onComplete() {
}
protected abstract void onHandleSuccess(T t);
protected void onHandleError(String msg) {
UIUtils.showToast(msg);
}
protected void onHandleSuccess(T t, String msg) {
}
protected void onHandleError(int code, String msg) {
}
protected void onHandleAssetsPwd() {
}
}
在订阅的时候使用subscribe的这个重载的方法:
@SchedulerSupport(SchedulerSupport.NONE)
@Override
public final void subscribe(Observer super T> observer) {
ObjectHelper.requireNonNull(observer, "observer is null");
try {
observer = RxJavaPlugins.onSubscribe(this, observer);
ObjectHelper.requireNonNull(observer, "Plugin returned null Observer");
subscribeActual(observer);
} catch (NullPointerException e) { // NOPMD
throw e;
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
// can't call onError because no way to know if a Disposable has been set or not
// can't call onSubscribe because the call might have set a Subscription already
RxJavaPlugins.onError(e);
NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");
npe.initCause(e);
throw npe;
}
}
这样可以处理网络异常、超时、4xx、5xx等大多数错误
总结
两种方案都能有效的避免OnErrorNotImplementedException造成的崩溃,建议同时使用,这样既能够全局的处理报错,也能精确地对不同报错进行响应
参考:
https://medium.com/@bherbst/the-rxjava2-default-error-handler-e50e0cab6f9f
http://engineering.rallyhealth.com/mobile/rxjava/reactive/2017/03/15/migrating-to-rxjava-2.html