如何實(shí)現(xiàn)一個(gè)獨(dú)立于網(wǎng)絡(luò)請求框架的緩存(與retrofit無縫銜接)

轉(zhuǎn)載請注明出處:
如何實(shí)現(xiàn)一個(gè)獨(dú)立于網(wǎng)絡(luò)請求框架的緩存(與retrofit無縫銜接)

地址:http://www.reibang.com/p/de0ec94ca5c1
目錄

1 前言

首先聲明,文章中cache-retrofit框架原理部分不是我想出來的。歸功于前同事夏恩龍同學(xué)旁舰。感謝他奇昙,散花~ 。我現(xiàn)在維護(hù)這個(gè)框架坡贺,在這個(gè)框架上增加了一些功能。因?yàn)楦杏X這個(gè)框架還挺不錯(cuò)的。所以講一下它的原理:怎么實(shí)現(xiàn)一個(gè)與retrofit的網(wǎng)絡(luò)請求框架無縫銜接的緩存器贩。這個(gè)需要的提出是這樣的:貓眼/美團(tuán)/點(diǎn)評使用的網(wǎng)絡(luò)請求的client并不一致,貓眼使用的是okhttp朋截,美團(tuán)/點(diǎn)評使用的是Shark 長連接蛹稍。長連接自帶的緩存機(jī)制不是很好用,沒有完全實(shí)現(xiàn)okhttp的cache-control部服。所以就需要一個(gè)脫離網(wǎng)絡(luò)請求框架之外的緩存實(shí)現(xiàn)唆姐,且緩存的使用需要對業(yè)務(wù)透明(與retrofit類似)。

如果你項(xiàng)目中的retrofit中的callFactory是okhttp client的話廓八,okhttp已經(jīng)幫我們實(shí)現(xiàn)好了cache-control奉芦,所以如果我們一直都使用okhttp作為網(wǎng)絡(luò)請求的client,并且沒有那么多的緩存定制需求剧蹂,那么cache-control這樣緩存策略(文件緩存)方式挺好的声功,但是如果你有一些特殊要求,比如prefer-network国夜,需要知道獲取的數(shù)據(jù)源來自網(wǎng)絡(luò)或本地(當(dāng)你cache-control的參數(shù)為時(shí)間段時(shí)减噪,不能確定數(shù)據(jù)來源),已經(jīng)不再使用okhttp(采用長連接)等,這些時(shí)候你就不得不自己實(shí)現(xiàn)一套緩存來解決以上問題筹裕。

2 okhttp自帶的文件緩存

我們先看下cache-control的策略醋闭,這樣對之后的緩存參數(shù)替換、cache-cotrol的優(yōu)缺點(diǎn)都有一定幫助朝卒。

2.1 cache-control緩存

cache-control 傳入的參數(shù)為:force-network证逻,force-cache,或一個(gè)時(shí)間段抗斤。意義為:如果傳入的是force-network囚企,那么就從網(wǎng)絡(luò)中加載,然后更新本地相應(yīng)url的緩存瑞眼,更新改緩存的時(shí)間戳龙宏。如果force-cache,那么就使用本地伤疙。如果是一個(gè)時(shí)間段银酗,那么會(huì)把當(dāng)前請求的時(shí)間和請求url對應(yīng)緩存的時(shí)間戳做差值,如果差值大于cache-control的時(shí)間段徒像,那么就進(jìn)行網(wǎng)絡(luò)請求黍特,更新同上,如果小于時(shí)間段锯蛀,那么就走本地灭衷。

這種cache-control的方式管理緩存的方式的優(yōu)點(diǎn)是,使用一個(gè)參數(shù)就可以區(qū)分緩存是否過期/使用網(wǎng)絡(luò)/使用本地旁涤。

同樣這樣帶來一些小問題:

2.2 使用okhttp自帶緩存存在的問題

  • 問題就是我本次的網(wǎng)絡(luò)請求的數(shù)據(jù)存到本地時(shí)翔曲,這時(shí)候并無法確定這次的緩存過期時(shí)間,過期時(shí)間是由下次cache-control的時(shí)間段參數(shù)決定的拭抬。所以使用cache-control的方式使用緩存的話部默,必須帶時(shí)間段。這是一個(gè)很麻煩的事情造虎。因?yàn)閷τ谕粋€(gè)url,在客戶端使用時(shí)纷闺,過期時(shí)間一般都是相同的算凿。如果每次都需要上層開發(fā)者手動(dòng)指定過期時(shí)間,很繁瑣犁功。
    所以把緩存參數(shù)由一個(gè)cache-control改成loadPolicy和cacheTime比較好一些氓轰。loadPolicy表示什么方式來獲取數(shù)據(jù),比如force-network浸卦,force-cache署鸡,prefer-network,prefer-cache等,由上層業(yè)務(wù)開發(fā)者調(diào)用。cacheTime即緩存時(shí)間靴庆,一般來說在創(chuàng)建相應(yīng)url的api時(shí)就可以決定緩存時(shí)間了时捌。這樣參數(shù)分離的方式對開發(fā)來說更方面一些。
  • 如果需要的緩存策略是prefer-network:優(yōu)先使用網(wǎng)絡(luò)炉抒,如果沒有網(wǎng)絡(luò)奢讨,那么使用緩存。cache-control沒辦法做這個(gè)事情焰薄。
  • 如果需要區(qū)分?jǐn)?shù)據(jù)來源是網(wǎng)絡(luò)/本地拿诸,以cache-control為緩存策略的okhttp緩存實(shí)現(xiàn) 并不能做到這個(gè)事情。當(dāng)你cache-control的參數(shù)為時(shí)間段時(shí)塞茅,這時(shí)候數(shù)據(jù)可能不過期或者過期亩码,所以不能確定最終反序列化的model是來自網(wǎng)絡(luò)/本地。使用okhhtp的話野瘦,緩存這塊最開發(fā)者是透明的蟀伸,我們并不容易去修改源碼。 因?yàn)槊骞簦斜仨氉约喝?shí)現(xiàn)一份緩存策略實(shí)現(xiàn)定制自己的緩存需求啊掏。
  • “有必須自己去實(shí)現(xiàn)一份緩存策略實(shí)現(xiàn)定制自己的緩存需求“更迫切的原因是我們不再使用okhttp,而是采用其他的網(wǎng)絡(luò)請求框架(長連接)衰猛,那么新的client內(nèi)部可能沒有實(shí)現(xiàn)cache-control的緩存迟蜜,那么我們就得自己實(shí)現(xiàn)一份。這樣把網(wǎng)絡(luò)請求框架和緩存框架分離之后啡省,改動(dòng)網(wǎng)絡(luò)請求框架時(shí)不用變動(dòng)緩存框架娜睛。
  • 如果后臺返回的code是200,但是沒有數(shù)據(jù)卦睹。對于okhttp緩存來說畦戒,這也是請求成功,會(huì)把空數(shù)據(jù)存到本地(覆蓋對應(yīng)的非空數(shù)據(jù))结序,這樣其實(shí)是不好的障斋。因?yàn)閛khttp的緩存機(jī)制是內(nèi)建的,我們不能修改徐鹤,這也是我們需要自建一個(gè)緩存實(shí)現(xiàn)的原因垃环。我們在retrofit網(wǎng)絡(luò)請求返回T類型數(shù)據(jù)以后,在把這個(gè)T類型數(shù)據(jù)進(jìn)行反序列化存儲到DiskLruCache時(shí)返敬,我們刪選(是不是數(shù)據(jù)為空)遂庄,把為空的這些數(shù)據(jù)不進(jìn)行本地存儲。

通過前面的分析劲赠,我們的緩存最好對業(yè)務(wù)透明涛目,也就是說仍然使用retrofit網(wǎng)絡(luò)請求的api service 接口秸谢,只是多傳入兩個(gè)參數(shù)。緩存需要與retrofit的callfactory隔離霹肝。這樣在替換call factory時(shí)估蹄,緩存邏輯不需要任何改變。即阿迈,我們的目標(biāo)是實(shí)現(xiàn)一個(gè)帶緩存的retrofit元媚。

3 怎么實(shí)現(xiàn)一個(gè)帶緩存的retrofit

3.1 與retrofit相比,帶緩存的retrofit的使用區(qū)別

關(guān)于緩存策略苗沧,前面指出了刊棕,需要兩個(gè)參數(shù):loadPolicy,cacheTime待逞。 loadPolicy相比cache-control增加了prefer-network甥角。

3.2 內(nèi)部如何實(shí)現(xiàn)-動(dòng)態(tài)代理

既然為了上層業(yè)務(wù)使用透明,數(shù)據(jù)請求api 還是采用retrofit的接口形式识樱。那么就需要和
retrofit一樣嗤无,使用動(dòng)態(tài)代理方式來代理api service 接口。
在數(shù)據(jù)請求時(shí)怜庸,多傳遞一個(gè)loadPocily和cacheTime当犯。在動(dòng)態(tài)代理中,先根據(jù)loadPocily和cacheTime來決定使用網(wǎng)絡(luò)還是使用cache割疾,如果使用本地嚎卫,那么直接從本地DiskLruCache獲取序列化數(shù)據(jù),然后使用反序列化工具(比如序列化字符串->Gson.fromJson()->T)生成反序列化對象返回宏榕。如果使用網(wǎng)絡(luò)拓诸,那么使用retrofit代理來執(zhí)行真正的網(wǎng)絡(luò)請求,因?yàn)閞etrofit內(nèi)部已經(jīng)完成了反序列化工作(序列化字符串->GsonConverterFactory.responseBodyConverter()->gson.fromJson()->T)麻昼,所以得到的是反序列對象奠支,把該對象序列化后,存入disLruCache中抚芦。
注意倍谜,DiskLruCache中存儲的字符串是retrofit已經(jīng)生成好的對象簡單的進(jìn)行fromJson/toGson,所以對于某個(gè)model,DiskLruCache的序列化和后臺返回的序列化字符串可能不一樣燕垃。因?yàn)楹笈_的json字符串需要經(jīng)過GsonConverterFactory.responseBodyConverter()->gson.fromJson()才能進(jìn)行轉(zhuǎn)換成對象枢劝。GsonConverterFactory.responseBodyConverter()中可以對后臺的json字符串做處理(比如去掉一層括號),gson也可以添加自定義的jsonAdapter來解析某個(gè)json字符串卜壕,這樣才生成最終的model對象。將這個(gè)model對象直接序列化后的字符串很可能與后臺的json字符串不一樣烙常。當(dāng)然這樣不影響使用轴捎,因?yàn)榫彺胬锏氖遣皇呛笈_的gson字符串無所謂鹤盒,能反序列化成想要的對象即可。

3.3 大致代碼

獲取api service的接口類T的Class類型侦副,傳入ache-retrofit中侦锯,返回api service的接口類T的代理類:

 Class<T> service=T.class
 T cachedRetrofit=cacheNet.create(service);

業(yè)務(wù)中,使用cachedRetrofit調(diào)用api service的接口方法

Observable result=cachedRetrofit.getHotCommentKeyList(...)

cache-retrofit實(shí)現(xiàn)(在txt中手打的秦驯,排版丑尺碰,莫笑,哈哈):

    T cachedRetrofit= 
    (T)Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler(){
  
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
 //使用緩存
 loadRecord();
 ...
 //需要網(wǎng)絡(luò)加載
 T serviceProxy=retrofit.create(service)
  Observable result =method.invoke(serviceProxy,args).onErrorResumeNext(loadExpiredRecord()).doOnNext(saveData());//保存數(shù)據(jù)
  ...
  return serviceProxy;
  ...
 
 }
}
);           

3.4 實(shí)現(xiàn)時(shí)注意問題:loadPolicy译隘,cacheTime參數(shù)傳遞到哪里亲桥?

使用動(dòng)態(tài)代理時(shí),注意一個(gè)問題固耘。從上面的代碼题篷,可以看到,代理的參數(shù)method和args會(huì)直接給到retrofit的代理進(jìn)行反射調(diào)用厅目。所以api service接口的方法參數(shù)中不能含有l(wèi)oadPolicy番枚,cacheTime參數(shù)(因?yàn)槿绻鹥i service接口的方法參數(shù)中含有這兩個(gè)參數(shù),那么retrofit的代理反射調(diào)用method時(shí)损敷,method中就會(huì)含有l(wèi)oadPolicy葫笼,cacheTime參數(shù)。這樣肯定是不對的拗馒。那可不可以通過loadPolicy路星,cacheTime參數(shù)分析完使用哪種方式加載后,然后將這兩個(gè)參數(shù)從method中刪掉瘟忱,然后再給retrofit的代理使用奥额?不可以,因?yàn)镸ethod中沒有removeParam的api)访诱。所以垫挨,既然不能把loadPolicy,cacheTime參數(shù)添加到api service接口方法中触菜,那么loadPolicy九榔,cacheTime只能作為生成接口代理的參數(shù)的一部分。

如果需要使用接口隔離涡相,接口可以大致寫成:

  Interface ICacheNet{
  
    T serviceProxy create(String loadPolicy哲泊,String cacheTime,Class<T> service)
    
  }

這樣的話,如果需要使用cacheRetrofit催蝗,那么傳入三個(gè)參數(shù)即可切威。如果仍然使用retrofit,那么前兩個(gè)參數(shù)傳入不處理即可丙号。所以對于服務(wù)來說先朦,盡量使用接口隔離把缰冤,因?yàn)槟悴恢朗裁磿r(shí)候就需要你進(jìn)行服務(wù)替換。

大致的思路就是這樣的喳魏,當(dāng)然除了以上這些棉浸,還需要添加序列/反序列化時(shí)的處理邏輯、DiskLruCache邏輯刺彩、key邏輯(method+args+自定義key參數(shù))迷郑,當(dāng)然上面提到的“如何判斷使用緩存還是網(wǎng)絡(luò)”也需要進(jìn)一步的具體化。不過核心的東西就是以上這些创倔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗡害,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子三幻,更是在濱河造成了極大的恐慌就漾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件念搬,死亡現(xiàn)場離奇詭異抑堡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朗徊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門首妖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爷恳,你說我怎么就攤上這事有缆。” “怎么了温亲?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵棚壁,是天一觀的道長。 經(jīng)常有香客問我栈虚,道長袖外,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任魂务,我火速辦了婚禮曼验,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粘姜。我一直安慰自己鬓照,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布孤紧。 她就那樣靜靜地躺著豺裆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪号显。 梳的紋絲不亂的頭發(fā)上留储,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天翼抠,我揣著相機(jī)與錄音咙轩,去河邊找鬼获讳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛活喊,可吹牛的內(nèi)容都是我干的丐膝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼钾菊,長吁一口氣:“原來是場噩夢啊……” “哼帅矗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起煞烫,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤浑此,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滞详,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凛俱,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年料饥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒲犬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岸啡,死狀恐怖原叮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巡蘸,我是刑警寧澤奋隶,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站悦荒,受9級特大地震影響唯欣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逾冬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一黍聂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧身腻,春花似錦产还、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至她按,卻和暖如春牛隅,著一層夾襖步出監(jiān)牢的瞬間炕柔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工媒佣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匕累,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓默伍,卻偏偏與公主長得像欢嘿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子也糊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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