Andorid rxJava:如何从缓存中获取数据并同时在后台更新它?

时间:2016-11-25 03:09:25

标签: android rx-java

我刚开始学习Android的 rxJava ,并希望实现常见用例:

  • 从缓存请求数据并向用户显示
  • 从网络上请求数据
  • 服务器更新存储中的数据并自动将其显示给用户

传统上,在最佳方案中,使用 CursorLoader 从缓存中获取数据,在单独的线程中运行Web请求,并通过内容提供商将数据保存到磁盘,< strong>内容提供程序会自动通知侦听器和 CursorLoader 自动更新UI。

在rxJava中我可以通过运行两个不同的观察者来实现,正如你在下面的代码中看到的那样,但是我找不到如何将这两个调用结合到一个达到我的目标的方法。谷歌搜索显示这个线程,但看起来它只是从缓存中获取数据或从Web服务器获取数据,但不同时做RxJava and Cached Data

代码段:

@Override
public Observable<SavingsGoals> getCachedSavingsGoal() {
    return observableGoal.getSavingsGoals()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}

@Override
public Observable<SavingsGoals> getRecentSavingsGoal() {
    return api.getSavingsGoals()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}

    model.getCachedSavingsGoal().subscribe(new Observer<SavingsGoals>() {
        @Override
        public void onCompleted() {
            // no op
        }

        @Override
        public void onError(Throwable e) {
            Log.e(App.TAG, "Failed to consume cached data");
            view.showError();
        }

        @Override
        public void onNext(SavingsGoals savingsGoals) {
            Log.d(App.TAG, "Show the next item");
            if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
                view.showData(savingsGoals.getSavingsGoals());
            } else {
                view.showError();
            }
        }
    });

    model.getRecentSavingsGoal().subscribe(new Observer<SavingsGoals>() {
        @Override
        public void onCompleted() {
            // no op
        }

        @Override
        public void onError(Throwable e) {
            Log.e(App.TAG, "Failed to consume data from the web", e);
            view.showError();
        }

        @Override
        public void onNext(SavingsGoals savingsGoals) {
            if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
                view.showData(savingsGoals.getSavingsGoals());
            } else {
                view.showError();
            }
        }
    });

此外,当前方法的问题之一是缓存和Web数据不被允许随后运行。当过时的数据最新并且覆盖最近的网络时,它是可能的。

为了解决这个问题,我通过timestamp实现了Observer合并过滤:它从缓存中获取数据,将其传递给下一个观察者,如果缓存过时,则激活对Web的新调用 - 通过带有时间戳的过滤解决线程竞争的情况。但是,这种方法的问题我不能从这个Observable返回缓存数据 - 我需要等待两个请求完成它们的工作。

代码段。

    @Override
public Observable<Timestamped<SavingsGoals>> getSavingGoals() {
    return observableGoal
            .getTimestampedSavingsGoals()
            .subscribeOn(Schedulers.io())
            .flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
                @Override
                public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> cachedData) {
                    Log.d(App.FLOW, "getTimestampedSavingsGoals");
                    return getGoalsFromBothSources()
                            .filter(filterResponse(cachedData));
                }
            })
            .subscribeOn(AndroidSchedulers.mainThread());
}

private Func1<Timestamped<SavingsGoals>, Boolean> filterResponse(Timestamped<SavingsGoals> cachedData) {
    return new Func1<Timestamped<SavingsGoals>, Boolean>() {
        @Override
        public Boolean call(Timestamped<SavingsGoals> savingsGoals) {
            return savingsGoals != null
                    && cachedData != null
                    && cachedData.getTimestampMillis() < savingsGoals.getTimestampMillis()
                    && savingsGoals.getValue().getSavingsGoals().size() != 0;
        }
    };
}

private Observable<Timestamped<SavingsGoals>> getGoalsFromBothSources() {
    Log.d(App.FLOW, "getGoalsFromBothSources:explicit");
    return Observable.merge(
            observableGoal.getTimestampedSavingsGoals().subscribeOn(Schedulers.io()),
            api.getSavingsGoals()
                    .timestamp()
                    .flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
                        @Override
                        public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> savingsGoals) {
                            Log.d(App.FLOW, "getGoalsFromBothSources:implicit");
                            return observableGoal.saveAllWithTimestamp(savingsGoals.getTimestampMillis(), savingsGoals.getValue().getSavingsGoals());
                        }
                    }))
                    .subscribeOn(Schedulers.io());
}

您是否知道在一个观察者中执行此操作的方法?

潜在解决方案:

@Override
public Observable<SavingsGoals> getSavingGoals() {
    return api.getSavingsGoals()
            .publish(network ->
                    Observable.mergeDelayError(
                            observableGoal.getSavingsGoals().takeUntil(network),
                            network.flatMap(new Func1<SavingsGoals, Observable<SavingsGoals>>() {
                                @Override
                                public Observable<SavingsGoals> call(SavingsGoals savingsGoals) {
                                    return observableGoal.saveAll(savingsGoals.getSavingsGoals());
                                }
                            })
                    )
            )
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}
  • 抱歉,IDE中的热替换隐藏了这种方法的问题:如果网络不可用,首先完成缓存线程的第一个,错误将终止整个合并(由mergeDelayError解决),第二个是在缓存是空并返回来自Web请求的第一个数据将不会返回订阅者。正如你所看到的,我的方法在保存和传统合并之后返回Observable,因为我在我的代码中正确处理了这种情况,但由于某些原因而不能使用。问题仍然存在。

2 个答案:

答案 0 :(得分:4)

对于第一个问题:您可以使用doOnNext方法保存网络结果的结果,它看起来像这样

def split(iterable, delimiter):
    lst = []
    for item in iterable:
        if item == delimiter:
            yield lst
            lst = []
        else:
            lst.append(item)
    if lst:
        yield lst

现在要结合Storage和Online的两个结果,最好的方法是结合发布和合并。我建议观看this talk。 代码看起来像这样

public Observable<NetworkResponse> getDataFromNetwork(
      final Request request) {
    return networkCall.doOnNext(networkResponse -> saveToStorage(networkResponse);
  }

为什么要使用发布并合并我的问题? publish方法使回调中的响应可访问。 takeUntil意味着您将从存储中获取数据但是由于某种原因您将停止它,在完成访问存储数据之前完成网络调用。这样,即使在从存储中获取旧数据之前已经完成,也可以确保始终显示来自网络的新数据。

最后但并非最不重要的是,在您的订阅者OnNext中,只需将项目添加到列表中即可。 (list.clear和list.addAll)或类似的函数或在你的情况下view.showData()

编辑:当网络出现错误时,呼叫会中断,最后添加onErrorResumeNext。

  public Observable<Response> getData(final Request request) {

    return dataService.getDataFromNetwork(request)
        .publish(networkResponse ->  Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)));
  }

答案 1 :(得分:0)

我建议&#34;听&#34;仅限本地数据,并在API响应到来时刷新它。 让我们说获取本地数据,例如:

@Nonnull
public Observable<SomeData> getSomeDataObservable() {
    return Observable
            .defer(new Func0<Observable<SomeData>>() {
                @Override
                public Observable<SomeData> call() {
                    return Observable.just(getSomeData());
                }
            });
}

因此,您需要添加每次更新本地数据时都会发出的PublishSubject(refreshSubject):

@Nonnull
public Observable<SomeData> getSomeDataObservableRefreshable() {
    return refreshSubject.startWith((Object)null).switchMap(new Func1() {
        public Observable<T> call(Object o) {
            return getSomeDataObservable();
        }
    }     
}

现在您只需订阅getSomeDataObservableRefreshable(),每当数据来自API时,您都会更新它并生成refreshSubject .onNext(new Object())

另外,我建议您查看rx-java-extensions lib,它有很多&#34;酷工具&#34;对于RxAndroid。例如,您的问题的解决方案是:

@Nonnull
public Observable<SomeData> getSomeDataObservable() {
    return Observable
            .defer(new Func0<Observable<SomeData>>() {
                @Override
                public Observable<SomeData> call() {
                    return Observable.just(getSomeData());
                }
            })
            .compose(MoreOperators.<SomeData>refresh(refreshSubject));
}