改进OKHTTP离线缓存无法正常工作

Men*_*ena 4 android caching retrofit retrofit2 okhttp3

我阅读了几十个教程和Stackoverflow对我的问题的答案,但没有什么对我有用!此外,他们中的大多数都是旧的,所以OKHTTP可能会以某种方式改变.

我想要的只是为Retrofit 启用离线缓存.

我正在使用GET

我曾尝试仅用offlineCacheInterceptor作拦截器,但我一直得到:

Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname
Run Code Online (Sandbox Code Playgroud)

我尝试使用offlineCacheInterceptorInterceptor + provideCacheInterceptor()作为NetworkInterceptor的组合,但我一直得到:

504 Unsatisfiable Request (only-if-cached) and a null response.body()
Run Code Online (Sandbox Code Playgroud)

我甚至确保.removeHeader("Pragma")到处添加!


我尝试了所有这些链接:

https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html (一个拦截器,不工作!!)

https://medium.com/mindorks/caching-with-retrofit-store-responses-offline-71439ed32fda(一个拦截器,不工作!)

https://caster.io/lessons/retrofit-2-offline-cache(单独在线+离线缓存,不工作)

https://www.journaldev.com/23297/android-retrofit-okhttp-offline-caching(不工作,504不满意请求(仅限缓存))

http://mikescamell.com/gotcha-when-offline-caching-with-okhttp3/(一个拦截器,不工作!!)

/sf/answers/3380677821/(不工作)无法解析主机"jsonplaceholder.typicode.com":没有与主机名关联的地址

使用OKHttp进行改造可以在离线时使用缓存数据 (令人困惑!)


这是我的代码:

public static Retrofit getRetrofitInstance(Context context) {
        if (retrofit == null) {
            c = context;
            int cacheSize = 10 * 1024 * 1024; // 10 MB
            Cache cache = new Cache(context.getCacheDir(), cacheSize);
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(provideHttpLoggingInterceptor())
                    .addInterceptor(offlineCacheInterceptor)
                    .addNetworkInterceptor(provideCacheInterceptor())
                    .cache(cache)
                    .build();
            //////////////////////////
            retrofit = new retrofit2.Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpClient)
                    .build();
        }
        return retrofit;
    }
Run Code Online (Sandbox Code Playgroud)
 public static Interceptor offlineCacheInterceptor = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Log.e("bbbb", "bbbb");
            if (!checkInternetAvailability()) {
                Log.e("aaaaa", "aaaaaa");
                CacheControl cacheControl = new CacheControl.Builder()
                        .maxStale(30, TimeUnit.DAYS)
                        .build();

                request = request.newBuilder()
                        .cacheControl(cacheControl)
                        .removeHeader("Pragma")
                        .build();
            }
            return chain.proceed(request);
        }
    };
Run Code Online (Sandbox Code Playgroud)
 public static Interceptor provideCacheInterceptor() {
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response response = chain.proceed(chain.request());

                // re-write response header to force use of cache
                CacheControl cacheControl = new CacheControl.Builder()
                        .maxAge(2, TimeUnit.MINUTES)
                        .build();

                return response.newBuilder()
                        .header(CACHE_CONTROL, cacheControl.toString())
                        .removeHeader("Pragma")
                        .build();
            }
        };
    }
Run Code Online (Sandbox Code Playgroud)

我正在使用jsonplaceholder.typicode.com/photos返回:

content-type: application/json; charset=utf-8
    date: Sun, 21 Oct 2018 14:26:41 GMT
    set-cookie: __cfduid=d9e935012d2f789245b1e2599a41e47511540132001; expires=Mon, 21-Oct-19 14:26:41 GMT; path=/; domain=.typicode.com; HttpOnly
    x-powered-by: Express
    vary: Origin, Accept-Encoding
    access-control-allow-credentials: true
    expires: Sun, 21 Oct 2018 18:26:41 GMT
    x-content-type-options: nosniff
    etag: W/"105970-HCYFejK2YCxztz8++2rHnutkPOQ"
    via: 1.1 vegur
    cf-cache-status: REVALIDATED
    expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    server: cloudflare
    cf-ray: 46d466910cab3d77-MXP
    Cache-Control: public, max-age=60
Run Code Online (Sandbox Code Playgroud)

Men*_*ena 22

2018年10月(Retrofit 2.4或OKHTTP 3.11)完整的解决方案

好吧,所以使用OKHTTP或Retrofit的在线和离线缓存已经在stackoverflow和其他论坛上给很多人带来了很多问题.互联网上有大量误导性信息和非工作代码样本.

那么,今天我将解释如何使用Retrofit和OKHTTP实现在线和离线缓存,并执行明确的步骤+如何测试并了解您是从缓存还是网络获取数据.

如果您正在获得504 Unsatisfiable Request (only-if-cached)OR,Unable to resolve host "HOST": No address associated with hostname那么您可以使用以下任何解决方案.

在开始之前,您必须始终记住:

  • 确保您使用的是GET请求,而不是POST!
  • 始终确保添加.removeHeader("Pragma")如下所示(这使您可以覆盖服务器的缓存协议)
  • 在测试时避免使用HttpLoggingInterceptor,它可能会在开始时引起一些混乱.如果需要,最后启用它.
  • 如果您想使用拦截器进行探索,请务必始终从设备中删除您的应用并在每次更改代码时重新安装.否则,当旧的缓存数据仍在设备上时更改代码将导致许多混淆和误导性扣除!
  • 向OKHTTPClient对象添加拦截器的顺序很重要!

注意:如果您想依赖服务器的缓存协议进行在线和离线缓存,请不要阅读2个解决方案.刚看完这篇文章.您所需要的只是创建一个缓存对象并将其附加到OKHTTPClient对象.


解决方案1 ​​:(更长,但你有完全控制权)

  • 第1步:(创建onlineInterceptor)

       static Interceptor onlineInterceptor = new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            okhttp3.Response response = chain.proceed(chain.request());
            int maxAge = 60; // read from cache for 60 seconds even if there is internet connection
            return response.newBuilder()
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .removeHeader("Pragma")
                    .build();
        }
    };
    
    Run Code Online (Sandbox Code Playgroud)
  • 第2步:(创建脱机拦截器)(仅当您希望脱机时访问缓存)

       static Interceptor offlineInterceptor= new Interceptor() {
       @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (!isInternetAvailable()) {
            int maxStale = 60 * 60 * 24 * 30; // Offline cache available for 30 days 
            request = request.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .removeHeader("Pragma")
                    .build();
          }
          return chain.proceed(request);
       }
     };
    
    Run Code Online (Sandbox Code Playgroud)
  • 第3步:(创建缓存对象)

    int cacheSize = 10 * 1024 * 1024; // 10 MB
    Cache cache = new Cache(context.getCacheDir(), cacheSize);
    
    Run Code Online (Sandbox Code Playgroud)
  • 第4步:(添加拦截器和缓存到OKHTTPClient对象)

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
     // .addInterceptor(provideHttpLoggingInterceptor()) // For HTTP request & Response data logging
        .addInterceptor(OFFLINE_INTERCEPTOR)
        .addNetworkInterceptor(ONLINE_INTERCEPTOR)
        .cache(cache)
        .build();
    
    Run Code Online (Sandbox Code Playgroud)
  • 第5步:(如果您使用的是Retrofit,请将OKHTTPClient对象添加到其中)

             retrofit = new retrofit2.Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build();
    
    Run Code Online (Sandbox Code Playgroud)

DONE!


解决方案2 :(只需使用库为您完成所有这些!但处理限制)

使用OkCacheControl

  • 第1步(创建Cache对象,如上图所示)
  • 第2步(创建一个OKHTTPClient对象)

         OkHttpClient okHttpClient = OkCacheControl.on(new OkHttpClient.Builder())
         .overrideServerCachePolicy(1, MINUTES)
         .forceCacheWhenOffline(networkMonitor)
         .apply() // return to the OkHttpClient.Builder instance
       //.addInterceptor(provideHttpLoggingInterceptor())
         .cache(cache)
         .build();
    
    Run Code Online (Sandbox Code Playgroud)
  • 第3步:(将OKHTTPClient对象附加到Retrofit,如上所示)

  • 第4步:(创建NetworkMonitor对象)

       static OkCacheControl.NetworkMonitor networkMonitor=new 
       OkCacheControl.NetworkMonitor() {
       @Override
        public boolean isOnline() {
        return isInternetAvailable();
       }
      };
    
    Run Code Online (Sandbox Code Playgroud)

DONE!


测试: 为了了解您的设备是从网络还是从缓存获取数据,只需将以下代码添加到您onResponse的Retrofit方法中.

 public void onResponse(Call<List<RetroPhoto>> call, Response<List<RetroPhoto>> response) {
            if (response.raw().cacheResponse() != null) {
                Log.e("Network", "response came from cache");
            }

            if (response.raw().networkResponse() != null) {
                Log.e("Network", "response came from server");
            }
        }
Run Code Online (Sandbox Code Playgroud)

如果设备正在使用网络,您将收到"来自服务器的响应".

如果设备使用缓存,您将获得以上两种响应!有关此内容的更多信息,请阅读本文.


有关使用OKHTTP拦截器的更多信息,请转到此页面.

  • @Mena,它有效,我们有一个 Vary 标头。删除变化标头后,它完美地工作了。多谢 :) (4认同)
  • 非常详细的答案,谢谢,它解决了我的问题! (2认同)