改造 OKHTTP 离线缓存不起作用

Posted

技术标签:

【中文标题】改造 OKHTTP 离线缓存不起作用【英文标题】:Retrofit OKHTTP Offline caching not working 【发布时间】:2019-03-25 18:24:27 【问题描述】:

我阅读了数十篇教程和 *** 对我的问题的回答,但对我没有任何帮助!此外,它们中的大多数都是旧的,所以 OKHTTP 可能以某种方式发生了变化。

我只想为 Retrofit 启用 离线缓存

我正在使用 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 Unsatisfiable Request (only-if-cached))

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

https://***.com/a/48295397/8086424(不工作) 无法解析主机“jsonplaceholder.typicode.com”:没有与主机名关联的地址

Can Retrofit with OKHttp use cache data when offline(太混乱了!)


这是我的代码:

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:

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

【问题讨论】:

嗨,伙计!看来这是您需要的解决方案 ***.com/a/48208801/4374863 它解决了“没有与主机名关联的地址” - 前段时间对我来说是错误 【参考方案1】:

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

自 2018 年 10 月以来,相同的方法仍在起作用

十月。 2018 (Retrofit 2.4 or OKHTTP 3.11) 完整解决方案

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

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

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

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

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

注意:如果您想依赖服务器的缓存协议进行在线和离线缓存,请不要阅读这两种解决方案。只需阅读此article。您只需要创建一个缓存对象并将其附加到 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();
     
 ;

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

第三步:(创建缓存对象)

 int cacheSize = 10 * 1024 * 1024; // 10 MB
 Cache cache = new Cache(context.getCacheDir(), cacheSize);

第 4 步:(向 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库

第一步(如上图创建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();

第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。

【讨论】:

您好,我有一个疑问,它一直在进行网络 API 调用?所以每次我在加载内容时得到白色的空白 @Mena 我仍然收到 -- 504 Unsatisfiable Request (only-if-cached) @droidev 您是否使用了与我提到的相同版本的 OKHTTP 和 Retrofit?并且在解决方案 2 中使用库是否会产生相同的结果? @Mena,它成功了,我们有一个 Vary 标头。删除可变标头后,它完美无缺。非常感谢:) 一条额外的建议,确保您使用的是 okhttp 库的绝对最新版本。我在最新的 Android 上落后了几个小版本,它在将缓存条目写入文件系统后立即删除它们

以上是关于改造 OKHTTP 离线缓存不起作用的主要内容,如果未能解决你的问题,请参考以下文章

离线时使用 OKHttp 进行改造如何使用缓存数据

缓存在 UIWebView 中不起作用

缓存在UIWebView中不起作用

Kotlin 中的 Okhttp3,String.mediaType() 不起作用

为啥当我解析我的响应 okHttp 时它不起作用

离线时可以使用 OKHttp 进行改造吗?