RxJava的应用场景记录

本文介绍了RxJava在实际开发中的常见应用场景,包括访问API后的耗时任务、数据库查询异常处理、订阅释放、背压异常、列表数据处理及界面显示等。同时,探讨了线程切换、异常处理和内存泄漏问题,提供了相应的解决方案和源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这篇文章主要编写的是我在工作中使用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函数中,这里我就不贴了。上面的代码中我们将subscribeOnobserveOn方法放在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

第一次
接收信号,数据准备完成!
...

第二次
接收信号,数据准备完成!
接收信号,数据准备完成!
...

我的订阅会直接触发多次,导致我接下来的任务,出现问题。正确的做法,肯定是在每次执行完任务的时候,及时销毁。

场景四:上下游处理速度不一致,导致的背压异常。

今天在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内存泄漏问题里面的导读说了一些比较关注的问题,可以查看一下。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值