【问题标题】:Retrofit + OkHTTP - response cache not workingRetrofit + OkHTTP - 响应缓存不起作用
【发布时间】:2015-07-05 15:13:37
【问题描述】:

我知道有很多类似的问题,但我都阅读了它们,但没有一个真正有帮助。

所以,这是我的问题:

我正在使用改造 + okhttp 从 API 获取一些数据,我想缓存它们。不幸的是,我没有对 API 服务器的管理员访问权限,因此我无法修改服务器返回的标头。 (目前服务器返回Cache-control: private)

所以我决定使用 okhttp 标头欺骗来插入适当的缓存标头。可悲的是,无论我做什么,缓存似乎都不起作用。

我这样初始化 api 服务:

int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheFile = new File(context.getCacheDir(), "thumbs");
final Cache cache = new Cache(cacheFile, cacheSize);

OkHttpClient client = new OkHttpClient();
client.setCache(cache);
client.interceptors().add(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
                .removeHeader("Access-Control-Allow-Origin")
                .removeHeader("Vary")
                .removeHeader("Age")
                .removeHeader("Via")
                .removeHeader("C3-Request")
                .removeHeader("C3-Domain")
                .removeHeader("C3-Date")
                .removeHeader("C3-Hostname")
                .removeHeader("C3-Cache-Control")
                .removeHeader("X-Varnish-back")
                .removeHeader("X-Varnish")
                .removeHeader("X-Cache")
                .removeHeader("X-Cache-Hit")
                .removeHeader("X-Varnish-front")
                .removeHeader("Connection")
                .removeHeader("Accept-Ranges")
                .removeHeader("Transfer-Encoding")
                .header("Cache-Control", "public, max-age=60")
              //.header("Expires", "Mon, 27 Apr 2015 08:15:14 GMT")
                .build();
    }
});

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(API_ROOT)
    .setLogLevel(RestAdapter.LogLevel.HEADERS_AND_ARGS)
    .setClient(new OkClient(client))
    .setConverter(new SimpleXMLConverter(false))
    .setRequestInterceptor(new RequestInterceptor() {
        @Override
        public void intercept(RequestFacade request) {
            if (Network.isConnected(context)) {
                int maxAge = 60; // read from cache for 2 minutes
                request.addHeader("Cache-Control", "public, max-age=" + maxAge);
            } else {
                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                request.addHeader("Cache-Control",
                    "public, only-if-cached, max-stale=" + maxStale);
            }
        }
    })
    .build();
api = restAdapter.create(ApiService.class);

当然,没有必要删除所有这些标头,但我想让响应尽可能干净,以排除这些额外标头的一些干扰。

如您所见,我还尝试欺骗 Expires 和 Date 标头(我尝试删除它们,设置它们以使它们之间恰好存在 max-age 差异,并将 Expires 设置为远在未来)。我还尝试了各种缓存控制值,但没有运气。

我确保 cacheFile 存在、isDirectory 并且可由应用程序写入。

这些是改造直接记录的请求和响应标头:

Request:
Cache-Control: public, max-age=60
---> END HTTP (no body)

Response:
Date: Mon, 27 Apr 2015 08:41:10 GMT
Server: Apache/2.2.22 (Ubuntu)
Expires: Mon, 27 Apr 2015 08:46:10 GMT
Content-Type: text/xml; charset=UTF-8
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1430124070000
OkHttp-Received-Millis: 1430124070040
Cache-Control: public, max-age=60
<--- END HTTP (-1-byte body)
<--- BODY: ...

最后,发生了一件奇怪的事情:在某些时候,缓存工作了几分钟。我得到了合理的命中数,甚至离线请求也返回了缓存值。 (在使用此处发布的确切设置时发生)但是当我重新启动应用程序时,一切都恢复了“正常”(恒定命中计数 0)。

如果有人知道这里可能是什么问题,我会很高兴得到任何帮助:)

【问题讨论】:

    标签: android caching retrofit cache-control okhttp


    【解决方案1】:

    使用networkInterceptors() 代替interceptors()。结合您删除与缓存有些相关的任何标头的策略将起作用。这是简短的答案。

    当您使用拦截器更改标头时,它不会在调用 CacheStrategy.isCacheable() 之前进行任何调整。看看 CacheStrategy 和 CacheControl 类,看看 OKHttp 如何处理与缓存相关的标头是值得的。在http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 上做 ctrl+f "cache" 也是值得的

    我不确定 networkInterceptors() 和 interceptors() 文档是否不清楚或者是否存在错误。一旦我进一步研究,我会更新这个答案。

    【讨论】:

    • 天哪,我太傻了。非常感谢。我一定有某种暂时性失明或其他什么 - 我从未意识到该代码中有 inteceptors() 而不是 networkInterceptors() ...
    【解决方案2】:

    这里还要补充一点,除了 Brendan Weinstein 的回答只是为了确认 OkHttp3 缓存不适用于发布请求。

    【讨论】:

      【解决方案3】:

      一整天后,我发现我的离线缓存无法正常工作,因为我在 API 类型中使用了POST。当我将其更改为GET 的那一刻,它就起作用了!

      @GET("/ws/audioInactive.php")
      Call<List<GetAudioEntity>> getAudios();
      

      我的整个改造课。

      import android.util.Log;
      
      import com.google.gson.Gson;
      import com.google.gson.GsonBuilder;
      import com.limnet.iatia.App;
      import com.limnet.iatia.netio.entity.registration.APIInterfaceProviderIMPL;
      
      import java.io.File;
      import java.io.IOException;
      import java.util.concurrent.TimeUnit;
      
      import okhttp3.Cache;
      import okhttp3.CacheControl;
      import okhttp3.Interceptor;
      import okhttp3.OkHttpClient;
      import okhttp3.Request;
      import okhttp3.Response;
      import okhttp3.logging.HttpLoggingInterceptor;
      import retrofit2.Retrofit;
      import retrofit2.converter.gson.GsonConverterFactory;
      
      public class RHTRetroClient {
      
          public static final String BASE_URL = "https://abc.pro";
          private static Retrofit retrofit = null;
          private static RHTRetroClient mInstance;
      
          private static final long cacheSize = 10 * 1024 * 1024; // 10 MB
          public static final String HEADER_CACHE_CONTROL = "Cache-Control";
          public static final String HEADER_PRAGMA = "Pragma";
      
      
          private RHTRetroClient() {
              Gson gson = new GsonBuilder()
                      .setLenient()
                      .create();
              HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
      
              Cache cache = new Cache(new File(App.getAppContext().getCacheDir(), "soundbites"),cacheSize);
      
              OkHttpClient client = new OkHttpClient.Builder()
                      .cache(cache)
                      .addInterceptor(httpLoggingInterceptor()) // used if network off OR on
                      .addNetworkInterceptor(networkInterceptor()) // only used when network is on
                      .addInterceptor(offlineInterceptor())
                      .build();
      
              interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
      
              retrofit = new Retrofit.Builder()
                      .baseUrl(BASE_URL)
                      .client(client)
                      .addConverterFactory(GsonConverterFactory.create(gson))
                      .build();
          }
      
          /**
           * This interceptor will be called both if the network is available and if the network is not available
           *
           * @return
           */
          private static Interceptor offlineInterceptor() {
              return new Interceptor() {
                  @Override
                  public Response intercept(Chain chain) throws IOException {
                      Log.d("rht", "offline interceptor: called.");
                      Request request = chain.request();
      
                      // prevent caching when network is on. For that we use the "networkInterceptor"
                      if (!App.hasNetwork()) {
                          CacheControl cacheControl = new CacheControl.Builder()
                                  .maxStale(7, TimeUnit.DAYS)
                                  .build();
      
                          request = request.newBuilder()
                                  .removeHeader(HEADER_PRAGMA)
                                  .removeHeader(HEADER_CACHE_CONTROL)
                                  .cacheControl(cacheControl)
                                  .build();
                      }
      
                      return chain.proceed(request);
                  }
              };
          }
      
          /**
           * This interceptor will be called ONLY if the network is available
           *
           * @return
           */
          private static Interceptor networkInterceptor() {
              return new Interceptor() {
                  @Override
                  public Response intercept(Chain chain) throws IOException {
                      Log.d("rht", "network interceptor: called.");
      
                      Response response = chain.proceed(chain.request());
      
                      CacheControl cacheControl = new CacheControl.Builder()
                              .maxAge(5, TimeUnit.SECONDS)
                              .build();
      
                      return response.newBuilder()
                              .removeHeader(HEADER_PRAGMA)
                              .removeHeader(HEADER_CACHE_CONTROL)
                              .header(HEADER_CACHE_CONTROL, cacheControl.toString())
                              .build();
                  }
              };
          }
      
          private static HttpLoggingInterceptor httpLoggingInterceptor() {
              HttpLoggingInterceptor httpLoggingInterceptor =
                      new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                          @Override
                          public void log(String message) {
                              Log.d("rht", "log: http log: " + message);
                          }
                      });
              httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
              return httpLoggingInterceptor;
          }
      
          public static synchronized RHTRetroClient getInstance() {
              if (mInstance == null) {
                  mInstance = new RHTRetroClient();
              }
              return mInstance;
          }
      
          public APIInterfaceProviderIMPL getAPIInterfaceProvider() {
              return retrofit.create(APIInterfaceProviderIMPL.class);
          }
      
      }
      

      【讨论】:

        【解决方案4】:

        检查您的响应中是否有 Pragma 标头。如果存在Pragma: no-cache 标头,则使用max-age 进行缓存将不起作用。

        如果确实有 Pragma 标头,请通过在您的 Interceptor 中执行以下操作将其删除:

        override fun intercept(chain: Interceptor.Chain): Response {
            val cacheControl = CacheControl.Builder()
                .maxAge(1, TimeUnit.MINUTES)
                .build()
        
            return originalResponse.newBuilder()
                .header("Cache-Control", cacheControl.toString())
                .removeHeader("Pragma") // Caching doesnt work if this header is not removed
                .build()
        }
        

        【讨论】:

          猜你喜欢
          • 2016-02-18
          • 2018-07-17
          • 1970-01-01
          • 2016-09-23
          • 2018-06-19
          • 1970-01-01
          • 2019-03-25
          • 2015-06-01
          • 2023-03-16
          相关资源
          最近更新 更多