1. 程式人生 > >Android okhttp快取真正正確的實現方式

Android okhttp快取真正正確的實現方式

前言

關於okhttp的快取,網上有大量的文章,或相同,或不同,方式不一,但都八九不離十,原理都是通過CacheControl的設定策略不同來實現的。
但是,真正實踐過的人會發現,好像有這樣那樣的問題。
比如:
- 到底是用addNetInterceptor呢還是用addInterceptor,不同的用法有不同的效果
- 什麼有網的時候是maxAge,無網的時候又是maxStale等等

簡直不明白。
於是乎,我個人做了很多很多的嘗試,幾乎把網上的方法都試了一遍,看了大量換湯不換藥的文章。下面是借鑑文章出處,但是我的方法都與他們的不同。

對於okhttp的快取解決方案,我的需求是:

1、有網的時候也可以讀取快取,並且可以控制快取的過期時間,這樣可以減輕伺服器壓力
2、有網的時候不讀取快取,比如一些及時性較高的介面請求
3、無網的時候讀取快取,並且可以控制快取過期的時間

正文

說了那麼多,先直接上解決方案

    /**
     * 有網時候的快取
     */
    final Interceptor NetCacheInterceptor = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            int
onlineCacheTime = 30;//線上的時候的快取過期時間,如果想要不快取,直接時間設定為0 return response.newBuilder() .header("Cache-Control", "public, max-age="+onlineCacheTime) .removeHeader("Pragma") .build(); } }; /** * 沒有網時候的快取 */ final
Interceptor OfflineCacheInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!SystemTool.checkNet(AppContext.context)) { int offlineCacheTime = 60;//離線的時候的快取的過期時間 request = request.newBuilder() // .cacheControl(new CacheControl // .Builder() // .maxStale(60,TimeUnit.SECONDS) // .onlyIfCached() // .build() // ) 兩種方式結果是一樣的,寫法不同 .header("Cache-Control", "public, only-if-cached, max-stale=" + offlineCacheTime) .build(); } return chain.proceed(request); } }; //setup cache File httpCacheDirectory = new File(AppContext.context.getCacheDir(), "okhttpCache"); int cacheSize = 10 * 1024 * 1024; // 10 MiB Cache cache = new Cache(httpCacheDirectory, cacheSize); OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(NetCacheInterceptor) .addInterceptor(OfflineCacheInterceptor) .cache(cache) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build();

沒錯最終的解決方案就是,兩個interceptor,並且針對不同的網路情況進行不同的處理。(為什麼是兩個interceptor後面會講到)

如果趕時間的朋友,可以直接拿去用,看到這裡就行了。如果覺得用起來就問題,歡迎下面留言。

實踐和測試的過程

首先我最初的寫法就是來源於網上大多數人的寫法,類似

public class HttpCacheInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    if (!NetWorkHelper.isNetConnected(MainApplication.getContext())) {
        request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build();
    }

    Response response = chain.proceed(request);

    if (NetWorkHelper.isNetConnected(MainApplication.getContext())) {
        int maxAge = 60 * 60; // read from cache for 1 minute
        response.newBuilder()
                .removeHeader("Pragma")
                .header("Cache-Control", "public, max-age=" + maxAge)
                .build();
    } else {
        int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
        response.newBuilder()
                .removeHeader("Pragma")
                .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                .build();
    }
    return response;
  }
}

  //設定快取100M
        Cache cache = new Cache(new File(MainApplication.getContext().getCacheDir(),"httpCache"),1024 * 1024 * 100);
        return new OkHttpClient.Builder()
            .cache(cache)
            .addNetworkInterceptor(new HttpCacheInterceptor())
            .build();

這段程式碼滿足不了我的需求,並且會發現,有些情況離線的時候快取過期的時間不可靠,有些情況線上的時候快取不可用。對於我的需求不可兼得。(故網上有人還分了2種不同情況的寫法來針對不同的需求,但我想,為什麼不綜合起來呢?)

對於這段程式碼疑惑點:
1、max-age是啥,maxStale是啥,他們的區別是啥?
2、為什麼沒有網路的情況下,request要cacheControl.FORCE_CACHE
3、為什麼又要對response設定header的cache-control,到底request的設定跟response的設定有什麼區別?
4、addNetInterceptor和addInterceptor有什麼區別?

解答:
1、max-age是啥,maxStale是啥,他們的區別是啥?

maxAge和maxStale的區別在於:
maxAge:沒有超出maxAge,不管怎麼樣都是返回快取資料,超過了maxAge,發起新的請求獲取資料更新,請求失敗返回快取資料。
maxStale:沒有超過maxStale,不管怎麼樣都返回快取資料,超過了maxStale,發起請求獲取更新資料,請求失敗返回失敗

2、為什麼沒有網路的情況下,request要cacheControl.FORCE_CACHE

public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
可以看到FORCE_CACHE是設定了maxStale的最大時間為interger的最大時間,所以,意思就是無論如何,都不會超過這個時間,所以就是一直(強制)拿快取,也是想要實現快取的正確邏輯

一般控制快取有兩種方式:
1、在request裡面去設定cacheControl()策略
2、在header裡面去新增cache-control

後面也會看到,在request裡面設定header的cache-control和呼叫cacheControl方法來設定其實是一樣的,我們就是通過在request裡面來控制無網快取的maxStale過期時間的

3、為什麼又要對response設定header的cache-control,到底request的設定跟response的設定有什麼區別?

其實我到現在都還沒有搞清楚,為啥那些人要這樣寫,只是後面我在測試的過程中推斷出來,有網的時候和無網的時候對於interceptor的呼叫是不同的,產生的結果也是不同的。比如request設定的時候就對無網快取及其時間控制有效,response就不行

4、addNetInterceptor和addInterceptor有什麼區別?

addNetInterceptor是新增網路攔截器,addInterceptor是新增應用攔截器,如果看到okhttp的流程分析的知道:應用攔截器是在網路攔截器前執行的。

如果我使用的是addNetInterceptor:
1、有網的情況下,可以在期限內拿到快取,而沒有去請求介面(通過測試資料庫的資料改動來判斷的)
2、沒有網的情況下,直接就ConnectException了,根本不會走到interceptor裡面去了。(網上很多人都提出了這樣的問題)

如果我使用的是addInterceptor:
1、有網的情況下,明明設定的是60秒,但是每次都沒有去拿快取而都是請求的介面。(通過測試資料庫的資料改動來判斷的)
2、沒有網的情況下,可以拿到快取資料(猜想:可能是因為應用攔截器在網路攔截器前執行,沒有網的情況下,本身就執行不到網路攔截器裡面去),但是快取過期時間是“永久”,因為FORCE_CACHE裡面已經設定為了integer的最大值,21億秒左右,堪稱永久
但是,依舊沒有辦法控制無網時候的快取過期時間

面對這些個問題,我也是很無奈。只得不停地嘗試,不停地摸索。於是就在嘗試的過程中發現了它的一些規則,於是最終寫出了一個自認為“萬全”的方法。

  • 既然想要有網的情況下拿快取,那麼就需要addNetInterceptor,如果需要無網的情況下拿快取,就需要addInterceptor,所以不如直接做兩個interceptor吧!
  • 另外,如果想要控制有網的時候不去讀取快取,可以直接通過在response裡設定maxAge=0來實現。
  • 這裡通過大量實驗發現,只有在request去設定其maxStale才能控制無網時候的快取時間,在response裡面去控制是不行的!
延伸

如果無網的時候,stale快取時間過了,會怎麼樣呢?
會報504錯誤(屬於正常的邏輯)。

最後

歡迎留言,歡迎提問題、交流。