glide內(nèi)存緩存策略和本地文件緩存策略學(xué)習(xí)與思考

預(yù)備知識:

假設(shè)已經(jīng)熟悉了如下概念事秀,不懂暫時這么理解也可以
圖片展示的Bitmap作喘,Drawable這種最終能被安卓ImageView識別用來加載的對象琢蛤,glide用抽象Resource表示蜒犯,也就是內(nèi)存緩存是一個個Resource對象

流程加載如下

Engine.load()分析
1 加載圖片先從內(nèi)存當(dāng)前展示緩存activeResources.get(key)讀取
2 加載圖片再從內(nèi)存MemoryCache讀取,可能此緩存已經(jīng)不在界面展示
3 開啟DecodeJob,本質(zhì)上一個線程郑叠,所以會使用線程池加載,
加載之前判斷是否已經(jīng)存在一個正在運行的加載線程,因為可能存在多個加載一模一樣url的情況晕粪。
這個線程內(nèi)部先判斷文件緩存是否存在,否則網(wǎng)絡(luò)加載
為方便理解渐裸,本文內(nèi)存2種緩存類型巫湘,以展示緩存,不展示緩存區(qū)分

/**
     Check the current set of actively used resources, return the active resource if
 present, and move any newly inactive resources into the memory cache
     Check the memory cache and provide the cached resource if present
     Check the current set of in progress loads and add the cb to the in progress load if one is present.
     Start a new load.

   * Active resources are those that have been provided to at least one request and have not yet
   * been released. Once all consumers of a resource have released that resource, the resource then
   * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
   * the active resources. If the resource is evicted from the cache, its resources are recycled and
   * re-used if possible and the resource is discarded. There is no strict requirement that
   * consumers release their resources so active resources are held weakly.
  **/
  public <R> LoadStatus load() {
    
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
//加載activeResource
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }
//加載MemoryCache
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      return null;
    }
//當(dāng)前已經(jīng)存在一個 加載線程昏鹃,直接添加監(jiān)聽即可
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      return new LoadStatus(cb, current);
    }
//觸發(fā)一個線程加載
    EngineJob<R> engineJob =
        engineJobFactory.build();

    DecodeJob<R> decodeJob =
        decodeJobFactory.build();

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
//線程池加載線程
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
  }

內(nèi)存上的activeResource和MemoryCache區(qū)別

如果當(dāng)前加載一張圖片成功尚氛,當(dāng)前界面展示出來。
此時Engine監(jiān)聽到完成洞渤,觸發(fā)activeResource添加操作

public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }

如果這個ImageView重新渲染一張圖片阅嘶,那么之前上一張圖片資源就需要回收,
類似這種情況载迄,都會觸發(fā)從activeResource刪除讯柔,而往MemoryCache添加
如RequestBuilder.into(),也就是最常見的Glide.with(context).load(url).into(ImgView);

  private <Y extends Target<TranscodeType>> Y into()
    options = options.autoClone();
    Request request = buildRequest(target, targetListener, options);
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
....
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);
    return target;
  }

如上都是先clear再track,requestManager.clear(target);
RequestManager 調(diào)用clear觸發(fā)Request的clear护昧,track觸發(fā)Request的begin魂迄,
這樣再分析一下SingleRequest對應(yīng)操作active緩存的刪除,對應(yīng)MemoryCache添加

  public void clear() {
    if (resource != null) {
      releaseResource(resource);
    }
  }
private void releaseResource(Resource<?> resource) {
    engine.release(resource);
    this.resource = null;
  }
void release() {
    if (--acquired == 0) {
//監(jiān)聽器是Engine
      listener.onResourceReleased(key, this);
    }
  }
@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

本地文件寫入緩存時機

一般內(nèi)存與本地緩存都沒有需要觸發(fā)網(wǎng)絡(luò)請求惋耙,那么進入SourceGenerator這個處理網(wǎng)絡(luò)請求的捣炬,實際上是SourceFetcher處理,處理完成绽榛,走到SourceGenerator監(jiān)聽回調(diào)遥金,
這時候觀察一下,思考一下邏輯蒜田,網(wǎng)絡(luò)加載完成稿械,便存入本地
使用的也是LRU緩存。

  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

cacheData就是調(diào)用LRU文件觸發(fā)保存文件緩存
至于這個對象是從哪賦值的冲粤,參考如下美莫,走的是回調(diào),那回調(diào)是這么觸發(fā)梯捕,一般每個Generator都是走內(nèi)部Fetcher工作然后回調(diào)給自己

@Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    }
  }

網(wǎng)絡(luò)請求到本地緩存保存流程

HttpUrlFetcher.java
 @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    }

這樣一樣厢呵。回調(diào)返回的是InputStream傀顾,那么就需要琢磨怎么把這個InputStream變成保存文件
StreamEncode實現(xiàn)將InputStream轉(zhuǎn)成文件襟铭。如下

  public boolean encode() {
    byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    boolean success = false;
    OutputStream os = null;
    try {
      os = new FileOutputStream(file);
      int read;
      while ((read = data.read(buffer)) != -1) {
        os.write(buffer, 0, read);
      }
      os.close();
      success = true;
    } catch (){
     ...}
      byteArrayPool.put(buffer);
    }
    return success;
  }

本地文件讀取時機

內(nèi)存沒有,則觸發(fā)本地讀取。
如果通過映射關(guān)系找到本地已經(jīng)有這個文件寒砖,需要加載這個文件資源赐劣。

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

 Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }

LRU讀寫這里不再展開,只知道使用這種緩存策略

原圖哩都,轉(zhuǎn)圖魁兼,也不展開了

原圖即是網(wǎng)絡(luò)下載的圖片,
實際上展示可能需要轉(zhuǎn)碼壓縮之類的

總結(jié)

  • 內(nèi)存分為當(dāng)前已經(jīng)展示的緩存和界面不展示但是內(nèi)存會存下的緩存漠嵌。
  • 耗時的操作放在單獨線程操作咐汞,有本地文件緩存和網(wǎng)絡(luò)獲取。
  • 如果網(wǎng)絡(luò)獲取觸發(fā)文件保存儒鹿。
  • 一個imageview加載完成化撕,觸發(fā)active緩存添加
  • 一個imageview加載第二張圖片,觸發(fā)active緩存刪除约炎,MemoryCache緩存添加
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侯谁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子章钾,更是在濱河造成了極大的恐慌墙贱,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱傀,死亡現(xiàn)場離奇詭異惨撇,居然都是意外死亡,警方通過查閱死者的電腦和手機府寒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門魁衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人株搔,你說我怎么就攤上這事剖淀。” “怎么了纤房?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵纵隔,是天一觀的道長。 經(jīng)常有香客問我炮姨,道長捌刮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任舒岸,我火速辦了婚禮绅作,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛾派。我一直安慰自己俄认,他們只是感情好个少,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眯杏,像睡著了一般夜焦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上役拴,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天糊探,我揣著相機與錄音钾埂,去河邊找鬼河闰。 笑死,一個胖子當(dāng)著我的面吹牛褥紫,可吹牛的內(nèi)容都是我干的姜性。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼髓考,長吁一口氣:“原來是場噩夢啊……” “哼部念!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氨菇,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤儡炼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后查蓉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乌询,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年豌研,在試婚紗的時候發(fā)現(xiàn)自己被綠了妹田。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹃共,死狀恐怖鬼佣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霜浴,我是刑警寧澤晶衷,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站阴孟,受9級特大地震影響房铭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜温眉,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一缸匪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧类溢,春花似錦凌蔬、人聲如沸露懒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懈词。三九已至,卻和暖如春辩诞,著一層夾襖步出監(jiān)牢的瞬間坎弯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工译暂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抠忘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓外永,卻偏偏與公主長得像崎脉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伯顶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

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