2016-08-01 141 views
1

在我的Android应用程序中,我使用了包含okhttp的retrofit 2。 我用下面的代码来设置缓存Retrofit 2:缓存响应失效后缓存不起作用

OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder(); 

File httpCacheDirectory = new File(MyApplication.getInstance().getCacheDir(), "responses"); 
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); 
httpBuilder.cache(cache); 


OkHttpClient httpClient = httpBuilder.build(); 

Retrofit.Builder builder = new Retrofit.Builder(). 
     baseUrl(ApplicationConstants.BASE_API_URL). 
     client(httpClient). 
     addConverterFactory(GsonConverterFactory.create(gson)); 

缓存头被从服务器端的响应集。它缓存文件就好了,并从缓存中显示出来,直到缓存文件过期。

问题是当缓存过期时,它不能再被缓存。这不再缓存或替换旧的缓存文件。我认为它应该自动清理旧的无效缓存文件,并用新的响应替换并缓存它。

如何清除无效响应并缓存新的有效响应。

我一直在尝试近两天,没有解决方案。实际上对我来说,似乎我正在按照文档做所有事情。还有别的可能是错的吗?

下面是okhttp

D/OkHttp: Connection: keep-alive 
D/OkHttp: Content-Type: application/json; charset=utf-8 
D/OkHttp: Vary: Accept-Encoding 
D/OkHttp: Transfer-Encoding: chunked 
D/OkHttp: Server: Cowboy 
D/OkHttp: X-Frame-Options: SAMEORIGIN 
D/OkHttp: X-Xss-Protection: 1; mode=block 
D/OkHttp: X-Content-Type-Options: nosniff 
D/OkHttp: Date: Tue, 02 Aug 2016 17:39:23 GMT 
D/OkHttp: X-Pagination: {"total":34,"total_pages":2,"first_page":true,"last_page":false,"prev_page":null,"next_page":2,"out_of_range":false} 
D/OkHttp: Cache-Control: max-age=10800, public, no-transform 
D/OkHttp: Etag: W/"4dcf69c9456102fd57666a1dff0eec3a" 
D/OkHttp: X-Request-Id: 1fb917ac-7f77-4c99-8a3b-20d56af9d441 
D/OkHttp: X-Runtime: 0.081711 
D/OkHttp: Via: 1.1 vegur 

我的回应日志JSON响应我的cache头低于:

由于提前,

+0

我不能重现此。 https://gist.github.com/swankjesse/29e4c343a785e586a6657022636d294e –

+0

@JesseWilson你提到的测试文件通过。但在真实设备上,我的应用程序仍然存在此问题。它与改进1.9和okhttp3工作正常。 – StarWars

+0

@JesseWilson我也添加了回复日志。你能帮忙解决这个问题吗? – StarWars

回答

0

你没有明确提到使用ETag的。但是由于他们的使用,我也遇到了同样的问题。所以它可能是相关的。

OkHttp支持ETags的一个缺点是OkHttp从不“刷新”缓存的过期时间。所以一旦过期,它将永远不会使用缓存,直到它被更新。这只会在资源被更新时才会发生(etags不同,并且返回200响应与304)。这意味着OkHttp客户端只要继续获得304响应,就会继续访问网络。 HTTP规范对客户如何处理这种情况很模糊,所以我不认为这是一个错误。但是如果我继续击中网络,它确实会破坏缓存的目的。我提出的唯一解决方案是当我知道缓存已过期并需要“刷新”时,提供“无缓存”缓存控制标头。这将检索响应(假设它是200)并刷新缓存及其到期。

下面是一个作为例子的方法。大纲:

  1. 强制客户端缓存(在我的情况下,服务器在发送必无效Cache-Control头)与日期+ max-age的
  2. 记录URL作为内部缓存
  3. 监听要求。如果请求不包含缓存的URL(首次或应用程序重新启动)或URL已过期,请在请求中发送无缓存Cache-Control标头。这会强制请求绕过缓存和If-Not-Modified条件。该响应然后由OkHttp在内部缓存,我们在步骤2中的前一个拦截器捕获并更新了我们的内部URL缓存。

这种方法的一个缺点是我们使用no-cache强制刷新。这意味着即使内容与已经缓存的内容相同,服务器也会最终提供内容。在我的情况下,这是一个可以接受的折衷方案,因为服务器正在处理请求(仅用于生成ETag哈希)。所以有条件的if-none-match只是保存有效载荷传输而不是web服务器处理。在我的情况下,服务器处理几乎占了所有的性能(因此我需要首先强制缓存)。

下面是一些代码:

<!-- language: kotlin --> 
class RestAdapterFactory(private val app: Context) { 

    private var httpClient: OkHttpClient.Builder? = null 
    private var builder: Retrofit.Builder? = null 
    private val MAX_AGE = 60 * 1 
    private val READ_TIMEOUT = 30.toLong() 
    private val CACHE_SIZE = 5 * 1024 * 1024.toLong() // 5 MB 
    private val apiCache: MutableMap<String, DateTime> = HashMap() 

    private fun getHttpClient(): OkHttpClient.Builder { 
     if (httpClient == null) { 
      httpClient = OkHttpClient.Builder() 
        .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) 
        .cache(Cache(app.cacheDir, CACHE_SIZE)) 
        .addNetworkInterceptor(getWebApiResponseInterceptor()) 
        .addInterceptor(getConditionalCacheRequestInterceptor()) 
     } 
     return httpClient!! 
    } 

    /*** 
    * Stores url entry with time to expire (to be used in conjunction with [getConditionalCacheRequestInterceptor] 
    */ 
    private fun getWebApiResponseInterceptor(): (Interceptor.Chain) -> Response { 
     return { 
      val response = it.proceed(it.request()) 
      apiCache[it.request().url().toString()] = DateTime().plusSeconds(MAX_AGE) 
      response.newBuilder().header("Cache-Control", "max-age=$MAX_AGE").build() // forcing client to cache 
     } 
    } 

    /*** 
    * Checks expiration of url, if url exists and is expired then force a response with no-cache 
    */ 
    private fun getConditionalCacheRequestInterceptor(): (Interceptor.Chain) -> Response { 
     return { 
      val original = it.request() 
      val urlExpiration = apiCache[original.url().toString()] 
      val noCache = urlExpiration == null || urlExpiration < DateTime() 
      it.proceed(if (noCache) original.newBuilder().header("Cache-Control", "no-cache").build() else original) 
     } 
    } 

} 
+0

你能提供一些代码,你如何解决它。 – StarWars