通过RxJava缓存API响应

时间:2018-07-09 20:39:17

标签: java rx-java rx-java2

因此,我试图将我的http响应缓存到ConcurrentHashMap中。我已经设置了缓存类和Api客户端类以返回Observables,如下所示:

public class UserCache {

    private static ConcurrentHashMap<Integer, User> cache = new ConcurrentHashMap<>();

    public Observable<User> get(Integer key) {
        return Observable.create(observableEmitter -> {
            if(cache.contains(key)) observableEmitter.onNext(cache.get(key));
            observableEmitter.onComplete();
        });
    }

    public void update(Integer key, User user) {
        cache.putIfAbsent(key, user);
    }

    public boolean contains(Integer key) {
        return cache.contains(key);
    }
}

ApiClient

public class ApiClient {

    private UserApi api;
    private static ApiClient apiClient;

    private ApiClient() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://jsonplaceholder.typicode.com")
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        api = retrofit.create(UserApi.class);
    }

    public Observable<User> get(int id) {
        return api.getUser(id);
    }

    public static ApiClient getInstance() {
        if(apiClient == null) apiClient = new ApiClient();
        return apiClient;
    }
}

在App类中

public class App {

    ApiClient apiSource = ApiClient.getInstance();

    UserCache userCache = new UserCache();

    public Observable<User> get(Integer key) {
        return Observable.concat(userCache.get(key), apiSource.get(key))
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.computation())
                .doOnNext(user -> {
                    userCache.update(user.id, user);
                })
                .subscribeOn(Schedulers.io());
    }

    public static void main(String[] args) throws InterruptedException {
        App app = new App();
        app.get(1).subscribe(System.out::println);
        Thread.sleep(3000);
        app.get(1).subscribe(System.out::println);
        Thread.sleep(1000);
    }
}

我对.concat的理解是,如果第一个可观察的(缓存)不发出任何东西,那么第二个可观察的(api客户端)将开始发出。但是我不知道为什么doOnNext(user -> userCache.update(user.id, user))不更新缓存,因此当我检索相同的键时,会再次执行另一个api调用。

1 个答案:

答案 0 :(得分:0)

我不确定为什么您的doOnNext不发光,但是如果您使用RX,则有一种方法需要较少的代码并消除竞争条件。在您给出的示例中,如果我们在一个空的缓存上两次调用该方法,它将进行两次网络调用,而最后一个将覆盖第一个。这是我的首选方法,可以防止这种情况的发生,并且所需的代码更少:

private final ConcurrentMap<Integer, Observable<User>> userCache = Maps.newConcurrentMap();

public Observable<User> getUser(int id) {
    return userCache.computeIfAbsent(id, theId -> {
        ConnectableObservable<User> cachedObservable = getUserFromApi(id)
                .replay();
        cachedObservable.connect();
        return cachedObservable;
    }).doOnError(err -> userCache.remove(id));
}

如您所见,我存储了缓存的可观察对象。这样,如果在第二个调用仍在飞行中时进行第二个调用,则它们将得到相同的结果,并且仅被缓存一次。之后的所有调用都直接从可缓存的可观察对象获取。

但是,我们不想(可能)缓存错误,因此我添加了doOnError,以确保也不会缓存任何包含错误(例如网络故障)的可观察对象。