从RxJava中的HTTP请求缓存数据

时间:2016-10-21 18:30:45

标签: android caching rx-java kotlin race-condition

我有这个从REST API获取电影的FilmStore。

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())
    }

}

我还有另外一个装饰器,如果存在,则从缓存中返回数据:

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

}

问题是存在竞争条件。当应用程序启动时,两个片段会检索同一部电影,并且两者都是从API获取电影,因为当第二部分要获得电影时,它还没有被放入缓存中。

我怎么能同步?我试图放synchronized,但它不起作用。

1 个答案:

答案 0 :(得分:0)

一种可能性是缓存要多播。

可能有更好的做法,但这是一个粗略的想法(请原谅Java):

<强> 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. 如果进行后续提取,则会立即点击您的缓存并完成。

  3. 当源观察结果完成(并且所有订阅者都收到其OnComplete)时,refCount将导致ConnectableObservable创建的publish() 断开连接并清除up - 但请注意它仍然可用。下次Observable对象(由refCount()创建)订阅时,它将只是重新连接。