Glide源碼分析五——緩存相關(guān)

DataSource(數(shù)據(jù)源)

數(shù)據(jù)源有5種類型:

  1. LOCAL:本地?cái)?shù)據(jù)盒延,例如本地圖片文件,也可能是通過ContentProvider共享的遠(yuǎn)程數(shù)據(jù),比如在ContentProvider中進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
  2. REMOTE:遠(yuǎn)程數(shù)據(jù),例如通過網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)刺覆。
  3. DATA_DISK_CACHE:緩存在磁盤的原始數(shù)據(jù)。
  4. RESOURCE_DISK_CACHE:緩存在磁盤的解碼史煎、變換后的數(shù)據(jù)谦屑。
  5. MEMORY_CACHE:存儲(chǔ)在內(nèi)存中的數(shù)據(jù)。

DiskCacheStrategy(磁盤緩存策略)

我們知道篇梭,在使用Glide進(jìn)行請(qǐng)求時(shí)氢橙,可以設(shè)置磁盤緩存策略。之后在DataFetcherGenerator請(qǐng)求數(shù)據(jù)成功后恬偷,會(huì)根據(jù)磁盤緩存策略DiskCacheStrategy和LoadData的Fetcher對(duì)應(yīng)的DataSource悍手,來決定是否緩存數(shù)據(jù)。

主要有:

  1. ALL(原始數(shù)據(jù)和處理后的數(shù)據(jù)都緩存)
  2. NONE(都不緩存)
  3. DATA(只緩存原始數(shù)據(jù))
  4. RESOURCE(只緩存處理后的數(shù)據(jù)袍患,即經(jīng)過縮放等轉(zhuǎn)換后的數(shù)據(jù))
  5. AUTOMATIC(它會(huì)嘗試對(duì)本地和遠(yuǎn)程圖片使用最佳的策略坦康。

當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如,從URL下載)時(shí)诡延,AUTOMATIC 策略僅會(huì)存儲(chǔ)未被你的加載過程修改過(比如滞欠,變換,裁剪–譯者注)的原始數(shù)據(jù)肆良,因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多筛璧。對(duì)于本地?cái)?shù)據(jù)逸绎,AUTOMATIC 策略則會(huì)僅存儲(chǔ)變換過的縮略圖,因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片夭谤,取回原始數(shù)據(jù)也很容易棺牧。默認(rèn)使用這種磁盤緩存策略)

// DiskCacheStrategy.java
public abstract class DiskCacheStrategy {
  
  // 原始數(shù)據(jù)是否可以緩存
  public abstract boolean isDataCacheable(DataSource dataSource);
  
  // 解碼(變換)后的數(shù)據(jù)是否可以緩存
  public abstract boolean isResourceCacheable(
      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
      
  // 是否可以解碼之前緩存的解碼(變換)后的數(shù)據(jù)
  public abstract boolean decodeCachedResource();
  
  // 是否可以解碼之前緩存的原始數(shù)據(jù)
  public abstract boolean decodeCachedData();

  public static final DiskCacheStrategy ALL =
      new DiskCacheStrategy() {
      
        // 如果是遠(yuǎn)程數(shù)據(jù),則可以緩存成原始數(shù)據(jù)
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }

        // 如果是本地?cái)?shù)據(jù)朗儒、遠(yuǎn)程數(shù)據(jù)和緩存在磁盤中的原始數(shù)據(jù)颊乘,則可以緩存成解碼、變換后的數(shù)據(jù)RESOURCE_DISK_CACHE
        // 因?yàn)榫彺嬖诖疟P中的解碼醉锄、變換后的數(shù)據(jù)RESOURCE_DISK_CACHE沒有必要再次緩存疲牵,
        // 而內(nèi)存中如果有數(shù)據(jù)的話,說明之前已經(jīng)通過其他4種方式加載過資源了榆鼠,如果可以緩存的話已經(jīng)緩存了,也就沒必要緩存內(nèi)存數(shù)據(jù)了
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return dataSource != DataSource.RESOURCE_DISK_CACHE
              && dataSource != DataSource.MEMORY_CACHE;
        }

        // 可以解碼緩存在磁盤中的解碼亥鸠、變換后的數(shù)據(jù)
        @Override
        public boolean decodeCachedResource() {
          return true;
        }

        // 可以解碼緩存在磁盤中的原始數(shù)據(jù)
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };
      
  public static final DiskCacheStrategy AUTOMATIC =
      new DiskCacheStrategy() {
      
        // 如果是遠(yuǎn)程數(shù)據(jù)妆够,則可以緩存成原始數(shù)據(jù)
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }

        // 如果是緩存在磁盤的原始數(shù)據(jù)或 需要transform的本地?cái)?shù)據(jù),則可以緩存成解碼负蚊、變換后的數(shù)據(jù)
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
                  || dataSource == DataSource.LOCAL)
              && encodeStrategy == EncodeStrategy.TRANSFORMED;
        }

        // 可以解碼緩存在磁盤中的解碼神妹、變換后的數(shù)據(jù)
        @Override
        public boolean decodeCachedResource() {
          return true;
        }

        // 可以解碼緩存在磁盤中的原始數(shù)據(jù)
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };
}

BitmapPool

BitmapPool.png
  • Android 2.3.3(API 級(jí)別 10)及以下的版本:Bitmap內(nèi)存在Native層,使用Bitmap對(duì)象的recycle方法回收內(nèi)存家妆,無法復(fù)用鸵荠。

  • 從Android3.0開始到8.0以前,bitmap對(duì)象存儲(chǔ)在java堆內(nèi)存中伤极,java虛擬機(jī)會(huì)自動(dòng)回收內(nèi)存蛹找。

  • 在8.0以后Bitmap內(nèi)存在Native層,所以我們要手動(dòng)釋放bitmap哨坪。

在 Android 4.4(API 級(jí)別 19)之前(3.0-4.3)復(fù)用bitmap的前提是:

  1. 被解碼的圖像必須是 JPEG 或 PNG 格式
  2. 被復(fù)用的圖像寬高必須等于 解碼后的圖像寬高
  3. 解碼圖像的 BitmapFactory.Options.inSampleSize 設(shè)置為 1 , 也就是不能縮放
  4. 被復(fù)用的圖像的像素格式 Config ( 如 RGB_565 ) 會(huì)覆蓋設(shè)置的 BitmapFactory.Options.inPreferredConfig 參數(shù)

在 4.4 之后只要 inBitmap 的大小比目標(biāo) Bitmap 大即可庸疾,且解碼圖像的BitmapFactory.Options.inSampleSize 可以大于1。

BitmapPool是一個(gè)Bitmap對(duì)象池当编,用于復(fù)用已有的未recycle的Bitmap對(duì)象届慈。
它主要有兩個(gè)子類:

  1. BitmapPoolAdapter,空實(shí)現(xiàn)
  2. LruBitmapPool忿偷,基于LruPoolStrategy策略來緩存對(duì)象金顿。

GlideBuilder在創(chuàng)建Glide時(shí),根據(jù)memorySizeCalculator.getBitmapPoolSize()獲取設(shè)備推薦的bitmap緩存池大欣鹎拧(字節(jié)為單位)揍拆,如果大于0,則使用LruBitmapPool茶凳,否則使用BitmapPoolAdapter礁凡。

我們也可以自己實(shí)現(xiàn)BitmapPool高氮,設(shè)置給Glide。

MemoryCache(二級(jí)內(nèi)存緩存)

MemoryCache.png

主要有兩個(gè)子類:

  1. MemoryCacheAdapter:空實(shí)現(xiàn)
  2. LruResourceCache:默認(rèn)實(shí)現(xiàn)顷牌,繼承自LruCache剪芍,實(shí)現(xiàn)了MemoryCache,內(nèi)部使用LinkedHashMap來實(shí)現(xiàn)LRU窟蓝。

LruResourceCache的key是一個(gè)自定義的Key類對(duì)象罪裹,維護(hù)了width、height运挫、url等信息状共。
LruResourceCache的value是一個(gè)Resource<?>包裝類,因?yàn)镚lide支持多種類型的結(jié)果谁帕,例如Bitmap峡继、Drawable等。

對(duì)于低內(nèi)存設(shè)備匈挖,默認(rèn)的緩存大小是應(yīng)用內(nèi)存(系統(tǒng)為每個(gè)應(yīng)用分配的近似內(nèi)存值)的33%碾牌,非低內(nèi)存設(shè)備則是40%。ps:這里的緩存包括:Resouce的緩存儡循、Bitmap對(duì)象池舶吗、數(shù)組對(duì)象池,這三種都采用了Lru算法進(jìn)行緩存择膝。

我們也可以自己實(shí)現(xiàn)MemoryCache類設(shè)置給Glide誓琼。

ActiveResources(一級(jí)內(nèi)存緩存)

ActiveResources.png

正在被使用的資源會(huì)被放入ActiveResources。

// ActiveResources.java
final class ActiveResources {

  // 是否允許正在使用的資源被保留(如果是的話肴捉,在ResourceWeakReference中會(huì)強(qiáng)引用該資源腹侣,release時(shí)需要手動(dòng)置空)
  private final boolean isActiveResourceRetentionAllowed;
  
  // 用于將resourceReferenceQueue中被回收的ResourceWeakReference從activeEngineResources中移除
  private final Executor monitorClearedResourcesExecutor;
  
  // 正在被使用的資源
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  
  // 當(dāng)ResourceWeakReference引用的資源被回收時(shí),ResourceWeakReference會(huì)被放入該隊(duì)列
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

  // 資源釋放監(jiān)聽
  private ResourceListener listener;

  // 是否中斷齿穗,是的話筐带,monitorClearedResourcesExecutor將會(huì)停止工作
  private volatile boolean isShutdown;
  
  
  ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            // 執(zhí)行清理工作,將resourceReferenceQueue中被回收的ResourceWeakReference從activeEngineResources中移除
            cleanReferenceQueue();
          }
        });
  }
  
  // 將正在使用的資源包裝成ResourceWeakReference缤灵,并放入activeEngineResources
  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    // 如果該key之前已經(jīng)有對(duì)應(yīng)的資源了伦籍,則清理舊資源
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

  // 將key對(duì)應(yīng)的ResourceWeakReference從activeEngineResources中移除,并清理舊資源
  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }
  
  // 將ResourceWeakReference從activeEngineResources中移除腮出,如果ResourceWeakReference強(qiáng)引用了資源帖鸦,則回調(diào)通知Engine放入MemoryCache中
  void cleanupActiveReference(ResourceWeakReference ref) {
    synchronized (this) {
    
      // 將key對(duì)應(yīng)的ResourceWeakReference從activeEngineResources中移除
      activeEngineResources.remove(ref.key);

      // 如果資源沒有被強(qiáng)引用則return
      if (!ref.isCacheable || ref.resource == null) {
        return;
      }
    }

    // 如果資源被強(qiáng)引用了,則回調(diào)通知Engine放入MemoryCache中
    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);
  }
  
  // 執(zhí)行清理工作胚嘲,將resourceReferenceQueue中被回收的ResourceWeakReference從activeEngineResources中移除
  void cleanReferenceQueue() {
    while (!isShutdown) {
        
        // 從隊(duì)列中獲取被回收的資源作儿,當(dāng)隊(duì)列沒數(shù)據(jù)時(shí)會(huì)阻塞在這里
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        
        // 清理ResourceWeakReference及其引用資源
        cleanupActiveReference(ref);

        // ... 回調(diào)通知ResourceWeakReference從resourceReferenceQueue中移除了
      
    }
  }
  
  // 關(guān)閉清理資源的線程池
  void shutdown() {
    isShutdown = true;
    if (monitorClearedResourcesExecutor instanceof ExecutorService) {
      ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;
      Executors.shutdownAndAwaitTermination(service);
    }
  }
  
  // 弱引用類,用于引用正在使用的資源馋劈,當(dāng)資源被回收時(shí)會(huì)被放入resourceReferenceQueue攻锰,之后會(huì)在線程池中將它從activeEngineResources中移除晾嘶,如果被引用的資源是可緩存在內(nèi)存中的,則會(huì)強(qiáng)引用資源娶吞,在被清理時(shí)垒迂,會(huì)回調(diào)到Engine中放入MemoryCache中
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    
    // 資源的key
    final Key key;

    // 資源是否可以緩存在內(nèi)存中
    final boolean isCacheable;

    // 被引用的資源(如果資源是可緩存在內(nèi)存中的,則不為null)
    Resource<?> resource;

   
    ResourceWeakReference(
        @NonNull Key key,
        @NonNull EngineResource<?> referent,
        @NonNull ReferenceQueue<? super EngineResource<?>> queue,
        boolean isActiveResourceRetentionAllowed) {
      super(referent, queue);
      this.key = Preconditions.checkNotNull(key);
      
      // 如果資源可以緩存在內(nèi)存中妒蛇,且允許持有正在使用的資源机断,則強(qiáng)引用資源
      this.resource =
          referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource())
              : null;
              
      isCacheable = referent.isMemoryCacheable();
    }

    // 清理資源
    void reset() {
      resource = null;
      clear();
    }
  }
  
}  

通過上面代碼的注釋,可以知道:

  1. 正在使用的資源會(huì)被轉(zhuǎn)成弱引用ResourceWeakReference绣夺,保存在ActiveResouces的activeEngineResources中吏奸。
  2. 當(dāng)資源被jvm回收時(shí),ResourceWeakReference會(huì)被放入隊(duì)列中陶耍。
  3. ActiveResources中有個(gè)線程池奋蔚,會(huì)循環(huán)從隊(duì)列中取被回收資源的ResourceWeakReference,將它從activeEngineResources中移除烈钞。如果ResourceWeakReference.resource不為null(條件:isActiveResourceRetentionAllowed為true泊碑,且該資源可以緩存在內(nèi)存中),則會(huì)回調(diào)到Engine中將資源加入MemoryCache棵磷。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市晋涣,隨后出現(xiàn)的幾起案子仪媒,更是在濱河造成了極大的恐慌,老刑警劉巖谢鹊,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件算吩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡佃扼,警方通過查閱死者的電腦和手機(jī)偎巢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兼耀,“玉大人压昼,你說我怎么就攤上這事×鲈耍” “怎么了窍霞?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拯坟。 經(jīng)常有香客問我但金,道長,這世上最難降的妖魔是什么郁季? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任冷溃,我火速辦了婚禮钱磅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘似枕。我一直安慰自己盖淡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布菠净。 她就那樣靜靜地躺著禁舷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毅往。 梳的紋絲不亂的頭發(fā)上牵咙,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音攀唯,去河邊找鬼。 笑死侯嘀,一個(gè)胖子當(dāng)著我的面吹牛另凌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戒幔,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼吠谢,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了诗茎?” 一聲冷哼從身側(cè)響起工坊,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敢订,沒想到半個(gè)月后王污,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楚午,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昭齐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾柜。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阱驾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怪蔑,到底是詐尸還是另有隱情啊易,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布饮睬,位于F島的核電站租谈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜割去,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一窟却、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呻逆,春花似錦夸赫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宜雀,卻和暖如春切平,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辐董。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工悴品, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人简烘。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓苔严,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孤澎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子届氢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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