因此,我试图将我的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调用。
答案 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
,以确保也不会缓存任何包含错误(例如网络故障)的可观察对象。