RxJava中onErrorResumeNext与onError异常问题排查与解决
解析启动时断网触发CompositeException的原因
先聊聊你的场景:用Retrofit+RxJava做网络请求,Realm做本地缓存,预期请求失败时自动从缓存读取数据。测试发现先成功请求过再断网时,onErrorResumeNext能正常兜底加载缓存;但启动App就开飞行模式,请求直接走到onError回调,日志报CompositeException(包含2个异常)。你把Realm.init(getContext())移到请求调用前就解决了问题,咱们来拆解下背后的原因。
核心问题:Realm初始化的时机错配
首先得明确Realm的规则:必须在第一次调用Realm.getDefaultInstance()之前完成Realm.init()初始化,而且这个初始化最好全局一次性完成,比如在Application的onCreate里。
咱们来看你原来的代码逻辑:
apiContent.getNews("Token " + myHelper.getAuthToken(getActivity())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(NewsObject::getResults) .flatMap(news -> { // 只有请求成功时,才会执行到这里初始化Realm Realm.init(getContext()); Realm.getDefaultInstance().executeTransaction(realm -> { realm.delete(News.class); realm.insert(news); Log.d("myLogs", "Cached"); }); return Observable.just(news); }) .onErrorResumeNext(throwable -> { // 请求失败时直接走到这里,尝试获取Realm实例 Realm realm = Realm.getDefaultInstance(); RealmResults<News> results = realm.where(News.class).findAll(); ArrayList<News> dataNews = new ArrayList<News>(); dataNews.addAll(realm.copyFromRealm(results)); Log.d("myLogs", "Error, loading from cache"); return Observable.just(dataNews); }) // ...subscribe回调省略
两种场景的差异
先成功请求再断网:
- 第一次请求成功,
flatMap执行,Realm.init()完成初始化。 - 之后断网请求失败,进入
onErrorResumeNext时,Realm已经初始化过了,能正常获取实例、读取缓存,不会额外抛出异常,onErrorResumeNext正常兜底。
- 第一次请求成功,
启动就开飞行模式:
- 网络请求直接失败,上游Observable直接发射
error信号,flatMap里的代码根本不会执行,也就是说Realm.init()完全没被调用过。 - 进入
onErrorResumeNext后,调用Realm.getDefaultInstance(),这时候Realm还没初始化,会抛出IllegalStateException(提示Realm未初始化)。 - 原来的网络异常 + 这个Realm未初始化的异常,被RxJava包装成了
CompositeException,最终走到onError回调,而非onErrorResumeNext正常返回缓存。
- 网络请求直接失败,上游Observable直接发射
为什么移到请求前就解决了?
把Realm.init(getContext())移到请求调用前,相当于不管请求成功还是失败,Realm都已经完成初始化。当启动就断网时,onErrorResumeNext里调用Realm.getDefaultInstance()不会抛出异常,只会处理原来的网络异常,顺利读取缓存返回,自然不会出现CompositeException。
额外建议:Realm初始化的最佳实践
最好把Realm.init()放在自定义Application的onCreate方法里,全局初始化一次:
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); // 还可以在这里配置Realm全局规则,比如加密、数据迁移等 RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.setDefaultConfiguration(config); } }
这样整个App里任何地方用Realm都不用重复初始化,从根源避免这类初始化时机的问题。
内容的提问来源于stack exchange,提问作者Vitaly Kalenik
相关产品推荐
相关产品推荐

