文章目录
前言
这篇文章主要编写的是我在工作中使用Rxjava后遇见的常见问题,本身不对源码进行剖析。
应用场景
场景一:访问api后,配置将创建耗时任务。
这个场景是我在使用阿里云OSS的服务时候,需要在后端获取到创建阿里云安卓端上传类OSSClient
的参数。但是,在创建过程中发现Oss报错NetworkOnMainThreadException
的异常。通过阿里云官方文档了解到OssClient
的对象,需要在线程中去创建。看了这篇文章《Android:随笔——RxJava的线程切换》后,作者在文章中提出一个知识点:
总所周知 RxJava 在切换线程时用到了两个方法 subscribeOn() 和 observeOn() 下面来分别解释一下这两个方法:
- subscribeOn() : 影响的是最开始的被观察者所在的线程。当使用多个 subscribeOn() 的时候,只有第一个 subscribeOn() 起作用;
- observeOn() : 影响的是跟在后面的操作(指定观察者运行的线程)。所以如果想要多次改变线程,可以多次使用 observeOn;
/**
* 初始化Oss客户端
* @param initCallBack
*/
public void initOSSClient(InitCallBack initCallBack) {
RepositoryManager.getInstance()
.obtainApiService(UploadDataApiService.class)
.getStsToken(1000)
.compose(RxUtil.transformerBaseResponse())
.map(stsTokenBean -> providerOssClient(stsTokenBean))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BackgroundSubscriber<OSS>() {
@Override
public void onSuccess(OSS oss) {
mOss = oss;
ToastUtil.showShort("mOss");
if (initCallBack != null) {
initCallBack.onSuccess();
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
initCallBack.onError(e.getMessage());
}
});
}
查看运行结果日志:
日志的打印在providerOssClient
函数中,这里我就不贴了。上面的代码中我们将subscribeOn
和observeOn
方法放在map
之后,这里通过上面的知识点,他的最初是的被观察者的线程还是运行在io
线程中。如果我们将方法放在map之前,map会受到observeOn
方法的影响,运行在主线程中,这样就会导致报错。
场景二:查询数据库中,抛出异常后,是什么流程
今天我在直接查询数据库中,主动向上抛出异常交给Rxjava来处理,但是突然报错了闪退了,犹豫我明明将进行了try-catch还会报错。我很诧异,特此将此分析记录下来。
DBDbHelper.getInstance()
.openRx()
.concatMap((Func1<SQLiteDatabase, Observable<List<WXMessage>>>) sqLiteDatabase -> {
return RxUtil.createData(WxMessageDao.getInstance()
.queryWxTMessage(sqLiteDatabase));
})
.compose(RxUtil.transformerScheduler())
.subscribe(new WxSubscriber<List<WXMessage>>() {
@Override
public void onSuccess(List<WXMessage> wxMessages) {
ToastUtil.showShort("wxMessages.size" + wxMessages.size());
}
@Override
public void onFailure(String msg) {
}
});
上面的代码封装好了,流程是两步:
- 使用Litepal查询本地数据库作为条件。
- 条件满足后触发查询另外一个数据库。
我在执行第一个流程的时候就报错了,然后我将异常抛给了Rxjava处理。debug了下,看了下堆栈信息。
上面的高亮出是Rxjava框架会将异常先给SafeSubscriber来处理,流程可以看代码注释。
protected void _onError(Throwable e) {
RxJavaPluginUtils.handleException(e);
try {
/*
* 1.actual是自定义Subscriber。
* 2.若定义出现了异常,则抓获交给SafeSubscriber的机制来处理。
*/
actual.onError(e);
} catch (Throwable e2) {
if (e2 instanceof OnErrorNotImplementedException) {
try {
unsubscribe();
} catch (Throwable unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new RuntimeException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException)));
}
throw (OnErrorNotImplementedException) e2;
} else {
RxJavaPluginUtils.handleException(e2);
try {
unsubscribe();
} catch (Throwable unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException)));
}
throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2)));
}
}
try {
unsubscribe();
} catch (RuntimeException unsubscribeException) {
RxJavaPluginUtils.handleException(unsubscribeException);
throw new OnErrorFailedException(unsubscribeException);
}
}
之前我在我的自定义的Subscriber中处理了异常org.litepal.exceptions.LitePalSupportException: the bind value at index 1 is null
,但是在执行到下面代码时直接报错
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/qmoor/qmoorassistant/model/utils/WxDbException;
被SafeSubscriber捕捉,导致App直接闪退。
@Override
public void onError(Throwable e) {
if (e instanceof WxDbException) {
ToastUtil.showShort(e.getMessage());
}
onFailure(e.getMessage());
super.onError(e);
}
如何解决呢,这里我将异常类WxDbException存放的包文件换了一个地方,重新编译就解决了,暂时不知道原因。
- 补充
之前在执行代码的时候,我们修改为下面这种方式没问题。
Observable.just(1)
.flatMap((Func1<Integer, Observable<List<WXMessage>>>) integer -> {
return Observable.create(emitter ->
{
try
{
emitter.onNext(WxMessageDao
.getInstance()
.queryWxTMessage(null));
emitter.onCompleted();
}
catch (Exception e)
{
emitter.onError(e);
}
});
})
.compose(RxUtil.transformerScheduler())
.subscribe(new WxSubscriber<List<WXMessage>>() {
@Override
public void onSuccess(List<WXMessage> wxMessages) {
ToastUtil.showShort("wxMessages.size" + wxMessages.size());
}
@Override
public void onFailure(String msg) {
}
});
参考文献:
场景三:订阅不释放会出现哪些情况?
大家在做订阅管理的时候,肯定使用过下面的代码,目的是Rxjava在执行的时候,Activity页面销毁后,若我们持有了当前Context的对象,会造成Activity无法释放,最终内存泄漏。
Rxjava1的订阅代码
private CompositeSubscription mCompositeDisposable;
public void addSubscribe(Subscription... subscriptions) {
if (mCompositeDisposable == null) {
mCompositeDisposable = new CompositeSubscription();
}
for (Subscription subscription : subscriptions) {
mCompositeDisposable.add(subscription);
}
}
public void unSubscribe() {
if (mCompositeDisposable != null) {
mCompositeDisposable.clear();//保证activity结束时取消所有正在执行的订阅
}
mCompositeDisposable = null;
}
上面的场景是在我们日常做App开发的时候会出现,下面我说下另外一种场景。
当在后台任务一直运行时,我们不及时取消订阅会出现什么?
- 订阅会一直增加
我们是用RxBus订阅的时候,我用线程池开启了一个定时轮询的任务,然后在start时候,会执行一次订阅代码
addSubscribe(RxBus.getDefault()
.toObservable(DataReadyBean.class)
.compose(transformerScheduler())
.subscribe(new WxSubscriber<WxDataReadyBean>() {
@Override
public void onSuccess(WxDataReadyBean radioBean) {
LogUtils.d("startSyncTask WxDataReadyBean= " + radioBean.toString());
switch (radioBean.getRequestCode()) {
case REQUEASTCODE_SYNC_ALL_COPY_DB:
break;
}
}
@Override
public void onFailure(String msg) {
}
}));
以上的代码,由于我不停的加入没有释放,在发布event时,会出现下面的log
第一次
接收信号,数据准备完成!
...
第二次
接收信号,数据准备完成!
接收信号,数据准备完成!
...
我的订阅会直接触发多次,导致我接下来的任务,出现问题。正确的做法,肯定是在每次执行完任务的时候,及时销毁。
- OOM
不释放肯定最终就是OOM,但是这里,我想说我们可以使用Android Profiler 测量应用性能工具去达到内存检测的效果。
场景四:上下游处理速度不一致,导致的背压异常。
今天在Bugly上,看见一个崩溃异常
异常信息
java.lang.IllegalStateException:Exception thrown on Scheduler.Worker thread. Add `onError` handling.
2 rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:60)
3 ......
4 Caused by:
5 rx.exceptions.MissingBackpressureException:
6 rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.onNext(OperatorObserveOn.java:130)
7 rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76)
8 rx.internal.operators.OperatorCast$1.onNext(OperatorCast.java:50)
9 rx.internal.operators.OperatorFilter$1.onNext(OperatorFilter.java:54)
10 rx.subjects.SubjectSubscriptionManager$SubjectObserver.onNext(SubjectSubscriptionManager.java:223)
11 rx.subjects.PublishSubject.onNext(PublishSubject.java:114)
12 rx.observers.SerializedObserver.onNext(SerializedObserver.java:95)
13 rx.subjects.SerializedSubject.onNext(SerializedSubject.java:64)
14 com.dustess.dustessassistant.common.rx.RxBus.post(RxBus.java:56)
15 com.dustess.dustessassistant.xiaomi.receiver.MIUIBackupReceiver.onReceive(MIUIBackupReceiver.java:44)
16 android.app.ActivityThread.handleReceiver(ActivityThread.java:3412)
17 android.app.ActivityThread.access$1300(ActivityThread.java:200)
18 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1684)
19 android.os.Handler.dispatchMessage(Handler.java:106)
20 android.os.Looper.loop(Looper.java:201)
21 android.app.ActivityThread.main(ActivityThread.java:6810)
22 java.lang.reflect.Method.invoke(Native Method)
23 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
24 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
看到堆栈信息,就是Rxjava的中上流发送太快,下流处理不及时的场景,我编写了一个样例代码,发送30次订阅事件。
private void backupAction() {
for (int i = 0; i < 30; i++) {
Event event = new Event();
RxBus.getDefault().post(backupEvent);
}
}
我先做了一个profile的分析。
点击发送订阅前
点击发送订阅后
内存在上升到一定量时下降平稳就抛出了异常信息,根据曲线的理解是Rxjava使用了一个集合内存储,存储的同时有消费的情况。分析了下代码,我在写RxBus的时候,在处理模块中,需要打开数据库的操作导致的。
RxBus.getDefault().toObservable(MIUIBackupEvent.class)
.compose(RxUtil.transformerSchedulerOnNewThread())
.subscribe(radioBean -> {
openMyDbAction(radioBean);
});
操作花费时间
2019-08-17 14:13:13.224 17082-17165 DataPrepareHelper:updateMemory and open wechat db take time 1376 ms
我猜测是背压引起的,google搜索了解决办法RxJava 教程第四部分:并发 之数据流发射太快如何办
上面清楚的分析了一些背压的操作,我在源码里面读了下,当我们发送操作的时
Event backupEvent = new Event(backResult, backupId);
RxBus.getDefault().post(backupEvent);
会在订阅的时候产生一个queue去存储,当存储到达queue的最大值时就会报错。
@Override
public void onNext(final T t) {
if (isUnsubscribed()) {
return;
}
if (!queue.offer(on.next(t))) {
onError(new MissingBackpressureException());
return;
}
schedule();
}
Rxjava的背压的核心就是自己维护的队列进行事件的存储,这里可以看见是16个,这个值是可以设置的。
知道了原理我们怎么去解决呢?这里我在项目中采用了onBackpressureDrop
方法目的是,如果消费者无法处理数据,则 onBackpressureDrop 就把该数据丢弃了。
RxBus.getDefault().toObservable(Event.class)
.onBackpressureDrop()
.compose(RxUtil.transformerSchedulerOnNewThread())
.subscribe(radioBean -> {
openMyDbAction(radioBean);
});
查看Profile的情况
加粗样式
看见下游处理不了的,可以看见下游处理不了的就被丢弃掉了。
场景五:取出列表集合中需要的数据上传。
有这样一种需求场景,当我们拉取接口的时候获取到一个列表集合,此时我们对列表集合进行多选以后,将多选数据打包集合进行上传,但是在上传过程当中,我们要剥离掉不需要的字段今天我们来处理这个问题。
返回的数据结构
{
"code": 1,
"msg": "获取我的待关注线索列表成功",
"data": {
"count": 11,
"list": [
{
"_id": "1a1dff59-f71e-11e9-9819-0242ac110004",
"customerStatus": "",
"birthday": "",
"companyId": "",
},
....
]
}
上传的数据格式
[
{
"cid": "xxx",
"customerStatus": "clue"
}
]
上面的场景中,我们需要将接收的列表id和customerStatus剥离出来,变为上传数据的格式,这里做的就是使用List<Map<String,String>>格式,使用Rxjava怎么处理呢。
Observable.fromIterable(list)
.map(bean -> {
Map<String, String> map = new HashMap<>();
map.put("cid", bean.getId());
map.put("customerStatus", bean.getCustomerStatus());
return map;
})
.buffer(list.size())
.flatMap((Function<List<Map<String, String>>, ObservableSource<Object>>) maps ->
ApiServiceManager.getInstance()
.getApiService(ApiService.class)
.addFocusFromMyCustomer(maps))
.compose(ThreadExecutorHandler.toMain());
可以看见,我们将选择的数据作为list遍历,然后创建一个Map将我们的数据结构赋值,这里使用到buffer进行一个数据缓存,缓存到我们的一定数据量后,进行上传结构的调用。
场景六:准备数据后进行界面显示
需求描述
最近做了个多选,在选择的时候需要判断我当前已经选中的数量,还可以选择多少个(这个数据是从接口返回的)。
获取关注上线和已关注数量
{
"code": 1,
"msg": "获取工作台最大限制成功",
"data": {
"maxNum": 20,
"focusNum": 14
},
"sid": ""
}
获取列表数据
{
"code": 1,
"msg": "获取成功",
"data": {
"count": 20,
"data": []
},
"sid": ""
}
我们需要将接口数据都准备完成后,才能展示界面。有接口报错则显示错误界面。
ZIP函数
通过ZIP函数,可以将两个API放回的数据结构整合打包后进行处理进行统一处理
Observable<DataResponse<Map<String, String>>> o1 = mMode.getFocusNumMsg(type);
Observable<Page<TestBean>> o2 = mMode.getClueCustomeroList(params);
addDisposable(Observable.zip(o1, o2, (response, list) -> {
if (!response.isCodeInvalid()){
getView().onFocusMsg(response.getResult());
}
return list;
}).retryWhen(new RetryWithDelay(RetryWithDelay.TYPE_RETRY_TIME, 3000))
.observeOn(ExecutorProvider.scheduler())
.compose(ThreadExecutorHandler.toMain())
.subscribe(page -> {
getView().hideLoading();
if (!Check.isEmpty(page.getList())) {
getView().resultMulti(page);
}
}, throwable -> {
getView().hideLoading();
if (!isNullView()) {
getView().showMessage(0, getGeneralErrorStr(throwable));
}
}));
创建两个订阅源o1,o2,使用zip打包。lambda中的(response, list),是他们对应的返回结构。数据处理进行加工就行。
zip的错误处理机制
Observable<Object> o1 = Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
KLog.e("request 1");
Integer integer = 1/0;
}
});
Observable<Object> o2 = Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
KLog.e("request 2");
String str = null;
str.toLowerCase();
}
});
Observable.zip(o1, o2, new BiFunction<Object, Object, Object>() {
@Override
public Object apply(Object o, Object o2) throws Exception {
return null;
}
}).subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
KLog.e(o.toString());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
KLog.e(throwable.getMessage());
}
});
经过测试,如果O1出错将会终止其他请求。如果需求每一个请求都需要请求然后统一处理异常可以使用combineLatest
函数。
源码解析文章
最近我在郭霖的公众号中,找到了rxjava的原理文章 《一起来造一个RxJava,揭秘RxJava的实现原理》摸清楚了rxjava的大致框架,主要包含如下:
- 订阅源和观察者
- 操作符
- 线程切换
参考链接
今天看了一个链接RxJava(十三)RxJava导致Fragment Activity内存泄漏问题里面的导读说了一些比较关注的问题,可以查看一下。