Android okhttp緩存真正正確的實(shí)現(xiàn)方式

前言

關(guān)于okhttp的緩存,網(wǎng)上有大量的文章,或相同,或不同,方式不一,但都八九不離十,原理都是通過(guò)CacheControl的設(shè)置策略不同來(lái)實(shí)現(xiàn)的。
但是,真正實(shí)踐過(guò)的人會(huì)發(fā)現(xiàn),好像有這樣那樣的問(wèn)題。
比如:

  • 到底是用addNetInterceptor呢還是用addInterceptor,不同的用法有不同的效果
  • 什么有網(wǎng)的時(shí)候是maxAge,無(wú)網(wǎng)的時(shí)候又是maxStale等等

簡(jiǎn)直不明白。
于是乎,我個(gè)人做了很多很多的嘗試,幾乎把網(wǎng)上的方法都試了一遍,看了大量換湯不換藥的文章。下面是借鑒文章出處,但是我的方法都與他們的不同。

http://blog.csdn.net/u014614038/article/details/51210685
http://blog.csdn.net/Picasso_L/article/details/50579884
http://blog.csdn.net/briblue/article/details/52920531
http://www.itdecent.cn/p/412157e236ad
https://stackoverflow.com/questions/23429046/can-retrofit-with-okhttp-use-cache-data-when-offline
https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html

對(duì)于okhttp的緩存解決方案,我的需求是:

1、有網(wǎng)的時(shí)候也可以讀取緩存,并且可以控制緩存的過(guò)期時(shí)間,這樣可以減輕服務(wù)器壓力
2、有網(wǎng)的時(shí)候不讀取緩存,比如一些及時(shí)性較高的接口請(qǐng)求
3、無(wú)網(wǎng)的時(shí)候讀取緩存,并且可以控制緩存過(guò)期的時(shí)間

正文

說(shuō)了那么多,先直接上解決方案

    /**
     * 有網(wǎng)時(shí)候的緩存
     */
    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;//在線的時(shí)候的緩存過(guò)期時(shí)間,如果想要不緩存,直接時(shí)間設(shè)置為0
            return response.newBuilder()
                    .header("Cache-Control", "public, max-age="+onlineCacheTime)
                    .removeHeader("Pragma")
                    .build();
        }
    };
    /**
     * 沒(méi)有網(wǎng)時(shí)候的緩存
     */
    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;//離線的時(shí)候的緩存的過(guò)期時(shí)間
                request = request.newBuilder()
//                        .cacheControl(new CacheControl
//                                .Builder()
//                                .maxStale(60,TimeUnit.SECONDS)
//                                .onlyIfCached()
//                                .build()
//                        ) 兩種方式結(jié)果是一樣的,寫(xiě)法不同
                        .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();

沒(méi)錯(cuò)最終的解決方案就是,兩個(gè)interceptor,并且針對(duì)不同的網(wǎng)絡(luò)情況進(jìn)行不同的處理。(為什么是兩個(gè)interceptor后面會(huì)講到)

如果趕時(shí)間的朋友,可以直接拿去用,看到這里就行了。如果覺(jué)得用起來(lái)就問(wèn)題,歡迎下面留言。

實(shí)踐和測(cè)試的過(guò)程

首先我最初的寫(xiě)法就是來(lái)源于網(wǎng)上大多數(shù)人的寫(xiě)法,類似

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;
  }
}

  //設(shè)置緩存100M
        Cache cache = new Cache(new File(MainApplication.getContext().getCacheDir(),"httpCache"),1024 * 1024 * 100);
        return new OkHttpClient.Builder()
            .cache(cache)
            .addNetworkInterceptor(new HttpCacheInterceptor())
            .build();

這段代碼滿足不了我的需求,并且會(huì)發(fā)現(xiàn),有些情況離線的時(shí)候緩存過(guò)期的時(shí)間不可靠,有些情況在線的時(shí)候緩存不可用。對(duì)于我的需求不可兼得。(故網(wǎng)上有人還分了2種不同情況的寫(xiě)法來(lái)針對(duì)不同的需求,但我想,為什么不綜合起來(lái)呢?)

對(duì)于這段代碼疑惑點(diǎn):
1、max-age是啥,maxStale是啥,他們的區(qū)別是啥?
2、為什么沒(méi)有網(wǎng)絡(luò)的情況下,request要cacheControl.FORCE_CACHE
3、為什么又要對(duì)response設(shè)置header的cache-control,到底request的設(shè)置跟response的設(shè)置有什么區(qū)別?
4、addNetInterceptor和addInterceptor有什么區(qū)別?

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

maxAge和maxStale的區(qū)別在于:
maxAge:沒(méi)有超出maxAge,不管怎么樣都是返回緩存數(shù)據(jù),超過(guò)了maxAge,發(fā)起新的請(qǐng)求獲取數(shù)據(jù)更新,請(qǐng)求失敗返回緩存數(shù)據(jù)。
maxStale:沒(méi)有超過(guò)maxStale,不管怎么樣都返回緩存數(shù)據(jù),超過(guò)了maxStale,發(fā)起請(qǐng)求獲取更新數(shù)據(jù),請(qǐng)求失敗返回失敗

2、為什么沒(méi)有網(wǎng)絡(luò)的情況下,request要cacheControl.FORCE_CACHE

public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
可以看到FORCE_CACHE是設(shè)置了maxStale的最大時(shí)間為interger的最大時(shí)間,所以,意思就是無(wú)論如何,都不會(huì)超過(guò)這個(gè)時(shí)間,所以就是一直(強(qiáng)制)拿緩存,也是想要實(shí)現(xiàn)緩存的正確邏輯

一般控制緩存有兩種方式:
1、在request里面去設(shè)置cacheControl()策略
2、在header里面去添加cache-control

后面也會(huì)看到,在request里面設(shè)置header的cache-control和調(diào)用cacheControl方法來(lái)設(shè)置其實(shí)是一樣的,我們就是通過(guò)在request里面來(lái)控制無(wú)網(wǎng)緩存的maxStale過(guò)期時(shí)間的

3、為什么又要對(duì)response設(shè)置header的cache-control,到底request的設(shè)置跟response的設(shè)置有什么區(qū)別?

其實(shí)我到現(xiàn)在都還沒(méi)有搞清楚,為啥那些人要這樣寫(xiě),只是后面我在測(cè)試的過(guò)程中推斷出來(lái),有網(wǎng)的時(shí)候和無(wú)網(wǎng)的時(shí)候?qū)τ趇nterceptor的調(diào)用是不同的,產(chǎn)生的結(jié)果也是不同的。比如request設(shè)置的時(shí)候就對(duì)無(wú)網(wǎng)緩存及其時(shí)間控制有效,response就不行

4、addNetInterceptor和addInterceptor有什么區(qū)別?

addNetInterceptor是添加網(wǎng)絡(luò)攔截器,addInterceptor是添加應(yīng)用攔截器,如果看到okhttp的流程分析的知道:應(yīng)用攔截器是在網(wǎng)絡(luò)攔截器前執(zhí)行的。

如果我使用的是addNetInterceptor:
1、有網(wǎng)的情況下,可以在期限內(nèi)拿到緩存,而沒(méi)有去請(qǐng)求接口(通過(guò)測(cè)試數(shù)據(jù)庫(kù)的數(shù)據(jù)改動(dòng)來(lái)判斷的)
2、沒(méi)有網(wǎng)的情況下,直接就ConnectException了,根本不會(huì)走到interceptor里面去了。(網(wǎng)上很多人都提出了這樣的問(wèn)題)

如果我使用的是addInterceptor:
1、有網(wǎng)的情況下,明明設(shè)置的是60秒,但是每次都沒(méi)有去拿緩存而都是請(qǐng)求的接口。(通過(guò)測(cè)試數(shù)據(jù)庫(kù)的數(shù)據(jù)改動(dòng)來(lái)判斷的)
2、沒(méi)有網(wǎng)的情況下,可以拿到緩存數(shù)據(jù)(猜想:可能是因?yàn)閼?yīng)用攔截器在網(wǎng)絡(luò)攔截器前執(zhí)行,沒(méi)有網(wǎng)的情況下,本身就執(zhí)行不到網(wǎng)絡(luò)攔截器里面去),但是緩存過(guò)期時(shí)間是“永久”,因?yàn)镕ORCE_CACHE里面已經(jīng)設(shè)置為了integer的最大值,21億秒左右,堪稱永久
但是,依舊沒(méi)有辦法控制無(wú)網(wǎng)時(shí)候的緩存過(guò)期時(shí)間

面對(duì)這些個(gè)問(wèn)題,我也是很無(wú)奈。只得不停地嘗試,不停地摸索。于是就在嘗試的過(guò)程中發(fā)現(xiàn)了它的一些規(guī)則,于是最終寫(xiě)出了一個(gè)自認(rèn)為“萬(wàn)全”的方法。

  • 既然想要有網(wǎng)的情況下拿緩存,那么就需要addNetInterceptor,如果需要無(wú)網(wǎng)的情況下拿緩存,就需要addInterceptor,所以不如直接做兩個(gè)interceptor吧!
  • 另外,如果想要控制有網(wǎng)的時(shí)候不去讀取緩存,可以直接通過(guò)在response里設(shè)置maxAge=0來(lái)實(shí)現(xiàn)。
  • 這里通過(guò)大量實(shí)驗(yàn)發(fā)現(xiàn),只有在request去設(shè)置其maxStale才能控制無(wú)網(wǎng)時(shí)候的緩存時(shí)間,在response里面去控制是不行的!
延伸

如果無(wú)網(wǎng)的時(shí)候,stale緩存時(shí)間過(guò)了,會(huì)怎么樣呢?
會(huì)報(bào)504錯(cuò)誤(屬于正常的邏輯)。

最后

歡迎留言,歡迎提問(wèn)題、交流。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容