okhttp 緩存實(shí)踐

以下內(nèi)容基于 okhttp:3.10.0 版本

在開發(fā)中,由于不同業(yè)務(wù)場(chǎng)景解,我們需要將接口返回的數(shù)據(jù)緩存到本地杭抠,以實(shí)現(xiàn)復(fù)用。例如恳啥,接口數(shù)據(jù)每間隔一定時(shí)間才會(huì)更新偏灿,在時(shí)間間隔內(nèi)就沒必要重復(fù)的向服務(wù)器請(qǐng)求數(shù)據(jù),直接使用緩存即可钝的;當(dāng) app 無法訪問網(wǎng)絡(luò)時(shí)菩混,也可以使用緩存的接口數(shù)據(jù),避免缺省頁等等扁藕。所以使用緩存也是好處多多:節(jié)省流量沮峡、提高響應(yīng)速度、增強(qiáng)用戶體驗(yàn)......

okhttp 的緩存功能使用起來也比較簡(jiǎn)單亿柑,我們一步步來看:

1邢疙、配置緩存

配置緩存首先要指定緩存目錄和緩存大小,這兩個(gè)可以根據(jù)項(xiàng)目的需求來確定望薄,然后使用 OkHttpClient..Builder()cache()方法來配置緩存對(duì)象疟游。這里的OkHttpClient是一個(gè)單例,保證了只有一個(gè)緩存緩存目錄的入口痕支。配置代碼如下:

public class OkHttpManager {
    private OkHttpClient client;

    private OkHttpManager() {
        // 緩存目錄
        File file = new File(Environment.getExternalStorageDirectory(), "a_cache");
        // 緩存大小
        int cacheSize = 10 * 1024 * 1024;
        client = new OkHttpClient.Builder()
                .cache(new Cache(file, cacheSize)) // 配置緩存
                .build();
    }

    public static OkHttpManager getInstance() {
        return OkHttpHolder.instance;
    }

    private static class OkHttpHolder {
        private static final OkHttpManager instance = new OkHttpManager();
    }
    ......
}

到這里就完成了基本配置工作颁虐,不要忘了處理權(quán)限問題,因?yàn)榫彺婀δ苄枰鎯?chǔ)空間的讀寫權(quán)限卧须。

如果客戶端和服務(wù)端已經(jīng)協(xié)商好了另绩,在接口的響應(yīng)包含合適的Cache-Control響應(yīng)頭,表示緩存的策略花嘶,例如Cache-Control:max-age=60笋籽,表示緩存的有效期為60秒。

這個(gè)響應(yīng)頭是實(shí)現(xiàn)緩存的一個(gè)重點(diǎn)椭员,如果包含合適的Cache-Control響應(yīng)頭车海,在無論網(wǎng)絡(luò)連接是否正常的情況下請(qǐng)求接口數(shù)據(jù),如果在緩存有效期內(nèi)則直接從緩存讀取數(shù)據(jù)隘击,超過有效期會(huì)重新請(qǐng)求接口數(shù)據(jù)侍芝。

列舉幾個(gè)常用的Cache-control響應(yīng)頭的可選值:

  • must-revalidate,一旦緩存過期埋同,必須向服務(wù)器重新請(qǐng)求州叠,不得使用過期內(nèi)容
  • no-cache,不使用緩存
  • no-store莺禁,不緩存請(qǐng)求的響應(yīng)
  • no-transform留量,不得對(duì)響應(yīng)進(jìn)行轉(zhuǎn)換或轉(zhuǎn)變
  • public,任何響應(yīng)都可以被緩存,即使響應(yīng)默認(rèn)是不可緩存或僅私有緩存可存的
  • private楼熄,表明響應(yīng)只能被單個(gè)用戶緩存忆绰,不能作為共享緩存(即代理服務(wù)器不能緩存它)
  • proxy-revalidate,與must-revalidate類似可岂,但它僅適用于共享緩存(例如代理)错敢,并被私有緩存忽略。
  • max-age缕粹,緩存的有效時(shí)間
  • s-maxage稚茅,指定響應(yīng)在公共緩存中的最大存活時(shí)間,它覆蓋max-age和expires字段平斩。

所以目前的問題是亚享,如果響應(yīng)不包含合適的Cache-Control響應(yīng)頭,該如何處理绘面,這也是接下來主要討論的問題欺税。

2、攔截器

由于客戶端和服務(wù)端不是同一團(tuán)隊(duì)揭璃,或者客戶端使用了第三方接口等原因晚凿,無法進(jìn)行協(xié)商,導(dǎo)致接口的響應(yīng)沒有合適的Cache-Control響應(yīng)頭瘦馍,或者緩存已被禁用歼秽。這種情況下要讓緩存功能正常工作,就需要使用自定義攔截器了情组,通過攔截器給請(qǐng)求的響應(yīng)(Response)添加合適的Cache-Control響應(yīng)頭即可燥筷,這樣問題就得到了解決!

不了解 okhttp 攔截器的可以先看官網(wǎng)的文檔呻惕,很詳細(xì):https://github.com/square/okhttp/wiki/Interceptors

看一下攔截器如何實(shí)現(xiàn):

public class NetCacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response originResponse = chain.proceed(request);

        //設(shè)置響應(yīng)的緩存時(shí)間為60秒荆责,即設(shè)置Cache-Control頭,并移除pragma消息頭亚脆,因?yàn)閜ragma也是控制緩存的一個(gè)消息頭屬性
        originResponse = originResponse.newBuilder()
                .removeHeader("pragma")
                .header("Cache-Control", "max-age=60")
                .build();

        return originResponse;
    }

攔截請(qǐng)求的響應(yīng),先移除pragma盲泛,然后手動(dòng)設(shè)置Cache-Control響應(yīng)頭濒持。

把定義好的攔截器添加到OkHttpClient中:

client = new OkHttpClient.Builder()
                .cache(new Cache(file, cacheSize))
                .addNetworkInterceptor(new NetCacheInterceptor())
                .build();

3、測(cè)試

封裝一個(gè)asyncGet()方法來實(shí)現(xiàn)異步get請(qǐng)求寺滚,http://publicobject.com/helloworld.txt是官方的一個(gè)實(shí)例地址:

public class OkHttpManager {
    private OkHttpClient client;
    ......
    public void asyncGet(Callback callback) {
        Request request = new Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();
        client.newCall(request).enqueue(callback);
    }
}

以下是發(fā)起請(qǐng)求柑营、以及回調(diào)的代碼:

OkHttpManager.getInstance().asyncGet(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.e("failure", e.toString());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (response.isSuccessful()) {
                        if (response.networkResponse() != null) {
                            Log.e("network", response.body().string().length() + "");
                        } else if (response.cacheResponse() != null) {
                            if (Utils.isNetworkAvailable(context)) {
                                Log.e("cache", response.body().string().length() + "");
                            } else {
                                Log.e("cache(no network)", response.body().string().length() + "");
                            }
                        }
                    }
                }
            });

如果響應(yīng)是從網(wǎng)絡(luò)請(qǐng)求得到的,那么response.networkResponse()不為空村视,如果是從緩存中得到的response.cacheResponse()不為空官套,以此來打印不同 log 觀察緩存功能是否能正常工作,這里打印了響應(yīng)的 boay 長(zhǎng)度。

下邊是在60秒內(nèi)發(fā)起了若干次請(qǐng)求奶赔,即便斷開網(wǎng)絡(luò)連接也能正常的從緩存讀取數(shù)據(jù)惋嚎,超過60秒會(huì)重新請(qǐng)求數(shù)據(jù),這也驗(yàn)證了我們的緩存功能可以正常工作了:


緩存功能的具體實(shí)現(xiàn)是通過 DiskLruCache 完成的站刑,在之前配置的緩存目錄可以找到對(duì)應(yīng)的緩存文件:

4另伍、okhttp 緩存策略

上邊在攔截器中統(tǒng)一設(shè)置了響應(yīng)的緩存時(shí)間,導(dǎo)致所有的接口數(shù)據(jù)都會(huì)緩存绞旅,且時(shí)間相同摆尝。這樣問題就來了,可能不同接口對(duì)數(shù)據(jù)的緩存時(shí)間要求不同因悲,或者有些接口并不需要緩存數(shù)據(jù)堕汞。要解決這個(gè)問題可以在攔截器中根據(jù)請(qǐng)求的地址(request.url().toString())來決定如何設(shè)置響應(yīng)的緩存時(shí)間,但不夠優(yōu)雅晃琳!除此之外可以使用 okhttp 的緩存策略類CacheControl來處理讯检。

CacheControl類提供了如下兩個(gè)默認(rèn)的緩存策略:

  • CacheControl.FORCE_NETWORK,即強(qiáng)制使用網(wǎng)絡(luò)請(qǐng)求
  • CacheControl.FORCE_CACHE蝎土,即強(qiáng)制使用本地緩存视哑,如果無可用緩存則返回一個(gè)code為504的響應(yīng)

根據(jù)默認(rèn)緩存策略的實(shí)現(xiàn)方式,我們可以通過CacheControl.Builder()定制自己的緩存策略誊涯,可選的設(shè)置方法如下:

  • noCache()挡毅,不使用緩存,使用網(wǎng)絡(luò)請(qǐng)求
  • noStore()暴构,不使用緩存也不存儲(chǔ)緩存數(shù)據(jù)
  • maxAge()跪呈,緩存的有效時(shí)間,超過該時(shí)間會(huì)重新請(qǐng)求數(shù)據(jù)
  • maxStale()取逾,超過緩存有效時(shí)間后耗绿,可繼續(xù)使用舊緩存的時(shí)間,之后需要重新請(qǐng)求數(shù)據(jù)
  • minFresh()砾隅,增加額外的緩存的有效時(shí)間误阻,之后需要重新請(qǐng)求數(shù)據(jù)
  • onlyIfCached(),使用緩存晴埂,不使用網(wǎng)絡(luò)請(qǐng)求
  • noTransform()究反,不接受經(jīng)過轉(zhuǎn)碼的響應(yīng)
  • immutable(),緩存有效時(shí)間內(nèi)儒洛,響應(yīng)不會(huì)變化精耐,避免服務(wù)器處理304響應(yīng)

了解了這些配置方法后,修改之前的asyncGet()方法琅锻,創(chuàng)建一個(gè)CacheControl卦停,并添加到Request

public void asyncGet(Callback callback) {
        CacheControl cacheControl = new CacheControl.Builder()
                .maxStale(10, TimeUnit.SECONDS)
                .maxAge(10, TimeUnit.SECONDS)
                .build();

        Request request = new Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .cacheControl(cacheControl)
                .build();

        client.newCall(request).enqueue(callback);
    }

Request添加CacheControl配置向胡,就相當(dāng)于給給Request添加了對(duì)應(yīng)的Cache-Control請(qǐng)求頭!>辍僵芹!

我們?cè)O(shè)置maxAge為10秒、maxStale為10秒专执,此時(shí)攔截器中設(shè)置的Cache-Control響應(yīng)頭還是60秒淮捆,測(cè)試下效果:


可以看出,當(dāng)時(shí)間間隔大于20秒會(huì)重新請(qǐng)求數(shù)據(jù)本股,即超過maxAge時(shí)間+maxStale時(shí)間攀痊。

我們修改maxAge為100秒再測(cè)試下效果:


可以看出,當(dāng)時(shí)間間隔大于70秒會(huì)重新請(qǐng)求數(shù)據(jù)拄显,即Cache-Control響應(yīng)頭時(shí)間+maxStale時(shí)間苟径。

所以當(dāng)通過CacheControl類設(shè)置的緩存時(shí)間大于Cache-Control響應(yīng)頭時(shí)間,緩存有效時(shí)間為Cache-Control響應(yīng)頭時(shí)間躬审,否則為CacheControl類設(shè)置的緩存時(shí)間棘街。

所以我們可以給有需要的接口請(qǐng)求通過CacheControl類設(shè)置緩存策略,然后在攔截器中判斷請(qǐng)求是否包含Cache-Control請(qǐng)求頭承边,如果有就把Cache-Control請(qǐng)求頭添加到響應(yīng)中去遭殉,這樣問題就解決了,修改后的攔截器如下:

public class NetCacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response originResponse = chain.proceed(request);

        if (!TextUtils.isEmpty(request.header("Cache-Control"))){
            originResponse = originResponse.newBuilder()
                    .removeHeader("pragma")
                    .header("Cache-Control", request.header("Cache-Control"))
                    .build();
        }

        return originResponse;
    }
}

內(nèi)容就這些了博助,不合理的地方還望指出险污。

測(cè)試代碼地址:https://github.com/SheHuan/SomeTest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市富岳,隨后出現(xiàn)的幾起案子蛔糯,更是在濱河造成了極大的恐慌,老刑警劉巖窖式,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚁飒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萝喘,警方通過查閱死者的電腦和手機(jī)淮逻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁簸,“玉大人弦蹂,你說我怎么就攤上這事∏拷眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵削祈,是天一觀的道長(zhǎng)翅溺。 經(jīng)常有香客問我脑漫,道長(zhǎng),這世上最難降的妖魔是什么咙崎? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任优幸,我火速辦了婚禮,結(jié)果婚禮上褪猛,老公的妹妹穿的比我還像新娘网杆。我一直安慰自己,他們只是感情好伊滋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布碳却。 她就那樣靜靜地躺著,像睡著了一般笑旺。 火紅的嫁衣襯著肌膚如雪昼浦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天筒主,我揣著相機(jī)與錄音关噪,去河邊找鬼。 笑死乌妙,一個(gè)胖子當(dāng)著我的面吹牛使兔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藤韵,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼艰争,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了拌消?” 一聲冷哼從身側(cè)響起镣煮,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悉盆,沒想到半個(gè)月后盯荤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焕盟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年秋秤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚翘。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灼卢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出来农,到底是詐尸還是另有隱情鞋真,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布沃于,位于F島的核電站涩咖,受9級(jí)特大地震影響海诲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檩互,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一特幔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闸昨,春花似錦蚯斯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至告抄,卻和暖如春撰茎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背打洼。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工龄糊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人募疮。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓炫惩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親阿浓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子他嚷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容