2016-08-01 85 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