Recently, I was writing an information flow project, and the whole architecture was implemented based on MVP + Retrofit + Rxjava. Since I just used Rxjava + Retrofit, I didn’t have a deep understanding ofit, so I used conventional thinking to design data caching at the beginning.

The cache processing that comes to mind:

  • Use sharedpreferences
  • Use the SqLite database

But there’s a problem:

  • One of the things that makes Retrofit + RxJava powerful is the ability to directly convert returned JSON data into our JavaBean objects for direct manipulation.
  • In the normal way, we just cache the JSON data, which is still converted to objects by GSON at the time of operation.
  • In that case, we’re not really showing the power of Retrofit, so I thought it would be nice if Retrofit could do caching.

As a joke, there is very little information on Retrofit and RxJava on the web, and most ofit is repetitive or just a snippet, but some solutions have been found.

So why do you want to cache?

One article said:

Reduce server load and latency to improve user experience. Complex cache policies will adopt different cache policies according to the current network conditions of users. For example, when the 2G network is poor, the cache usage time is increased. Different applications, business requirements, and interfaces need different caching strategies. Some need to ensure real-time data, so no caching, some you can cache for 5 minutes, and so on. Depending on the availability of the data you need, you need to make different proposals. Of course, you can also use the same caching strategy for all of them, depending on yourself.

Retrofit+OkHttp caching mechanism

  • Create a response folder under data/data/< package name >/cache after responding to the request to keep the cache data.
  • In this way, we can automatically read the cached data if there is no network at the time of request.
  • This can also be done, even if we don’t have the Internet, by reopening the App to view previously displayed content.
  • If there is a network, obtain it from the network and save it in the cache. If there is no network, obtain it from the cache.

Cache implementation

  1. Start by enabling OkHttp caching

    After Retrofit2.0, the underlying Retrofit automatically relied on OkHttp, so we didn’t have to rely on OkHttp again

    File httpCacheDirectory = new File(MyApp.mContext.getCacheDir(), "responses");
    int cacheSize = 10 * 1024 * 1024; 
    Cache cache = new Cache(httpCacheDirectory, cacheSize);
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
            .cache(cache).build();Copy the code

    This step is to set the cache path and cache size, where addInterceptor is our second step.

  2. Set up the OkHttp interceptor

    Mainly intercepting operations, including controlling the maximum health of the cache, and controlling the expiration time of the cache

    Both operations are performed in the Interceptor

    • CacheControl controls cached data

      CacheControl.Builder cacheBuilder = new CacheControl.Builder();
       cacheBuilder.maxAge(0, TimeUnit.SECONDS);
       cacheBuilder.maxStale(365,TimeUnit.DAYS);
       CacheControl cacheControl = cacheBuilder.build();Copy the code
    • Setting up interceptors

      Request request = chain.request(); if(! StateUtils.isNetworkAvailable(MyApp.mContext)){ request = request.newBuilder() .cacheControl(cacheControl) .build(); Response originalResponse = chain.proceed(request); if (StateUtils.isNetworkAvailable(MyApp.mContext)) { int maxAge = 60; return originalResponse.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public ,max-age=" + maxAge) .build(); } else { int maxStale = 60 * 60 * 24 * 28; return originalResponse.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build();Copy the code

    As you can see, the two Settings above have the same content, what is the difference?

    One article explained it this way:

    If.maxage (0, timeunit.seconds) is set to a time longer than the interceptor, it will not work. If it is set to a time shorter than the interceptor, it will take precedence. .maxstale (365, timeunit.days) is set to stale time. I think the okthHP cache is divided into two parts: one is to save traffic by directly retrieving the cache when requesting, and the other is to directly retrieve the cache when entering the application next time.

    All the code

    In this way, we can use the same Retrofit request method directly, and either the latest data or the cached data can be converted into the objects we need to use directly.

    weiBoApiRetrofit() { File httpCacheDirectory = new File(MyApp.mContext.getCacheDir(), "responses"); int cacheSize = 10 * 1024 * 1024; Cache cache = new Cache(httpCacheDirectory, cacheSize); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR) .cache(cache).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); WeiBoApiService = retrofit.create(WeiBoApi.class); Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> { CacheControl.Builder cacheBuilder = new CacheControl.Builder(); cacheBuilder.maxAge(0, TimeUnit.SECONDS); cacheBuilder.maxStale(365,TimeUnit.DAYS); CacheControl cacheControl = cacheBuilder.build(); Request request = chain.request(); if(! StateUtils.isNetworkAvailable(MyApp.mContext)){ request = request.newBuilder() .cacheControl(cacheControl) .build(); Response originalResponse = chain.proceed(request); if (StateUtils.isNetworkAvailable(MyApp.mContext)) { int maxAge = 0; return originalResponse.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public ,max-age=" + maxAge) .build(); } else { int maxStale = 60 * 60 * 24 * 28; return originalResponse.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build();Copy the code

    Issues for attention

    • The cache is restored after each network request, so Retrofit automatically requests web server data after the cache expiration date, when it checks it’s out of cache, and takes care of the follow-up actions, like flipping toast to tell the user that the network is gone.
    • Caching data also needs to be downloaded from the network, so in the case of a bad network, it may not be able to cache immediately, which is also where I was confused before: clearly the cache has been set up, why sometimes there is a cache, sometimes there is not? — I’m really worried about my IQ.

    Related articles

    Because I just got into contact with Retrofit + RxJava, so if there are any bad or wrong things or unclear expressions, please point them out in time and I will modify them in time. PS: But the above code can be cached after my test, I hope it can help you.