改造 OKHTTP 离线缓存不起作用

Retrofit OKHTTP Offline caching not working

我阅读了很多教程和 Whosebug 对我的问题的回答,但对我没有任何帮助!另外,它们中的大多数都是旧的,所以 OKHTTP 可能以某种方式更改了。

我只想为改造启用离线缓存

我正在使用 GET

我尝试只使用 offlineCacheInterceptor 作为拦截器,但我不断得到:

Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname

我尝试使用 offlineCacheInterceptor 作为拦截器 + provideCacheInterceptor() 作为网络拦截器的组合,但我一直得到:

504 Unsatisfiable Request (only-if-cached) and a null response.body()

我什至确保在所有地方都添加 .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/(一个拦截器,不工作!!)

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

(太混乱了!)


这是我的代码:

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;
    }

 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);
        }
    };

 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();
            }
        };
    }

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

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

2021 年 6 月(Retrofit 2.9.0 或 OKHTTP 3.14.9)完整解决方案(更新)

自 2018 年 10 月以来,同样的方法仍然有效

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

好的,所以使用 OKHTTP 或 Retrofit 的在线和离线缓存已经给 Whosebug 和其他论坛上的许多人带来了很多问题。互联网上有大量的误导性信息和无效代码示例。

所以,今天我将解释如何使用 Retrofit 和 OKHTTP 以清晰的步骤实现在线和离线缓存 + 如何测试并知道您是从缓存还是网络获取数据。

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

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

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

N.B:如果您想依赖服务器的缓存协议进行在线和离线缓存,那么请不要阅读这两个解决方案。只需阅读此 article。您只需要创建一个缓存对象并将其附加到 OKHTTPClient 对象即可。


解决方案 1:(更长,但您可以完全控制)

  • 第一步:(创建在线拦截器)

        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();
         }
     };
    
  • 第 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);
        }
      };
    
  • 第 3 步:(创建缓存对象)

     int cacheSize = 10 * 1024 * 1024; // 10 MB
     Cache cache = new Cache(context.getCacheDir(), cacheSize);
    
  • 第四步:(添加拦截器和缓存到一个OKHTTPClient对象)

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

              retrofit = new retrofit2.Retrofit.Builder()
             .baseUrl(BASE_URL)
             .addConverterFactory(GsonConverterFactory.create())
             .client(okHttpClient)
             .build();
    

完成!


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

使用OkCacheControl

  • 步骤 1(如上所示创建缓存对象)

  • 步骤 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();
    
  • 第 3 步:(如上所示将 OKHTTPClient 对象附加到 Retrofit)

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

        static OkCacheControl.NetworkMonitor networkMonitor=new 
        OkCacheControl.NetworkMonitor() {
        @Override
         public boolean isOnline() {
         return isInternetAvailable();
        }
       };
    

完成!


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

 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");
            }
        }

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

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


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