1
votes

So I am trying to cache HTTP responses (using OkHtttp + retrofit, rxjava for multithreading) using Guava cache. It currently looks something like:

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

    apiClient = retrofit.create(Api.class);

    CacheLoader<Integer, HttpResponse> cacheLoader = new CacheLoader<Integer, HttpResponse>() {
        @Override
        public Response load( Integer key ) throws InterruptedException, ExecutionException {
            return apiClient.getHttpResponse(key)
                    .subscribeOn(Schedulers.io())
                    .blockingFirst();
        }
    };

    responseCache = CacheBuilder.newBuilder()
            .concurrencyLevel(8)
            .maximumSize( 10 )
            .build(cacheLoader);

The apiClient returns Observable, and it is subscribed within the CacheLoader's load method. I have also set concurrencyLevel(8) but it doesn't seem to allow 'loading' and 'reading' at the same time.

I think the blockingFirst() call probably block the thread so I can't make concurrent requests from the cache i.e. whenever cache is loading a new http response, cache can't be read.

I don't know how to make it asynchronous, any help is appreciated :)

1
is there any reason you are not using the caching provided by OkHttp? - LordRaydenMK
I believe Okhttp's cache is saved on disk, I am trying to use an in-memory cache.. - creampiedonut

1 Answers

0
votes

Using Guava, you can still make asynchronous non-blocking calls and cache HTTP responses, but you will have to manually call cache.put() after the HTTP response is received. So you can define your LoadingCache like the below.

private LoadingCache<String, ServerResponse> cache =
    CacheBuilder.newBuilder()
        .maximumSize(50)
        .expireAfterWrite(24, TimeUnit.HOURS)
        .build(
            new CacheLoader<String, ServerResponse>() {
                public ServerResponse load(String id) {
                    return null; // or empty ServerResponse
                }
            });

And in your in your calling method:

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

apiClient = retrofit.create(Api.class);

try {
    ServerResponse cachedResponse = cache.get(someUniqueKey);
    if (cachedResponse != null) {
        // return cachedResponse here
        return;
    }
    doNormalHttpRequest()
} catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) {
    doNormalHttpRequest();
}

void doNormalHttpRequest() {
    apiClient.request(body).enqueue(
        new retrofit2.Callback<ServerResponse>() {
            @Override
            public void onResponse(Call<ServerResponse> call,
                Response<ServerResponse> response) {
                if (response.isSuccessful()) {
                    cache.put(someUniqueKey, response.body();
                }
            }

            @Override
            public void onFailure(Call<ServerResponse> call, Throwable {}
    });
}

As an added bonus, this can cache POST requests as well since retrofit only caches GET requests using OkHttp's caching implementation.