How does Android OkHttp cache work?
Life without caching!
Caching is an interesting topic in software engineering.
Imagine that Facebook app keeps loading the new feeds all the time we refresh or Google Photos app takes 10s for creating all the photo thumbnails every time we enter. 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 scope down the problem into how does OkHttp works.


Some 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: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching. 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. What to do to make it works?
Do nothing! Yeah. It automatically parses the cache related header from server and store the response into the cache dir. Next time we send the request, it will automatically append the header for us.
For example, if the server gives:
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"
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 just works.
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:


More general, if you always want to use local cache when ever 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 add as an application interceptor. If we add it as a network interceptor, it will has no chance to get trigger.
4. Is it a good idea to use OkHttp for data storing? Just use ‘FORCE_CACHE’ and we will alway get the stored data?
It’s a bad idea.
Those are fundamentally different and should be treated differently. Data should always available, cache may not.
More details doc is here: https://github.com/square/okhttp/wiki/Recipes
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 and that list will stay unchanged for quite long. So instead of giving the response every time, the server can just say max-age=3600 and all the subsequence request in the next 1 hour (3600 seconds)can just use the cached data.
6. How does server decide whether client can use its cached data or not?
There can be a few ways to solve this but basically the client will send out something like timestamp or tag of the last request, so that the server can check if there is some data has changed in that period of time or not. And incase there is nothing changed, server can just give a special code without sending the whole same response again.
One example is the Gmail app, because the client just don’t know there can be new email every time the user refresh or not, so it can’t just use the cached data. What it does is alway send out the last tag to tell server what was the last time the user checked their inbox. Then if there is no new email, server just tell client to use its cache to display same email list.