我还是RxJava的新手,我在Android应用程序中使用它。我已经阅读了关于这个主题的公吨,但仍然觉得我错过了什么。
我有以下情况:
我有数据存储在系统中,可通过各种服务连接(AIDL)访问,我需要从该系统中检索数据(可能会发生1-n次异步调用)。 Rx帮助我简化了这段代码。但是,整个过程往往需要几秒钟(超过5秒+),因此我需要缓存此数据以加速本机应用程序。
此时的要求是:
初始订阅时,缓存将为空,因此我们必须等待所需的加载时间。没什么大不了。之后,应该缓存数据。
后续加载应该从缓存中提取数据,但是应该重新加载数据并且磁盘缓存应该在幕后。
问题:我有两个Observable - A和B. A包含从本地服务中提取数据的嵌套Observable(这里有吨)。 B更简单。 B只包含从磁盘缓存中提取数据的代码。
需要解决: a)返回缓存项(如果已缓存)并继续重新加载磁盘缓存。 b)缓存为空,从系统加载数据,缓存并返回。后续调用将返回“a”。
我有几个人推荐一些操作,如flatmap,merge甚至主题但由于某些原因我无法连接点。
我该怎么做?
答案 0 :(得分:28)
以下是有关如何执行此操作的几个选项。我会尝试尽可能地解释它们。这是餐巾码,我使用Java8风格的lambda语法,因为我很懒,而且它更漂亮。 :)
如果您可以将这些作为实例状态保存在内存中,那么主题(如AsyncSubject
)将是完美的,尽管听起来您需要将这些存储到磁盘中。但是,我认为这种方法值得一提,万一你能够。此外,它只是一个非常好的技术知道。
AsyncSubject
是一个Observable,只发出发布给它的LAST值(Subject既是Observer又是Observable),只会在调用onCompleted
后才开始发出。因此,在完成之后订阅的任何内容都将获得下一个值。
在这种情况下,您可以(在应用程序级别的应用程序类或其他单例实例中):
public class MyApplication extends Application {
private final AsyncSubject<Foo> foo = AsyncSubject.create();
/** Asynchronously gets foo and stores it in the subject. */
public void fetchFooAsync() {
// Gets the observable that does all the heavy lifting.
// It should emit one item and then complete.
FooHelper.getTheFooObservable().subscribe(foo);
}
/** Provides the foo for any consumers who need a foo. */
public Observable<Foo> getFoo() {
return foo;
}
}
推迟观察。 Observable.defer允许您等待创建Observable,直到订阅为止。您可以使用它来允许磁盘缓存提取在后台运行,然后返回缓存版本,如果不在缓存中,则返回真实交易。
此版本假设您的getter代码(缓存提取和非catch捕获创建)都是阻塞调用,而不是可观察对象,并且延迟在后台运行。例如:
public Observable<Foo> getFoo() {
Observable.defer(() -> {
if (FooHelper.isFooCached()) {
return Observable.just(FooHelper.getFooFromCacheBlocking());
}
return Observable.just(FooHelper.createNewFooBlocking());
}).subscribeOn(Schedulers.io());
}
使用concatWith
和take
。在这里,我们假设我们从磁盘缓存中获取Foo的方法是发出单个项目并完成,否则只是完成而不会发出,如果为空。
public Observable<Foo> getFoo() {
return FooHelper.getCachedFooObservable()
.concatWith(FooHelper.getRealFooObservable())
.take(1);
}
如果缓存的observable为空,那么该方法应该只尝试获取真实交易。
使用amb
或ambWith
。这可能是一个最疯狂的解决方案,但有趣的是指出。 amb
基本上需要一对(或更多的重载)observables并等待,直到其中一个发出一个项目,然后它完全丢弃另一个observable并且只拿一个赢得比赛的那个。这将是有用的唯一方法是,如果创建新Foo的计算步骤比从磁盘获取它更快。在这种情况下,你可以这样做:
public Observable<Foo> getFoo() {
return Observable.amb(
FooHelper.getCachedFooObservable(),
FooHelper.getRealFooObservable());
}
我更喜欢选项3.就实际缓存而言,你可以在其中一个入口点有这样的东西(最好在我们需要Foo之前,因为你说这是一个长期运行的操作)只要完成编写,以后的消费者就应该获得缓存版本。在这里使用AsyncSubject
也可能有所帮助,以确保我们在等待它写入时不会多次触发工作。消费者只会得到完整的结果,但同样,只有在内存中可以合理地保存时才会有效。
if (!FooHelper.isFooCached()) {
getFoo()
.subscribeOn(Schedulers.io())
.subscribe((foo) -> FooHelper.cacheTheFoo(foo));
}
请注意,您应该保留用于磁盘写入(和读取)的单个线程调度程序,并在.observeOn(foo)
之后使用.subscribeOn(...)
,或者以其他方式同步对磁盘高速缓存的访问以防止并发问题。
答案 1 :(得分:2)
我最近在Github上发布了一个名为RxCache的Github库,它可以满足您使用observable缓存数据的需求。
RxCache实现了两个缓存层-memory和disk,它使用多个注释进行计数,以便配置每个提供程序的行为。
强烈建议与Retrofit一起使用从http调用中检索的数据。使用lambda表达式,您可以按如下方式表达表达式:
rxCache.getUser(retrofit.getUser(id), () -> true).flatmap(user -> user);
我希望你会发现它很有趣:)
答案 2 :(得分:0)
看看下面的项目。这是我个人对事物的看法,我在许多应用程序中都使用过这种模式。
https://github.com/zsiegel/rxandroid-architecture-sample
看一下PersistenceService。您可以简单地使用save()方法更新数据库(或示例项目中的MockService),而只需在get()中返回该用户的本地列表。
如果您有任何问题,请与我们联系。