18
votes

I'm trying to cache some responses in my app using Retrofit 2.0, but I'm missing something.

I installed a caching file as follows:

private static File httpCacheDir;
private static Cache cache;
try {
    httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
    httpCacheDir.setReadable(true);
    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
    HttpResponseCache.install(httpCacheDir, httpCacheSize);
    cache = new Cache(httpCacheDir, httpCacheSize);
    Log.i("HTTP Caching", "HTTP response cache installation success");
} catch (IOException e) {
    Log.i("HTTP Caching", "HTTP response cache installation failed:" + e);
}

public static Cache getCache() {
        return cache;
    }

which creates a file in /data/user/0/<PackageNmae>/cache/http , then prepared a network interceptor as follows:

public class CachingControlInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // Add Cache Control only for GET methods
        if (request.method().equals("GET")) {
            if (ConnectivityUtil.checkConnectivity(getContext())) {
                // 1 day
                request.newBuilder()
                    .header("Cache-Control", "only-if-cached")
                    .build();
            } else {
                // 4 weeks stale
                request.newBuilder()
                    .header("Cache-Control", "public, max-stale=2419200")
                    .build();
            }
        }

        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
            .header("Cache-Control", "max-age=86400")
            .build();
    }
}

my Retrofit and OkHttpClient instance:

OkHttpClient client = new OkHttpClient();
client.setCache(getCache());
client.interceptors().add(new MainInterceptor());
client.interceptors().add(new LoggingInceptor());
client.networkInterceptors().add(new CachingControlInterceptor());
Retrofit restAdapter = new Retrofit.Builder()
        .client(client)
        .baseUrl(Constants.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

productsService = restAdapter.create(ProductsService.class);

where ProductsService.class contains:

@Headers("Cache-Control: max-age=86400")
@GET("categories/")
Call<PagedResponse<Category>> listCategories();

and

Call<PagedResponse<Category>> call = getRestClient().getProductsService().listCategories();
call.enqueue(new GenericCallback<PagedResponse<Category>>() {
      // whatever 
      // GenericCallback<T> implements Callback<T>
   }
});

The question here is: How to make it access cached responses when device being offline?

Header of backend response are:

Allow → GET, HEAD, OPTIONS
Cache-Control → max-age=86400, must-revalidate
Connection → keep-alive
Content-Encoding → gzip
Content-Language → en
Content-Type → application/json; charset=utf-8
Date → Thu, 17 Dec 2015 09:42:49 GMT
Server → nginx
Transfer-Encoding → chunked
Vary → Accept-Encoding, Cookie, Accept-Language
X-Frame-Options → SAMEORIGIN
x-content-type-options → nosniff
x-xss-protection → 1; mode=block
5

5 Answers

15
votes

Finally I get the answer.

Network Interceptor should be as follow:

public class CachingControlInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // Add Cache Control only for GET methods
        if (request.method().equals("GET")) {
            if (ConnectivityUtil.checkConnectivity(YaootaApplication.getContext())) {
                // 1 day
               request = request.newBuilder()
                        .header("Cache-Control", "only-if-cached")
                        .build();
            } else {
                // 4 weeks stale
               request = request.newBuilder()
                        .header("Cache-Control", "public, max-stale=2419200")
                        .build();
            }
        }

        Response originalResponse = chain.proceed(request);
        return originalResponse.newBuilder()
            .header("Cache-Control", "max-age=600")
            .build();
    }
}

then installing cache file is that simple

long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(new File(context.getCacheDir(), "http"), SIZE_OF_CACHE);
OkHttpClient client = new OkHttpClient();
client.cache(cache);
client.networkInterceptors().add(new CachingControlInterceptor());
8
votes

In your CachingControlInterceptor, you create new requests, but never actually use them. You call newBuilder and ignore the result, so the header modification is never actually sent any where. Try assigning those values to request and then instead of calling proceed on chain.request() call it on request.

public class CachingControlInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // Add Cache Control only for GET methods
        if (request.method().equals("GET")) {
            if (ConnectivityUtil.checkConnectivity(getContext())) {
                // 1 day
                request = request.newBuilder()
                    .header("Cache-Control", "only-if-cached")
                    .build();
            } else {
                // 4 weeks stale
                request = request.newBuilder()
                    .header("Cache-Control", "public, max-stale=2419200")
                    .build();
            }
        }

        Response originalResponse = chain.proceed(request);
        return originalResponse.newBuilder()
            .header("Cache-Control", "max-age=600")
            .build();
    }
}
3
votes

you can also try:

public class CachingInterceptor implements Interceptor {
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        request = new Request.Builder()
                .cacheControl(new CacheControl.Builder()
                        .maxAge(1, TimeUnit.DAYS)
                        .minFresh(4, TimeUnit.HOURS)
                        .maxStale(8, TimeUnit.HOURS)
                        .build())
                .url(request.url())
                .build();


        return chain.proceed(request);
    }
}
0
votes

I finally discovered the solution that worked for me in Retrofit 2.x and OkHttp 3.x

I had to implement two Interceptors, one of them is responsible to rewrite the Request headers and the other to rewrite the Response headers.

  1. First, make sure you delete any old cache. (root explorer /data/data/com.yourapp/cache

  2. Instantiate the client builder:

    OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(new RewriteRequestInterceptor())
        .addNetworkInterceptor(new RewriteResponseCacheControlInterceptor())
    
  3. Create the RewriteRequestInterceptor

    public class RewriteRequestInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            int maxStale = 60 * 60 * 24 * 5;
            Request request;
            if (NetworkUtils.isNetworkAvailable()) {
                request = chain.request();
            } else {
                request = chain.request().newBuilder().header("Cache-Control", "max-stale=" + maxStale).build();
            }
            return chain.proceed(request);
        }
    }
    
  4. Create the RewriteResponseCacheControlInterceptor

    public class RewriteResponseCacheControlInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            int maxStale = 60 * 60 * 24 * 5;
            Response originalResponse = chain.proceed(chain.request());
            return originalResponse.newBuilder().header("Cache-Control", "public, max-age=120, max-stale=" + maxStale).build();
        }
    }
    

It's important to make sure you add the ResponseCacheControlInterceptor as a Network Interceptor, and the RewriteRequestInterceptor as a Interceptor (as I did in the 2nd step).

-4
votes
    OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();