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錯誤(屬於正常的邏輯)。
最後
歡迎留言,歡迎提問題、交流。