okhttp and retrofit implement network caching

1. Examples in life

In the process of using the APP, similar to the news client, we will find that when there is no network, the homepage of the APP displays the news list, which means that the data at this time is cached data, so how can we achieve this effect? ?

2. Analysis requirements

  • Always visit the server to get the latest data when there is a network, and cache the data locally
  • When there is no network, go directly to the cache to find the data, and throw an exception if it is not found (we can catch the exception and give the user a friendly reminder if there is no network connection currently)

Then some people may wonder, do all the interfaces have such a process? For example, I do not want to cache the interfaces I have. If there is no network, I just want to access the server. At worst, a network connection timeout exception will be thrown. In fact, this can be achieved. yes, we'll talk about it later

3. How to implement it (note that only get request interface data can be cached here)

1. In fact, there are many articles on the Internet that implement caching, mainly using the interceptor of okhttp, and caching is realized by modifying the Cache-Control of the request header. 2.
Regarding the interceptor of okhttp, we used two methods addInterceptor and addNetworkInterceptor. The difference between the two is relatively official and not easy to understand. Of course, I am not particularly clear. Let me talk about my understanding.

  • addInterceptor priority addNetworkInterceptor execution
  • Both addInterceptor and addNetworkInterceptor will be executed when there is a network
  • addInterceptor executes without network, but addNetworkInterceptor does not execute
  • When there is no network but there is a cache, addInterceptor will execute, but addNetworkInterceptor will not execute

3. Code implementation

//先进行缓存目录的配置
File httpCacheDirectory = new File(context.getCacheDir(), "OkHttpCache");
int cacheSize = 10 * 1024 * 1024;//设置缓存文件大小为10M
Cache cache = new Cache(httpCacheDirectory, cacheSize);

//和addInterceptor配置同级
mOkHttpClient.cache(cache)
//requestInterceptor 这个拦截器通过addInterceptor添加到OkHttpClient中
Interceptor requestInterceptor = new Interceptor() {
    
    
            @NotNull
            @Override
            public Response intercept(@NotNull Chain chain) throws IOException {
    
    
                Request request = chain.request();
                //拦截请求头 判断哪些接口配置上了缓存(该配置是在retrofit上配置)
                String cacheControl =request.cacheControl().toString();
                //如果没有配置过 那么就是走正常的访问,这里直接return回去了,并没有对请求做处理
                if(TextUtils.isEmpty(cacheControl)) {
    
    
                    return chain.proceed(request);
                }
                //如果没有网络的情况下,并且配置了缓存,那么我们就设置强制读取缓存,没有读到就抛异常,但是并不会去服务器请求数据
                if (!isNetworkConnected(context)) {
    
    
                    int offlineCacheTime = 30;
                    request = request.newBuilder()
                    //only-if-cached 强制使用缓存
                    //max-stale 跟max-age同样用,只是这两个谁的值大,就用谁的值
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + offlineCacheTime)
                            .build();
                }
                return chain.proceed(request);
            }
        };
//responseInterceptor 这个拦截器通过addNetworkInterceptor添加到OkHttpClient中
Interceptor responseInterceptor = new Interceptor() {
    
    
            @NotNull
            @Override
            public Response intercept(@NotNull Chain chain) throws IOException {
    
    
                Request request = chain.request();
                Response response = chain.proceed(request);

                String cacheControl =request.cacheControl().toString();
                //同理获取请求头看看有没有配置过缓存,如果没有配置过就设置成无缓存
                if (TextUtils.isEmpty(cacheControl) || "no-store" .contains(cacheControl)) {
    
    
                    //响应头设置成无缓存
                    cacheControl = "no-store";
                }
                return response.newBuilder()
                //移除掉Pragma 避免服务器默认返回的Pragma对我们自己的缓存配置产生影响
                        .removeHeader("Pragma")
                        .header("Cache-Control", cacheControl)
                        .build();
            }
        };
//跟平时retorfit写法一样,只是添加了@Headers这个配置
@Headers("Cache-Control:public ,max-age=0")
@GET("user/account/getStudent/v1.0")
Observable<BaseResBean<UserBean>> getUserInfo(@QueryMap HashMap<String, Object> map);

Fourth, how to implement the cache

In fact, the above four parts of the code have already realized the requirements we want, so let’s analyze how to achieve it next.
1. When there is a network, how to make it so that only the interface configured with the cache will cache the data, and each time Will all requests go through the server interface?

On the retrofit interface, we configured @Headers("Cache-Control: public, max-age=0"). After adding this configuration, the cacheControl will be intercepted in the interceptor, and we can know which interface is by judging the cacheControl. For cache configuration, max-age=0 means that we need to cache the data locally, just because the validity period is set to 0, so that every request will go to the server instead of reading the cache.
If the interface of @Headers has not been configured, the cacheControl intercepted by responseInterceptor is empty, because we set cacheControl = "no-store", so that the data will not be cached locally.

2. How does the configured interface get the cached data without accessing the server when there is no network? Why does the unconfigured interface not get the cache but access the server?

In the request intercepted by requestInterceptor, you can know which interface is configured according to whether the cacheControl is empty. If it is configured and there is no network, then set only-if-cached to force the use of the cache, even if an exception is thrown. Nor will it go to the server. For those that have not been configured, we return directly, and the default configuration is still taken.

3. Why do I set max-age=0 in retrofit, that is to say, the validity period of the cache is 0. When the cached data is obtained under no network conditions, the data should be expired and unusable, because setting 0 means that It just doesn't work.

This is about the usage of max-age and max-stale. In fact, these two usages are the same. Max-age=0 is valid when there is a network, so the cache must be judged when there is a network. Invalid every time to access the server. However, in the case of no network, we set max-stale in requestInterceptor, that is, when max-age and max-stale exist at the same time, we take the maximum value, because we set max-stale to 30, then the cache validity period is 30s Then, the cached data is valid within 30s without the network.

Guess you like

Origin blog.csdn.net/qq_36356379/article/details/109804330