RxJava和缓存数据

时间:2014-11-14 21:30:07

标签: android caching aidl rx-java

我还是RxJava的新手,我在Android应用程序中使用它。我已经阅读了关于这个主题的公吨,但仍然觉得我错过了什么。

我有以下情况:

我有数据存储在系统中,可通过各种服务连接(AIDL)访问,我需要从该系统中检索数据(可能会发生1-n次异步调用)。 Rx帮助我简化了这段代码。但是,整个过程往往需要几秒钟(超过5秒+),因此我需要缓存此数据以加速本机应用程序。

此时的要求是:

  1. 初始订阅时,缓存将为空,因此我们必须等待所需的加载时间。没什么大不了。之后,应该缓存数据。

  2. 后续加载应该从缓存中提取数据,但是应该重新加载数据并且磁盘缓存应该在幕后。

  3. 问题:我有两个Observable - A和B. A包含从本地服务中提取数据的嵌套Observable(这里有吨)。 B更简单。 B只包含从磁盘缓存中提取数据的代码。

    需要解决: a)返回缓存项(如果已缓存)并继续重新加载磁盘缓存。 b)缓存为空,从系统加载数据,缓存并返回。后续调用将返回“a”。

    我有几个人推荐一些操作,如flatmap,merge甚至主题但由于某些原因我无法连接点。

    我该怎么做?

3 个答案:

答案 0 :(得分:28)

以下是有关如何执行此操作的几个选项。我会尝试尽可能地解释它们。这是餐巾码,我使用Java8风格的lambda语法,因为我很懒,而且它更漂亮。 :)

  1. 如果您可以将这些作为实例状态保存在内存中,那么主题(如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;
        }
    
    }
    
  2. 推迟观察。 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());
    }
    
  3. 使用concatWithtake。在这里,我们假设我们从磁盘缓存中获取Foo的方法是发出单个项目并完成,否则只是完成而不会发出,如果为空。

    public Observable<Foo> getFoo() {
        return FooHelper.getCachedFooObservable()
                .concatWith(FooHelper.getRealFooObservable())
                .take(1);
    }
    

    如果缓存的observable为空,那么该方法应该只尝试获取真实交易。

  4. 使用ambambWith。这可能是一个最疯狂的解决方案,但有趣的是指出。 amb基本上需要一对(或更多的重载)observables并等待,直到其中一个发出一个项目,然后它完全丢弃另一个observable并且只拿一个赢得比赛的那个。这将是有用的唯一方法是,如果创建新Foo的计算步骤比从磁盘获取它更快。在这种情况下,你可以这样做:

    public Observable<Foo> getFoo() {
        return Observable.amb(
                FooHelper.getCachedFooObservable(),
                FooHelper.getRealFooObservable());
    }
    
  5. 我更喜欢选项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()中返回该用户的本地列表。

如果您有任何问题,请与我们联系。