Using Retrofit and Okhttp to implement network caching, updated in 2016.02.02
This article uses Retrofit 2.0.0-beta 2 and Okhttp 2.6.0 (the api writing changed after Okhttp 3.0)
- Cache for configuring Okhttp
- Configure the cache-control in the request header or unify the request headers for all requests
- Cloud cooperates with setting response header or writing interceptor to modify cache-control in response header
The final effect is: when you have a network, cache according to the time you need to cache for each interface (1 minute, 5 minutes, etc.), and then request again after time; when you have no network, read the cache.
Why do you want to cache, or what are the benefits?
Reduce server load, reduce latency and enhance user experience. Complex caching strategies will adopt different caching strategies according to users'current network conditions, such as improving the time of cache usage in the case of poor 2g network; different caching strategies are needed for unused applications, business requirements and interfaces; some need to ensure real-time data, so there is no caching, some can cache for 5 minutes, and so on. You have to give different solutions according to the timeliness of the data you need. Of course, you can all have the same caching strategy, depending on yourself.
1. Configure Cache in okhttp
OkHttpClient okHttpClient = new OkHttpClient();
File cacheFile = new File(context.getCacheDir(), "[Cache directory]");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
okHttpClient.setCache(cache);
2. Configure cache-control in request header
Cache related knowledge and parameter description, I am a link 1
Cache related knowledge and parameter description, I am a link 2
In Retrofit, we can configure it by @Headers, such as:
@Headers("Cache-Control: public, max-age=3600)
@GET("merchants/{shopId}/icon")
Observable<ShopIconEntity> getShopIcon(@Path("shopId") long shopId);
If there is no settings, it will not be cached when there is a network.
Or if all of your interfaces do not need to be cached or need to be cached at the same time when you have a network, then you don't need to configure Cache-Control for @Headers of each interface.
3. Cloud cooperates with setting response header or writing interceptor to modify cache-control in response header
At this point, the cache is already in your cache directory.
If there's a cache in the cloud, it's OK.
But it's likely that the cloud hasn't been processed, so the cache-control in the response header returned is no-cache. At this time, you still can't do the cache. You can use okhttp's write log interceptor to view the content of the response header.
Okhttp Interceptors instructions, I am a link
If the cloud is not easy to handle now, you can also make your own cache, that is, write the interceptor to modify the cache-control in the response header. I read out the cache-control in the request header and set it in the response header.
Set up interceptors:
The REWRITE_CACHE_CONTROL_INTERCEPTOR interceptor needs to set up both network Interceptors and interceptors (whether the OKHTTP 3.0 configuration is valid for me to test)
okHttpClient.interceptors().add(LoggingInterceptor);
okHttpClient.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
okHttpClient.interceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
Interceptors are as follows: Cloud Response Head Interceptors, which are used to configure caching policies
/**
* Cloud Response Header Interceptor for Configuring Caching Policies
* Dangerous interceptor that rewrites the server's cache-control header.
*/
private final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> {
Request request = chain.request();
if(!NetUtils.hasNetwork(context)){
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
Logger.t(TAG).w("no network");
}
Response originalResponse = chain.proceed(request);
if(NetUtils.hasNetwork(context)){
//When you have a network, you can read the configuration in @Headers on the interface, where you can set up unified settings.
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
}else{
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=2419200")
.removeHeader("Pragma")
.build();
}
};
Finally, log interceptors are posted.
private final Interceptor LoggingInterceptor = chain -> {
Request request = chain.request();
long t1 = System.nanoTime();
Logger.t(TAG).i(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Logger.t(TAG).i(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
};
The following tests show that the configuration of Cache-Control is the same in both request and response headers.
The max-stale setting is valid in the request header and invalid in the response header. (Because max-stale is the request header setting parameter, refer to the second link of cache-related knowledge above)
When max-stale and max-age are set at the same time, the cache failure time is calculated as the longest.
For max-age and max-stale, I did a test here:
Test results:
I set Cache-Control: public, max-age=60,max-stale=120 in the request header, and the Cache-Control in the response header is the same as the request header.
- Within one minute of the first request, the response headers are: Cache-Control: public, max-age=60,max-stale=120.
- Between 1 and 3 minutes, the response headers are: Cache-Control: public, max-age=60,max-stale=120.
Warning: 110 HttpURLConnection "Response is stale"
One more Warning can be found. - Three minutes: The data is re-requested, so the loop fails if there is no network at the re-requested node.
There is also an rxcache for caching that you can try.