RxJava y datos almacenados en caché

Todavía soy relativamente nuevo en RxJava y lo estoy usando en una aplicación de Android. He leído una tonelada métrica sobre el tema, pero todavía siento que estoy perdiendo algo.

Tengo el siguiente escenario:

Tengo datos almacenados en el sistema al que se accede a través de varias conexiones de servicio (AIDL) y necesito recuperar datos de este sistema (puede ocurrir un número de llamadas asíncronas de 1 a n). Rx me ha ayudado mucho a simplificar este código. Sin embargo, todo este proceso tiende a tomar unos segundos (más de 5 segundos +) por lo tanto necesito almacenar estos datos en caché para acelerar la aplicación nativa.

Los requisitos en este punto son:

  1. La suscripción inicial, la memoria caché estará vacía, por lo tanto tenemos que esperar el tiempo requerido para cargar. No es gran cosa. Después de que los datos deben ser almacenados en caché.

  2. Las cargas subsiguientes deben extraer los datos del caché, pero entonces los datos deben ser recargados y el caché del disco debe estar detrás de las escenas.

El Problema: Tengo dos Observables – A y B. A contiene los observables anidados que extraen datos de los servicios locales (toneladas que suceden aquí). B es mucho más simple. B simplemente contiene el código para extraer los datos de caché de disco.

Necesidad de resolver: a) Devuelve un elemento en caché (si está en caché) y continúa recargando la caché de disco. B) El caché está vacío, carga los datos del sistema, lo almacena en caché y lo devuelve. Las llamadas posteriores vuelven a "a".

He tenido algunas personas recomiendan algunas operaciones como flatmap, combinar e incluso temas, pero por alguna razón estoy teniendo problemas para conectar los puntos.

¿Cómo puedo hacer esto?

3 Solutions collect form web for “RxJava y datos almacenados en caché”

Aquí hay un par de opciones sobre cómo hacer esto. Voy a tratar de explicar lo mejor que pueda a medida que avanzo. Esto es napkin-code, y estoy usando Java8-style lambda sintaxis porque soy perezoso y es más bonito. 🙂

  1. Un tema, como AsyncSubject , sería perfecto si pudiera mantener estos como estados de instancia en la memoria, aunque suena como si usted necesita para almacenar estos en el disco. Sin embargo, creo que este enfoque vale la pena mencionar sólo en caso de que sea capaz de hacerlo. Además, es sólo una técnica ingeniosa para saber. AsyncSubject es un observable que sólo emite el último valor publicado en él (un sujeto es a la vez un observador y un observable), y sólo comenzará a emitir después de onCompleted se ha llamado. Por lo tanto, cualquier cosa que se suscribe después de que completa recibirá el siguiente valor.

    En este caso, podría tener (en una clase de aplicación u otra instancia singleton en el nivel de la aplicación):

     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. Difiriendo el Observable. Observable.defer te permite esperar para crear un Observable hasta que se suscriba. Puede utilizar esto para permitir que la recuperación del caché de disco se ejecute en segundo plano y, a continuación, devolver la versión almacenada en caché o, si no en caché, realizar el trato real.

    Esta versión supone que su código getter, tanto la recuperación de caché como la no captura, están bloqueando las llamadas, no observables, y el diferimiento funciona en segundo plano. Por ejemplo:

     public Observable<Foo> getFoo() { Observable.defer(() -> { if (FooHelper.isFooCached()) { return Observable.just(FooHelper.getFooFromCacheBlocking()); } return Observable.just(FooHelper.createNewFooBlocking()); }).subscribeOn(Schedulers.io()); } 
  3. Utilizar concatWith y take . Aquí asumimos nuestro método para obtener el Foo de la caché de disco o bien emite un solo elemento y se completa o bien simplemente se completa sin emitir, si está vacía.

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

    Ese método sólo debería intentar obtener el trato real si el observador en caché terminó vacío.

  4. Use amb o ambWith . Esta es probablemente una de las soluciones más locas, pero divertido de señalar. amb básicamente toma un par (o más con las sobrecargas) observables y espera hasta que uno de ellos emite un ítem, luego descarta completamente el otro observable y solo toma el que ganó la carrera. La única manera que esto sería útil es si es posible que el paso de cálculo de crear un nuevo Foo sea más rápido que buscarlo desde el disco. En ese caso, podría hacer algo como esto:

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

Prefiero la Opción 3. En cuanto a ponerla en caché, podrías tener algo como esto en uno de los puntos de entrada (preferiblemente antes de que necesitemos el Foo, ya que como dijiste que es una operación de larga duración) Debe obtener la versión en caché, siempre y cuando haya terminado de escribir. El uso de AsyncSubject aquí también puede ayudar, para asegurarnos de que no activamos el trabajo varias veces mientras esperamos que se escriba. Los consumidores sólo obtendrían el resultado completo, pero de nuevo, eso sólo funciona si se puede mantener razonablemente en la memoria.

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

Tenga en cuenta que debe mantener alrededor de un único programador de subprocesos destinado a escritura (y lectura) de disco y utilizar .observeOn(foo) después de .subscribeOn(...) , o bien sincronizar el acceso a la caché de disco para evitar problemas de concurrencia.

Recientemente he publicado una biblioteca en Github para Android y Java, llamada RxCache , que satisface sus necesidades sobre el almacenamiento en caché de datos mediante observables.

RxCache implementa dos capas de caché -memoria y disco, y cuenta con varias anotaciones para configurar el comportamiento de cada proveedor.

Se recomienda usar Retrofit para los datos recuperados de las llamadas http. Utilizando la expresión lambda, puede formular la expresión de la siguiente manera:

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

Espero que lo encuentre interesante 🙂

Echa un vistazo al proyecto de abajo. Esta es mi opinión personal sobre las cosas y he utilizado este patrón en una serie de aplicaciones.

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

Echa un vistazo a PersistenceService. En lugar de golpear la base de datos (o MockService en el proyecto de ejemplo), podría simplemente tener una lista local de usuarios que se actualizan con el método save () y simplemente devolverlo en get ().

Hazme saber si tienes alguna pregunta.

  • Utilizar RxJava para encadenar la solicitud en un solo hilo
  • ¿Cómo ejecutar 2 consultas secuencialmente en un Android RxJava Observable?
  • RxJava - Encadenamiento de solicitudes y actualización de la interfaz de usuario
  • Uso de "skipWhile" combinado con "repeatWhen" en RxJava para implementar el sondeo de servidor
  • RXJava - hacer un observable pausable (con buffer y ventana, por ejemplo)
  • Solución global de errorHandling con RxJava sólo cuando onError no está implementado
  • ¿Existe algún patrón de desarrollo que pueda reemplazar un IntentService para las solicitudes de red?
  • Cómo manejar la rotación con Retrofit y RxJava / RxAndroid en Actividad?
  • Operadores de manipulación de errores de Retrofit 2 y RxJava
  • ¿Cómo recuperar cuerpo de respuesta con RxAndroid y Retrofit 2?
  • Cómo crear un objeto retrofit.Response durante las pruebas de unidad con Retrofit 2
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.