将数据库和网络调用与RxJava2结合使用

时间:2018-08-30 03:14:48

标签: java android rx-java rx-java2

我有2个数据源:数据库(缓存)和api,我需要将它们组合成一个流。我知道我可以简单地使用concatArray或类似的东西,但是我想实现更复杂的行为:

  • 可观察到的流,最多可发射2个元素。

  • 它将在开始时同时订阅两个来源。

  • 如果api调用足够快(<〜300ms),它将仅从中发出数据并完成流。

  • 如果api调用速度较慢(>〜300毫秒),请从数据库中发出数据,并仍在等待api中的数据
  • 如果api调用无法成功,请从数据库发出数据并发出错误。
  • 如果数据库以某种方式比api慢,它将无法发出其数据(流完成解决了该问题)

我用以下代码完成了它:

    public Observable<Entity> getEntity() {
    final CompositeDisposable disposables = new CompositeDisposable();
    return Observable.<Entity>create(emitter -> {
        final Entity[] localEntity = new Entity[1];

        //database call:
        disposables.add(database.getEntity()
                .subscribeOn(schedulers.io())
                .doOnSuccess(entity -> localEntity[0] = entity) //saving our entity because 
                                                        //apiService can emit error before 300 ms 
                .delay(300, MILLISECONDS)
                .subscribe((entity, throwable) -> {
                    if (entity != null && !emitter.isDisposed()) {
                        emitter.onNext(entity);
                    }
                }));

        //network call:
        disposables.add(apiService.getEntity()
                .subscribeOn(schedulers.io())
                .onErrorResumeNext(throwable -> {
                    return Single.<Entity>error(throwable) //we will delay error here
                            .doOnError(throwable1 -> {
                                if (localEntity[0] != null) emitter.onNext(localEntity[0]); //api error, emit localEntity
                            })
                            .delay(200, MILLISECONDS, true); //to let it emit localEntity before emitting error
                })
                .subscribe(entity -> {
                    emitter.onNext(entity); 
                    emitter.onComplete(); //we got entity from api, so we can complete the stream
                }, emitter::onError));
    })
            .doOnDispose(disposables::clear)
            .subscribeOn(schedulers.io());
}

代码有点笨拙,我在这里在observable中创建了observables,我认为这是不好的。但是通过这种方式,我可以全局访问发射器,这使我能够以自己想要的方式控制主流(发射数据,成功,错误)。

是否有更好的方法来实现这一目标?我很想看看一些代码示例。谢谢!

3 个答案:

答案 0 :(得分:1)

可能是下面的代码可以完成这项工作。根据您的要求,我假设api和数据库处理的是Single<Entity>

private static final Object STOP = new Object();

public static void main(String[] args) {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(500, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));
    BehaviorSubject<Object> subject = BehaviorSubject.create();

    Observable.merge(
        apiService.getEntity()
                  .toObservable()
                  .doOnNext(t -> subject.onNext(STOP))
                  .doOnError(e -> subject.onNext(STOP))
                  .onErrorResumeNext(t ->
                                        Observable.concatDelayError(database.getEntity().toObservable(),
                                                                    Observable.error(t))),
        database.getEntity()
                .delay(300, MILLISECONDS)
                .toObservable()
                .takeUntil(subject)
    )
    .subscribe(System.out::println, 
               System.err::println);

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

由于以下情况,我无法删除Subject的使用:“如果数据库以某种方式比api慢,则无法发出数据”和“如果api调用将慢( >〜300ms),从数据库中发出数据,仍在等待来自api的数据。”否则,amb()运算符会很好用。

我希望这会有所帮助。

答案 1 :(得分:1)

另一个解决方案可能是这个(无主题):

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    database.getEntity()
            .toObservable()
            .groupJoin(apiService.getEntity()
                                 .toObservable()
                                 .onErrorResumeNext(
                                    err -> Observable.concatDelayError(database.getEntity().toObservable(),
                                                                       Observable.error(err))),
                       dbDuration -> Observable.timer(300, MILLISECONDS),
                       apiDuration -> Observable.never(),
                       (db, api) -> api.switchIfEmpty(Observable.just(db)))
            .flatMap(o -> o)
            .subscribe(System.out::println,
                       Throwable::printStackTrace,
                       () -> System.out.println("It's the end!"));

    Observable.timer(1, MINUTES) // just for blocking the main thread
              .toBlocking()
              .subscribe();
}

如果在300毫秒内(dbDuration -> timer(300, MILLISECONDS))没有从API服务发出任何消息,则发出来自数据库的实体(api.switchIfEmpty(db))。

如果api在300毫秒内发出某物,则该发出的仅是其Entityapi.switchIfEmpty(.))。

这似乎也如您所愿...

答案 2 :(得分:1)

另一个更好的解决方案:

public static void main(String[] args) throws InterruptedException {
    Database database = new Database(Single.just(new Entity("D1")));
    ApiService apiService = new ApiService(Single.just(new Entity("A1")));
    // ApiService apiService = new ApiService(Single.just(new Entity("A1")).delay(400, MILLISECONDS));
    // ApiService apiService = new ApiService(Single.error(new Exception("Error! Error!")));

    Observable<Entity> apiServiceWithDbAsBackup =
            apiService.getEntity()
                      .toObservable()
                      .onErrorResumeNext(err -> 
                            Observable.concatDelayError(database.getEntity().toObservable(), Observable.error(err)));

    Observable.amb(database.getEntity()
                           .toObservable()
                           .delay(300, MILLISECONDS)
                           .concatWith(apiServiceWithDbAsBackup),
                   apiServiceWithDbAsBackup)
              .subscribe(System.out::println,
                         Throwable::printStackTrace,
                         () -> System.out.println("It's the end!"));

我们使用amb()来延迟,以便观察到的数据库采取将要发出的第一个数据库。如果api服务出错,我们将从数据库中发出该项目。 似乎也可以按照您的意愿工作...