深入理解Android中的緩存機(jī)制(三)磁盤緩存

概述

磁盤存儲(chǔ)有兩種形式椎扬,一種是File存儲(chǔ),一種是DB(DataBase)存儲(chǔ)剥哑。

File

File存儲(chǔ)比較常見,當(dāng)我們數(shù)據(jù)量較小淹父,數(shù)據(jù)的分類以及檢索沒有較大的要求的時(shí)候株婴,可以采用File存儲(chǔ)

File存在的問題:

  • 文件較大時(shí),對(duì)文件的讀取速度較慢
  • 定位暑认,讀寫具體的數(shù)據(jù)較為困難

DataBase

對(duì)數(shù)據(jù)的并發(fā)性和檢索速度有高要求的時(shí)候困介,這個(gè)時(shí)候,DB就上場(chǎng)了蘸际,DB具有如下特點(diǎn)

  • 大數(shù)據(jù)訪問速度更快
  • 索引特定條件的數(shù)據(jù)較為方便

Http緩存機(jī)制

相對(duì)于內(nèi)存緩存而言座哩,磁盤時(shí)效性很低,所以通常單獨(dú)的磁盤緩存沒有太大意義粮彤,每次去讀緩存之前需要判斷一下懁促是否有效根穷,必須要結(jié)合HTTP的緩存機(jī)制來做一些處理,這樣緩存才會(huì)比較有效导坟,所以下面還是先介紹一下HTTP緩存機(jī)制缠诅,將從緩存存儲(chǔ)策略緩存過期策略乍迄,緩存對(duì)比策略三個(gè)方面來分析一下Http的緩存機(jī)制管引。

緩存存儲(chǔ)策略

用來確定 Http 響應(yīng)內(nèi)容是否可以被客戶端緩存,以及可以被哪些客戶端緩存

對(duì)于 Cache-Control 頭里的 Public闯两、Private褥伴、no-cache谅将、max-age 、no-store 他們都是用來指明響應(yīng)內(nèi)容是否可以被客戶端存儲(chǔ)的重慢,其中前4個(gè)都會(huì)緩存文件數(shù)據(jù)(關(guān)于 no-cache 應(yīng)理解為“不建議使用本地緩存”饥臂,其仍然會(huì)緩存數(shù)據(jù)到本地),后者 no-store 則不會(huì)在客戶端緩存任何響應(yīng)數(shù)據(jù)似踱。另關(guān)于 no-cache 和 max-age 有點(diǎn)特別隅熙,我認(rèn)為它是一種混合體,下面我會(huì)講到核芽。

通過 Cache-Control:Public 設(shè)置我們可以將 Http 響應(yīng)數(shù)據(jù)存儲(chǔ)到本地囚戚,但此時(shí)并不意味著后續(xù)瀏覽器會(huì)直接從緩存中讀取數(shù)據(jù)并使用,為啥轧简?因?yàn)樗鼰o法確定本地緩存的數(shù)據(jù)是否可用(可能已經(jīng)失效)驰坊,還必須借助一套鑒別機(jī)制來確認(rèn)才行, 這就是我們下面要講到的“緩存過期策略”哮独。

緩存過期策略

客戶端用來確認(rèn)存儲(chǔ)在本地的緩存數(shù)據(jù)是否已過期拳芙,進(jìn)而決定是否要發(fā)請(qǐng)求到服務(wù)端獲取數(shù)據(jù)


剛上面我們已經(jīng)闡述了數(shù)據(jù)緩存到了本地后還需要經(jīng)過判斷才能使用,那么瀏覽器通過什么條件來判斷呢皮璧? 答案是:Expires舟扎,Expires 指名了緩存數(shù)據(jù)有效的絕對(duì)時(shí)間,告訴客戶端到了這個(gè)時(shí)間點(diǎn)(比照客戶端時(shí)間點(diǎn))后本地緩存就作廢了悴务,在這個(gè)時(shí)間點(diǎn)內(nèi)客戶端可以認(rèn)為緩存數(shù)據(jù)有效浆竭,可直接從緩存中加載展示。

不過 Http 緩存頭設(shè)計(jì)并沒有想象的那么規(guī)矩惨寿,像上面提到的 Cache-Control(這個(gè)頭是在Http1.1里加進(jìn)來的)頭里的 no-cache 和 max-age 就是特例,它們既包含緩存存儲(chǔ)策略也包含緩存過期策略删窒,以 max-age 為例裂垦,他實(shí)際上相當(dāng)于:

Cache-Control:public/private
Expires:當(dāng)前客戶端時(shí)間 + maxAge 。

而 Cache-Control:no-cache 和 Cache-Control:max-age=0 (單位是秒)

這里需要注意的是:

  1. Cache-Control 中指定的緩存過期策略優(yōu)先級(jí)高于 Expires肌索,當(dāng)它們同時(shí)存在的時(shí)候蕉拢,后者會(huì)被覆蓋掉。
  2. 緩存數(shù)據(jù)標(biāo)記為已過期只是告訴客戶端不能再直接從本地讀取緩存了诚亚,需要再發(fā)一次請(qǐng)求到服務(wù)器去確認(rèn)晕换,并不等同于本地緩存數(shù)據(jù)從此就沒用了,有些情況下即使過期了還是會(huì)被再次用到站宗,具體下面會(huì)講到闸准。

緩存對(duì)比策略

將緩存在客戶端的數(shù)據(jù)標(biāo)識(shí)發(fā)往服務(wù)端,服務(wù)端通過標(biāo)識(shí)來判斷客戶端 緩存數(shù)據(jù)是否仍有效梢灭,進(jìn)而決定是否要重發(fā)數(shù)據(jù)夷家。

客戶端檢測(cè)到數(shù)據(jù)過期或?yàn)g覽器刷新后蒸其,往往會(huì)重新發(fā)起一個(gè) http 請(qǐng)求到服務(wù)器,服務(wù)器此時(shí)并不急于返回?cái)?shù)據(jù)库快,而是看請(qǐng)求頭有沒有帶標(biāo)識(shí)( If-Modified-Since摸袁、If-None-Match)過來,如果判斷標(biāo)識(shí)仍然有效义屏,則返回304告訴客戶端取本地緩存數(shù)據(jù)來用即可(這里要注意的是你必須要在首次響應(yīng)時(shí)輸出相應(yīng)的頭信息(Last-Modified靠汁、ETags)到客戶端)。至此我們就明白了上面所說的本地緩存數(shù)據(jù)即使被認(rèn)為過期闽铐,并不等于數(shù)據(jù)從此就沒用了的道理了蝶怔。

Android中的磁盤緩存

很多時(shí)候我們都會(huì)說一些圖片加載框架使用了兩級(jí)或者三級(jí)緩存,然后就會(huì)說先從內(nèi)存中取阳啥,然后再?gòu)拇疟P中取添谊,最后再?gòu)木W(wǎng)絡(luò)中去取,我們現(xiàn)在按照這個(gè)思路來分析一下Picasso察迟,Picasso一開始就從內(nèi)存中去讀斩狱,然后就會(huì)去進(jìn)行網(wǎng)絡(luò)請(qǐng)求,如果內(nèi)存中沒有讀取到扎瓶,他就會(huì)去生成一個(gè)Request所踊,去請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù),他為什么沒有直接去讀磁盤緩存概荷,這個(gè)時(shí)候你可能會(huì)說番甩,Picasso默認(rèn)沒有設(shè)置磁盤緩存棠涮,只要當(dāng)設(shè)置了OkHttp.Downloader之后才會(huì)進(jìn)行磁盤緩存,實(shí)際上不是這樣的,Picasso是有磁盤緩存的乞娄,因?yàn)樗木彺嬉蕾囉贖TTP緩存機(jī)制,所以每次是在請(qǐng)求之后根據(jù)Response的響應(yīng)頭去看是否讀取內(nèi)存緩存咧最,當(dāng)Picasso在build的時(shí)候翩腐,如果沒有設(shè)置DownLoader,他會(huì)自己去設(shè)置一個(gè)Downloader

  public Picasso build() {
    Context context = this.context;
    if (downloader == null) {
      //創(chuàng)建一個(gè)默認(rèn)的Downloader
      downloader = Utils.createDefaultDownloader(context);
    }
    return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
        defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
  }
}

繼續(xù)查看createDefaultDownloader蓝谨,如果沒有OkhttpDownloader灌具,那么就會(huì)采用UrlConnectionDownloader

static Downloader createDefaultDownloader(Context context) {
  try {
    Class.forName("com.squareup.okhttp.OkHttpClient");
    return OkHttpLoaderCreator.create(context);
  } catch (ClassNotFoundException ignored) {
  }
  return new UrlConnectionDownloader(context);
}

然后我們就分開查看,因?yàn)镺kHttpLoader是在UrlConnectionDownloader的基礎(chǔ)上進(jìn)行改良的,所以我們先查看一下UrlConnectionDownloader譬巫,也就是load方法

UrlConnectionDownloader

@Override 
public Response load(Uri uri, int networkPolicy) throws IOException {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    installCacheIfNeeded(context);
  }
  HttpURLConnection connection = openConnection(uri);
  connection.setUseCaches(true);
  if (networkPolicy != 0) {
    String headerValue;
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
      headerValue = FORCE_CACHE;
    } else {
      StringBuilder builder = CACHE_HEADER_BUILDER.get();
      builder.setLength(0);
      if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
        builder.append("no-cache");
      }
      if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        if (builder.length() > 0) {
          builder.append(',');
        }
        builder.append("no-store");
      }
      headerValue = builder.toString();
    }
    //設(shè)置緩存策略
    connection.setRequestProperty("Cache-Control", headerValue);
  }

  int responseCode = connection.getResponseCode();
  if (responseCode >= 300) {
    connection.disconnect();
    throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
        networkPolicy, responseCode);
  }
long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
  //我們根據(jù)服務(wù)端返回的Response的Header來判斷是走緩存還是重新取數(shù)據(jù)
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
  return new Response(connection.getInputStream(), fromCache, contentLength);
}

緊接著看一下UrlConnectionDownloader

OkHttpDownloader

@Override 
public Response load(Uri uri, int networkPolicy) throws IOException {
  CacheControl cacheControl = null;
  if (networkPolicy != 0) {
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
      cacheControl = CacheControl.FORCE_CACHE;
    } else {
      CacheControl.Builder builder = new CacheControl.Builder();
      if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
        builder.noCache();
      }
      if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        builder.noStore();
      }
      cacheControl = builder.build();
    }
  }

  Request.Builder builder = new Request.Builder().url(uri.toString());
  if (cacheControl != null) {
    //設(shè)置緩存策略
    builder.cacheControl(cacheControl);
  }
  com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
  int responseCode = response.code();
  if (responseCode >= 300) {
    response.body().close();
    throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
        responseCode);
  }
 //是否讀取緩存的標(biāo)志
  boolean fromCache = response.cacheResponse() != null;
  ResponseBody responseBody = response.body();
  return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
}

存儲(chǔ)目錄

在開發(fā)Android的過程中咖楣,也會(huì)涉及到很多的IO操作,比如說網(wǎng)絡(luò)請(qǐng)求芦昔,下載圖片等诱贿,由于很多框架平時(shí)已經(jīng)幫我們封裝好了,所以平時(shí)容易忽略咕缎,下面簡(jiǎn)單分析一下Android下的存儲(chǔ)目錄:


Android平臺(tái)的存儲(chǔ)目錄

內(nèi)部存儲(chǔ)

data文件夾就是我們常說的內(nèi)部存儲(chǔ)瘪松,對(duì)于沒有root的手機(jī)來說咸作,我們是沒有權(quán)限打開這個(gè)文件夾的但是可以訪問到,

外部存儲(chǔ)

外部存儲(chǔ)才是我們平時(shí)操作最多的宵睦,外部存儲(chǔ)一般就是我們上面看到的storage文件夾记罚,當(dāng)然也有可能是mnt文件夾,這個(gè)名稱不影響我們操作數(shù)據(jù)壳嚎。

路徑獲取

兩種存儲(chǔ)方式都是通過Context類來進(jìn)行獲取的

內(nèi)部存儲(chǔ)

   getFilesDir();//獲取內(nèi)部存儲(chǔ)的File路徑
   getCacheDir();//獲取內(nèi)部存儲(chǔ)的Cache路徑
   getDatabasePath("demo.db");//獲取database路徑
   getSharedPreferences("demo",MODE_PRIVATE);//獲取SP

外部存儲(chǔ)

   getExternalCacheDir();//獲取外部存儲(chǔ)私有目錄
   getExternalFilesDir(Environment.DIRECTORY_DCIM);//獲取外部存儲(chǔ)公有目錄
      

清除緩存/清除數(shù)據(jù)

清除緩存:緩存是程序運(yùn)行時(shí)的臨時(shí)存儲(chǔ)空間桐智,它可以存放從網(wǎng)絡(luò)下載的臨時(shí)圖片,從用戶的角度出發(fā)清除緩存對(duì)用戶并沒有太大的影響烟馅,但是清除緩存后用戶再次使用該APP時(shí)说庭,由于本地緩存已經(jīng)被清理,所有的數(shù)據(jù)需要重新從網(wǎng)絡(luò)上獲取郑趁,注意:為了在清除緩存的時(shí)候能夠正常清除與應(yīng)用相關(guān)的緩存刊驴,請(qǐng)將緩存文件存放在getCacheDir()或者 getExternalCacheDir()路徑下。
清除數(shù)據(jù):清除用戶配置寡润,比如SharedPreferences捆憎、數(shù)據(jù)庫(kù)等等,這些數(shù)據(jù)都是在程序運(yùn)行過程中保存的用戶配置信息梭纹,清除數(shù)據(jù)后躲惰,下次進(jìn)入程序就和第一次進(jìn)入程序時(shí)一樣

關(guān)于權(quán)限

Android6.0以后,谷歌加強(qiáng)了對(duì)用戶權(quán)限的控制变抽,但是這個(gè)權(quán)限只是針對(duì)于外部存儲(chǔ)的公有目錄础拨,對(duì)于私有目錄的,仍然可以正常訪問绍载。所以當(dāng)遇到有些手機(jī)權(quán)限很難適配的時(shí)候可以把文件存儲(chǔ)在外部存儲(chǔ)的私有目錄诡宗。

總結(jié)

磁盤緩存在Android中需要注意訪問外部存儲(chǔ)時(shí)候需要權(quán)限,注意各個(gè)不同路徑下的區(qū)別击儡,同時(shí)需要結(jié)合Http緩存注意緩存的時(shí)效性塔沃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市曙痘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌立肘,老刑警劉巖边坤,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谅年,居然都是意外死亡茧痒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門融蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旺订,“玉大人弄企,你說我怎么就攤上這事∏” “怎么了拘领?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)樱调。 經(jīng)常有香客問我约素,道長(zhǎng),這世上最難降的妖魔是什么笆凌? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任圣猎,我火速辦了婚禮,結(jié)果婚禮上乞而,老公的妹妹穿的比我還像新娘送悔。我一直安慰自己,他們只是感情好爪模,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布欠啤。 她就那樣靜靜地躺著,像睡著了一般呻右。 火紅的嫁衣襯著肌膚如雪跪妥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天声滥,我揣著相機(jī)與錄音眉撵,去河邊找鬼。 笑死落塑,一個(gè)胖子當(dāng)著我的面吹牛纽疟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播憾赁,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼污朽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了龙考?” 一聲冷哼從身側(cè)響起蟆肆,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晦款,沒想到半個(gè)月后炎功,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缓溅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蛇损,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淤齐,死狀恐怖股囊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情更啄,我是刑警寧澤稚疹,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站锈死,受9級(jí)特大地震影響贫堰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜待牵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一其屏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缨该,春花似錦偎行、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至膨更,卻和暖如春妙真,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荚守。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工珍德, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矗漾。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓锈候,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親敞贡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泵琳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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

  • 0. 前言 前面有被用戶投訴 APP 流量消耗厲害: 于是乎考慮了流量方面的問題。暫時(shí) APP 中涉及流量的幾個(gè)方...
    zyl06閱讀 24,000評(píng)論 5 62
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理誊役,服務(wù)發(fā)現(xiàn)获列,斷路器,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • 理論總結(jié) 它要解決什么樣的問題蛔垢? 數(shù)據(jù)的訪問击孩、存取、計(jì)算太慢啦桌、太不穩(wěn)定溯壶、太消耗資源,同時(shí)甫男,這樣的操作存在重復(fù)性且改。因...
    jiangmo閱讀 2,849評(píng)論 0 11
  • 今天在補(bǔ)劉潤(rùn)五分鐘,我很喜歡劉潤(rùn)老師的專欄板驳,所以也都會(huì)很認(rèn)真的聽又跛,今天聽到一篇值得分享的內(nèi)容,題目為事實(shí)有真假若治,觀...
    Gzw丶南山閱讀 599評(píng)論 0 0