2016-10-21 5 views
0

REST API에서 영화를 가져 오는이 FilmStore를 보유하고 있습니다.RxJava에서 HTTP 요청의 데이터 캐시

class CacheableFilmsStore(private val origin: FilmsStore) : FilmsStore { 

    private val cache = ReactiveCache<Film>() 

    override fun get(filmId: String): Observable<Film> { 
     return cache[filmId].onErrorResumeNext({ 
      origin[filmId].doOnNext { film -> 
       cache.put(filmId, film) 
      } 
     }) 
    } 

} 

을 그리고 난 메모리 내 데이터를 관리하는이 클래스 ReactiveCache 있습니다 :

class ReactiveCache<T> { 

    private val cache = LruCache<String, T>(4 * 1024 * 1024) //4MiB 

    operator fun get(key: String): Observable<T> { 
     return Observable.create { subscriber -> 
      //synchronized (this) { It doesn't work 
       val value = cache[key] 
       if (value == null) { 
        subscriber.onError(KeyNotExistsException()) 
       } else { 
        subscriber.onNext(value) 
        subscriber.onCompleted() 
       } 
      //} 
     } 
    } 

    fun put(key: String, value: T) { 
     cache.put(key, value) 
    } 

} 

class ApiFilmsStore(private val tmdApi: TmdApi, private val converter: ApiFilmToFilmConverter) : FilmsStore { 

    override fun get(filmId: String): Observable<Film> { 
     return tmdApi.filmById(filmId).flatMap { apiFilm -> Observable.just(converter.convert(apiFilm)) } 
       .subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()) 
    } 

} 

I이 존재하는 경우 캐시에서 데이터를 반환하는 장식이 또 다른 하나가

경쟁 조건이 발생하는 문제가 있습니다. 두 개의 프래그먼트는 앱이 시작될 때 동일한 영화를 가져오고, 두 번째 프래그먼트는 API에서 필름을 가져 오는 중입니다. 두 번째 프래그먼트가 영화를 가져올 때 아직 캐시에 저장되지 않았기 때문입니다.

어떻게 동기화 할 수 있습니까? synchronized을 넣으려고했으나 작동하지 않습니다.

+0

'ReactiveCache' 또는이를 사용하는 ApiFilmsStore가 싱글 톤 (아마도 DI 프레임 워크를 통해)으로 사용됩니까? 다른 말로하면, 그것은 같은 대상입니까? 즉, 두 개의 별도 캐시가있을 수 있다고 아직 배제 했습니까? – drhr

+0

(또한, 사이드 노트 : 이것을 가지고있을 때 :'.flatMap {apiFilm -> Observable.just (converter.convert (apiFilm))}''.map {apiFilm -> converter.convert (apiFilm)}' 또는 w/lambda 메소드'.map (converter :: convert)') – drhr

+0

응답 해 주셔서 감사합니다. 예, 두 조각 모두 동일한 인스턴스를 사용하고 있습니다. Dagger2와 함께 CacheableFilmsStore를 사용하고 있습니다. map, nice point로 flatMap을 변경했습니다.) –

답변

0

캐시가 멀티 캐스팅 될 가능성이 있습니다.

는 (자바를 용서하십시오)이 그것을 할 수있는 더 좋은 수 있지만 여기에 거친 생각이 있습니다

CacheableFilmsStore :

// A map since you'll likely want to separate this by key. 
Map<String, Observable<T>> observableMap = new HashMap<>(); 

...get(...) { 
    if (observableMap.containsKey(key)) { 
     return observableMap.get(key); 
    } else { 
     Observable<T> observable = Observable.create(...) 
      .publish()  // distributes results to all active subscribers 
      .refCount(); // cleans up resources established by `publish()` 
     observableMap.put(key, observable); 
     return observable; 
    } 
} 

시나리오 :

  1. 하는 경우 이 키에 대한 결과가 아직 LruCache에 없으면 두 조각 모두 두 개가 아니라 한 API 호출 만 듣게됩니다.

  2. 다음 페치가 수행되면 캐시에 즉시 도달하여 완료됩니다.

소스 관찰 완료 (모든 가입자가 onComplete를 수신), 다음 refCountpublish()에 의해 생성 된 ConnectableObservable을 분리하고 정리하게됩니다 -하지만 여전히 사용할 수 있습니다. 다음 번에 Observable 객체 (refCount()으로 생성됨)가 구독되면 단순히 다시 연결됩니다.