web-dev-qa-db-ja.com

RxJavaとキャッシュデータ

私はまだRxJavaにかなり慣れておらず、Androidアプリケーションで使用しています。この件についてメートルトンを読みましたが、それでも何かが足りないように感じます。

次のシナリオがあります。

さまざまなサービス接続(AIDL)を介してアクセスされるシステムにデータが保存されており、このシステムからデータを取得する必要があります(1〜n回の非同期呼び出しが発生する可能性があります)。 Rxは、このコードを単純化するのに大いに役立ちました。ただし、このプロセス全体には数秒(5秒以上)かかる傾向があるため、ネイティブアプリを高速化するには、このデータをキャッシュする必要があります。

この時点での要件は次のとおりです。

  1. 最初のサブスクリプションでは、キャッシュは空になるため、ロードするのに必要な時間を待つ必要があります。大きな問題ではない。その後、データをキャッシュする必要があります。

  2. 後続のロードではキャッシュからデータをプルする必要がありますが、データをリロードし、ディスクキャッシュをバックグラウンドで実行する必要があります。

問題:AとBの2つのObservableがあります。Aには、ローカルサービスからデータをプルするネストされたObservableが含まれています(ここではトンが発生しています)。 Bははるかに単純です。 Bには、ディスクキャッシュからデータをプルするためのコードが含まれているだけです。

解決する必要がある:a)キャッシュされたアイテムを返し(キャッシュされている場合)、ディスクキャッシュの再ロードを続行します。 b)キャッシュが空です。システムからデータをロードし、キャッシュして返します。その後の呼び出しは「a」に戻ります。

フラットマップ、マージ、さらにはサブジェクトなど、いくつかの操作を推奨する人が何人かいましたが、何らかの理由でドットの接続に問題があります。

これどうやってするの?

21
Donn Felker

これを行う方法に関するいくつかのオプションがあります。できる限り説明しようと思います。これはナプキンコードです。私は怠惰できれいなので、Java8スタイルのラムダ構文を使用しています。 :)

  1. AsyncSubjectのようなサブジェクトは、これらをインスタンス状態としてメモリに保持できれば完璧ですが、ディスクに保存する必要があるようです。ただし、このアプローチは、可能であれば言及する価値があると思います。また、知っておくのは気の利いたテクニックです。 AsyncSubjectは、公開された最後の値のみを発行するObservableであり(サブジェクトはオブザーバーと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の作成を待機できます。これを使用して、ディスクキャッシュフェッチをバックグラウンドで実行し、キャッシュされたバージョンを返すか、キャッシュにない場合は実際の取引を行うことができます。

    このバージョンでは、キャッシュフェッチと非キャッチ作成の両方のゲッターコードが、監視可能ではなく呼び出しをブロックしており、遅延がバックグラウンドで機能することを前提としています。例えば:

    _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);
    }
    _

    そのメソッドは、キャッシュされたオブザーバブルが空に終わった場合にのみ、実際の取引をフェッチしようとする必要があります。

  4. ambまたはambWithを使用します。これはおそらく最もクレイジーな解決策の1つですが、指摘するのは楽しいことです。 ambは基本的に、いくつか(またはオーバーロードの場合はそれ以上)のオブザーバブルを取得し、そのうちの1つがアイテムを放出するまで待機します。その後、もう1つのオブザーバブルを完全に破棄し、レースに勝ったものだけを取得します。これが役立つ唯一の方法は、新しいFooを作成する計算ステップが、ディスクからフェッチするよりも高速である可能性がある場合です。その場合、次のようなことができます。

    _public Observable<Foo> getFoo() {
        return Observable.amb(
                FooHelper.getCachedFooObservable(),
                FooHelper.getRealFooObservable());
    }
    _

オプション3の方がいいと思います。実際にキャッシュする限り、エントリポイントの1つにこのようなものを置くことができます(これは長時間実行される操作なので、Fooが必要になる前が望ましいです)後の消費者書き込みが終了している限り、キャッシュされたバージョンを取得する必要があります。ここでAsyncSubjectを使用すると、書き込みが行われるのを待っている間に作業が複数回トリガーされないようにするためにも役立つ場合があります。消費者は完成した結果しか得られませんが、これもまた、それがメモリ内に合理的に保持できる場合にのみ機能します。

_if (!FooHelper.isFooCached()) {
    getFoo()
        .subscribeOn(Schedulers.io())
        .subscribe((foo) -> FooHelper.cacheTheFoo(foo));
}
_

ディスクの書き込み(および読み取り)を目的としたシングルスレッドスケジューラを維持し、.observeOn(foo)の後に.subscribeOn(...)を使用するか、同時実行の問題を防ぐためにディスクキャッシュへのアクセスを同期する必要があることに注意してください。 。

28
lopar

最近、GithubでAndroidおよびJavaと呼ばれる RxCache というライブラリを公開しました。これは、オブザーバブルを使用したデータのキャッシュに関するニーズを満たしています。

RxCacheは、メモリとディスクの2つのキャッシュ層を実装し、すべてのプロバイダーの動作を構成するために、いくつかの注釈を付けてカウントします。

Http呼び出しから取得したデータには、 Retrofit とともに使用することを強くお勧めします。ラムダ式を使用すると、次のように式を定式化できます。

    rxCache.getUser(retrofit.getUser(id), () -> true).flatmap(user -> user);

おもしろいと思います:)

2

以下のプロジェクトをご覧ください。これは私の個人的な見解であり、このパターンを多くのアプリで使用しています。

https://github.com/zsiegel/rxandroid-architecture-sample

PersistenceServiceを見てください。データベース(またはサンプルプロジェクトのMockService)にアクセスするのではなく、save()メソッドで更新されたユーザーのローカルリストを取得し、それをget()に返すだけで済みます。

ご不明な点がございましたらお知らせください。

0
Zac Siegel