How does Android OkHttp cache work?

Life without caching!

Caching is an interesting topic in software engineering.

Let’s imagine that Facebook app keeps loading the new feeds all the time we refresh or Google Photos app takes 10s to rebuild all the photo thumbnails every time the user open. That experience will kill the user’s happiness!

We are all good engineers and we really care about user experience. In this topic, let’s just narrow down the caching topic into how does OkHttp work.

OkHttp caching mechanism

Quick questions and answers.

We will not cover everything about OkHttp cache because it just follows the Http caching mechanism, if you have no idea about that, read it first: . No rush :)

1. How to enable the cache?

int cacheSize = 10 * 1024 * 1024; // 10MB
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(new Cache(context.getCacheDir(), cacheSize)
....

2. How can I make it works?

Do nothing! Yeah. It automatically parses the cache-related header from the server and stores the response into the cache dir. Next time, when we send the request, it will automatically append the corresponding header for us.

For example, if the server responses:

Date:Wed, 29 Mar 2017 10:54:09 GMT
ETag:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"
Last-Modified:Wed, 29 Mar 2017 03:25:27 GMT

then next time, OkHttp will set the request header to:

If-Modified-Since:Wed, 29 Mar 2017 03:25:27 GMT
If-None-Match:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"

More info:

3. How to make it works offline?

If the server gives max-age which tells OkHttp that it can cache the response and use it offline then it will just work.

If max-age is not available or it expires but you still want to use local data, you can fore use it by set request like this:

new Request.Builder().cacheControl(CacheControl.FORCE_CACHE)
...;

Then the flow will become:

Force cache

More general, if you always want to use local cache whenever there is no Internet:

public class ForceCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (!NetworkUtils.internetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}

return chain.proceed(builder.build());
}
}
okHttpClient.addInterceptor(new ForceCacheInterceptor());

Note that this interceptor must be added as an application interceptor. If we add it as a network interceptor, it will have no chance to be triggered.

4. Is it a good idea to use OkHttp for data storing? Just use ‘FORCE_CACHE’ and we will always get the stored data?

It’s a bad idea.

Those are fundamentally different and should be treated differently​. Data should always available, while cache may not.

E.g. login token should be stored, while new feeds should be cached.

Stop for a while, enjoy coffee and think about that…

More details doc is here:

5. How to decide whether we need the server to validate the cache?

This is done using max-age which tells how long the response can be cached.

For example, you are refreshing your Facebook setting page which has a list of settings. So, that list should stay unchanged for quite sometimes. So instead of return the same response every time, the server can just say max-age=3600 and for all the subsequence requests in the next 1 hour (3600 seconds), the client can just use the local cached data.

6. How does the server decide whether the client can use its cached data or not?

There can be done in a few ways. Basically:

  • The client will send out something like timestamp or Etag of the last request
  • The server can then check if there is some data has changed in during that period of time or not.
  • If nothing has changed, the server can just give a special code (304 -not modified) without sending the whole same response again. And we can save some bandwidth of in this case.

One example is the Gmail app, because the client has no way to know if there are new emails every time the user refreshes, so it can’t just simply use the cached data. What it always does is to send out the last Etag to tell the server what was the last time the user checked their inbox. Then if there is no new email, the server just tells the client to use its cache to display the same email list.