Glide源碼分析-緩存與復(fù)用機(jī)制

1 Glide緩存與復(fù)用機(jī)制簡介

1.1 Glide的資源狀態(tài)可以分為四種

  1. Active Resources:有其他View正在展示這張圖片
  2. Memory cache:該圖片被存進(jìn)內(nèi)存中
  3. Resource:經(jīng)過decode蚌堵、transformed后的緩存
  4. Data:原始的沒有修改過的數(shù)據(jù)緩存

Glide讀取緩存也是依次從上面四種狀態(tài)的緩存中讀取,如果都未能找到圖片,則Glide會(huì)返回到原始資源以取回?cái)?shù)據(jù)(原始文件肢扯,Uri, Url等)

1.2 Glide中Bitmap復(fù)用機(jī)制

復(fù)用:

將已經(jīng)不需要使用的數(shù)據(jù)空間重新拿來使用,減少內(nèi)存抖動(dòng)(指在短時(shí)間內(nèi)有大量的對象被創(chuàng)建或者被回收的現(xiàn)象)

原理:

inMutable是Glide能夠復(fù)用Bitmap的基石,是BitmapFactory提供的一個(gè)參數(shù),表示該Bitmap是可變的我注,支持復(fù)用的。BitmapFactory.Options中提供了兩個(gè)屬性:inMutable迟隅、inBitmap但骨。當(dāng)進(jìn)行Bitmap復(fù)用時(shí),需要設(shè)置inMutable為true智袭,inBitmap設(shè)置被復(fù)用的已經(jīng)存在的Bitmap奔缠。Bitmap復(fù)用池使用LRU算法實(shí)現(xiàn)。

Bitmap復(fù)用使用條件:
  • 在Android 4.4之前吼野,僅支持相同大小的Bitmap校哎,inSampleSize必須為1,而且必須采用jpeg或png格式瞳步。
  • 在Android 4.4之后只有一個(gè)限制闷哆,就是被復(fù)用的Bitmap尺寸要大于 新的bitmap,簡單來說就是大圖可以給小圖復(fù)用单起。

2 緩存源碼流程

Glide源碼分析-網(wǎng)絡(luò)圖片加載主流程分析一文中抱怔,我們已經(jīng)知道m(xù)emory cache和disk cache在Glide創(chuàng)建的時(shí)候也被創(chuàng)建了,Glide創(chuàng)建的代碼在GlideBuilder.build(Context)方法

@NonNull
Glide build(@NonNull Context context) {
  if (memoryCache == null) {
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
  }

  if (diskCacheFactory == null) {
    diskCacheFactory = new InternalCacheDiskCacheFactory(context);
  }

  if (engine == null) {
    engine =
        new Engine(
            memoryCache,
            diskCacheFactory,
            ...);
  }

  return new Glide(
      ...
      memoryCache,
      ...);
}

2.1 memoryCache

通過代碼可以看到 memoryCache 被放入 Engine 和 Glide 實(shí)例中嘀倒。在Engine中利用memoryCache進(jìn)行存取操作屈留,Glide 實(shí)例中的memoryCache是用來在內(nèi)存緊張的時(shí)候,通知memoryCache釋放內(nèi)存测蘑。Glide實(shí)現(xiàn)了ComponentCallbacks2接口灌危,在Glide創(chuàng)建完成后,通過applicationContext.registerComponentCallbacks(glide)似的 Glide 實(shí)例可以監(jiān)聽內(nèi)存緊張的信號帮寻。


// Glide
@Override
public void onTrimMemory(int level) {
  trimMemory(level);
}

public void trimMemory(int level) {
  // Engine asserts this anyway when removing resources, fail faster and consistently
  Util.assertMainThread();
  // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
  memoryCache.trimMemory(level);
  bitmapPool.trimMemory(level);
  arrayPool.trimMemory(level);
}

memoryCache是一個(gè)使用LRU(least recently used)算法實(shí)現(xiàn)的內(nèi)存緩存類LruResourceCache,繼承至LruCache類赠摇,并實(shí)現(xiàn)了MemoryCache接口固逗。LruCache定義了LRU算法實(shí)現(xiàn)相關(guān)的操作浅蚪,而MemoryCache定義的是內(nèi)存緩存相關(guān)的操作。

LruCache 的實(shí)現(xiàn)是利用了 LinkedHashMap 的這種數(shù)據(jù)結(jié)構(gòu)的一個(gè)特性( accessOrder=true 基于訪問順序 )再加上對 LinkedHashMap 的數(shù)據(jù)操作上鎖實(shí)現(xiàn)的緩存策略烫罩。

當(dāng)調(diào)用 put()方法時(shí)惜傲,就會(huì)在集合中添加元素,并調(diào)用
trimToSize()判斷緩存是否已滿贝攒,如果滿了就用 LinkedHashMap 的迭代器刪除隊(duì)尾元素盗誊,即近期最少訪問的元素。

當(dāng)調(diào)用 get()方法訪問緩存對象時(shí)隘弊,就會(huì)調(diào)用 LinkedHashMap 的 get()方法獲得對應(yīng)集合元素哈踱,同時(shí)會(huì)更新該元素到隊(duì)頭。

2.2 diskCacheFactory

diskCacheFactory是創(chuàng)建DiskCache的Factory梨熙,DiskCache接口定義

public interface DiskCache {

  interface Factory {
    /** 250 MB of cache. */
    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

    @Nullable
    DiskCache build();
  }

  interface Writer {
    boolean write(@NonNull File file);
  }

  @Nullable
  File get(Key key);

  void put(Key key, Writer writer);

  @SuppressWarnings("unused")
  void delete(Key key);

  void clear();
}

接著再來看下DiskCache.Factory的默認(rèn)實(shí)現(xiàn):InternalCacheDiskCacheFactory

public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {

  public InternalCacheDiskCacheFactory(Context context) {
    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR,
        DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);
  }

  public InternalCacheDiskCacheFactory(Context context, long diskCacheSize) {
    this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);
  }

  public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
                                       long diskCacheSize) {
    super(new CacheDirectoryGetter() {
      @Override
      public File getCacheDirectory() {
        File cacheDirectory = context.getCacheDir();
        if (cacheDirectory == null) {
          return null;
        }
        if (diskCacheName != null) {
          return new File(cacheDirectory, diskCacheName);
        }
        return cacheDirectory;
      }
    }, diskCacheSize);
  }
}

由以上代碼可以看出:默認(rèn)會(huì)創(chuàng)建一個(gè)250M的緩存目錄开镣,其路徑為/data/data/{package}/cache/image_manager_disk_cache/

繼續(xù)看其父類DiskLruCacheFactory的代碼

public class DiskLruCacheFactory implements DiskCache.Factory {
  private final long diskCacheSize;
  private final CacheDirectoryGetter cacheDirectoryGetter;

  public interface CacheDirectoryGetter {
    File getCacheDirectory();
  }
  
  ...
  
  public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long diskCacheSize) {
    this.diskCacheSize = diskCacheSize;
    this.cacheDirectoryGetter = cacheDirectoryGetter;
  }

  @Override
  public DiskCache build() {
    File cacheDir = cacheDirectoryGetter.getCacheDirectory();

    if (cacheDir == null) {
      return null;
    }

    if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
      return null;
    }

    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
  }
}

DiskLruCacheFactory.build()方法會(huì)返回一個(gè)DiskLruCacheWrapper類的實(shí)例,看下DiskLruCacheWrapper的實(shí)現(xiàn)

public class DiskLruCacheWrapper implements DiskCache {
  private static final String TAG = "DiskLruCacheWrapper";

  private static final int APP_VERSION = 1;
  private static final int VALUE_COUNT = 1;
  private static DiskLruCacheWrapper wrapper;

  private final SafeKeyGenerator safeKeyGenerator;
  private final File directory;
  private final long maxSize;
  private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
  private DiskLruCache diskLruCache;

  @SuppressWarnings("deprecation")
  public static DiskCache create(File directory, long maxSize) {
    return new DiskLruCacheWrapper(directory, maxSize);
  }

  @Deprecated
  @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
  protected DiskLruCacheWrapper(File directory, long maxSize) {
    this.directory = directory;
    this.maxSize = maxSize;
    this.safeKeyGenerator = new SafeKeyGenerator();
  }

  private synchronized DiskLruCache getDiskCache() throws IOException {
    if (diskLruCache == null) {
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
    }
    return diskLruCache;
  }

  @Override
  public File get(Key key) {
    String safeKey = safeKeyGenerator.getSafeKey(key);
    File result = null;
    try {
      final DiskLruCache.Value value = getDiskCache().get(safeKey);
      if (value != null) {
        result = value.getFile(0);
      }
    } catch (IOException e) {
      ...
    }
    return result;
  }
  
  @Override
  public void put(Key key, Writer writer) {
    String safeKey = safeKeyGenerator.getSafeKey(key);
    writeLocker.acquire(safeKey);
    try {
      try {
        
        DiskLruCache diskCache = getDiskCache();
        Value current = diskCache.get(safeKey);
        ...
        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        ...
        try {
          File file = editor.getFile(0);
          if (writer.write(file)) {
            editor.commit();
          }
        } finally {
          editor.abortUnlessCommitted();
        }
      } catch (IOException e) {
        ...
      }
    } finally {
      writeLocker.release(safeKey);
    }
  }
  ...
}

顧名思義咽扇,里面包裝了一個(gè)DiskLruCache邪财,該類主要是為DiskLruCache提供了一個(gè)根據(jù)Key生成safeKey的SafeKeyGenerator以及寫鎖DiskCacheWriteLocker。

回到GlideBuilder.build(Context)中质欲,diskCacheFactory會(huì)被傳進(jìn)Engine中树埠,在Engine的構(gòu)造方法中會(huì)被包裝成為一個(gè)LazyDiskCacheProvider,在被需要的時(shí)候調(diào)用getDiskCache()方法嘶伟,這樣就會(huì)調(diào)用factory的build()方法返回一個(gè)DiskCache怎憋。代碼如下:

private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {

    private final DiskCache.Factory factory;
    private volatile DiskCache diskCache;

    LazyDiskCacheProvider(DiskCache.Factory factory) {
      this.factory = factory;
    }

    ...

    @Override
    public DiskCache getDiskCache() {
      if (diskCache == null) {
        synchronized (this) {
          if (diskCache == null) {
            diskCache = factory.build();
          }
          if (diskCache == null) {
            diskCache = new DiskCacheAdapter();
          }
        }
      }
      return diskCache;
    }
  }

LazyDiskCacheProvider會(huì)在Engine后面的初始化流程中作為入?yún)鞯紻ecodeJobFactory的構(gòu)造器。在DecodeJobFactory創(chuàng)建DecodeJob時(shí)也會(huì)作為入?yún)?huì)傳進(jìn)去奋早,DecodeJob中會(huì)以全局變量保存此LazyDiskCacheProvider盛霎,在資源加載完畢并展示后,會(huì)進(jìn)行緩存的存儲(chǔ)耽装。同時(shí)愤炸,DecodeJob也會(huì)在DecodeHelper初始化時(shí),將此DiskCacheProvider設(shè)置進(jìn)去掉奄,供ResourceCacheGenerator规个、DataCacheGenerator讀取緩存,供SourceGenerator寫入緩存姓建。

2.3 ActiveResources

ActiveResources在Engine的構(gòu)造器中被創(chuàng)建诞仓,在ActiveResources的構(gòu)造器中會(huì)啟動(dòng)一個(gè)后臺(tái)優(yōu)先級級別(THREAD_PRIORITY_BACKGROUND)的線程,在該線程中會(huì)調(diào)用cleanReferenceQueue()方法一直循環(huán)清除ReferenceQueue中的將要被GC的Resource速兔。

final class ActiveResources {
  private final boolean isActiveResourceRetentionAllowed;
  private final Executor monitorClearedResourcesExecutor;
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

  private volatile boolean isShutdown;

  ActiveResources(boolean isActiveResourceRetentionAllowed) {
    this(
        isActiveResourceRetentionAllowed,
        java.util.concurrent.Executors.newSingleThreadExecutor(
            new ThreadFactory() {
              @Override
              public Thread newThread(@NonNull final Runnable r) {
                return new Thread(
                    new Runnable() {
                      @Override
                      public void run() {
                        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                        r.run();
                      }
                    },
                    "glide-active-resources");
              }
            }));
  }

  @VisibleForTesting
  ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            cleanReferenceQueue();
          }
        });
  }

  @SuppressWarnings("WeakerAccess")
  @Synthetic void cleanReferenceQueue() {
    while (!isShutdown) {
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        cleanupActiveReference(ref);

        // This section for testing only.
        DequeuedResourceCallback current = cb;
        if (current != null) {
          current.onResourceDequeued();
        }
        // End for testing only.
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }
}

先來看看ActiveResources的activate方法(保存)墅拭、deactivate方法(刪除)的方法

  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }

activate方法會(huì)將參數(shù)封裝成為一個(gè)ResourceWeakReference,然后放入map中涣狗,如果對應(yīng)的key之前有值谍婉,那么調(diào)用之前值的reset方法進(jìn)行清除舒憾。deactivate方法先在map中移除,然后調(diào)用resource的reset方法進(jìn)行清除穗熬。ResourceWeakReference繼承WeakReference镀迂,內(nèi)部只是保存了Resource的一些屬性。

static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
  @SuppressWarnings("WeakerAccess") @Synthetic final Key key;
  @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;

  @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;

  @Synthetic
  @SuppressWarnings("WeakerAccess")
  ResourceWeakReference(
      @NonNull Key key,
      @NonNull EngineResource<?> referent,
      @NonNull ReferenceQueue<? super EngineResource<?>> queue,
      boolean isActiveResourceRetentionAllowed) {
    super(referent, queue);
    this.key = Preconditions.checkNotNull(key);
    this.resource =
        referent.isCacheable() && isActiveResourceRetentionAllowed
            ? Preconditions.checkNotNull(referent.getResource()) : null;
    isCacheable = referent.isCacheable();
  }
}

構(gòu)造方法中調(diào)用了super(referent, queue)唤蔗,這樣做可以讓將要被GC的對象放入到ReferenceQueue中探遵。而ActiveResources.cleanReferenceQueue()方法會(huì)一直嘗試從queue中獲取將要被GC的resource,然后調(diào)用cleanupActiveReference方法將resource從activeEngineResources中移除妓柜。cleanupActiveReference源碼如下:

  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    
    synchronized (listener) {
      synchronized (this) {
        
        // 移除active資源
        activeEngineResources.remove(ref.key);
        
        if (!ref.isCacheable || ref.resource == null) {
          return;
        }
        // 構(gòu)造新的 Resource
        EngineResource<?> newResource =
            new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
        newResource.setResourceListener(ref.key, listener);
        // 回調(diào)Engine的onResourceReleased方法
        // 這會(huì)導(dǎo)致此資源從active變成memory cache狀態(tài)
        listener.onResourceReleased(ref.key, newResource);
      }
    }
  }

Engine實(shí)現(xiàn)了EngineResource.ResourceListener箱季,此處的listener就是Engine,最終會(huì)回調(diào)Engine.onResourceReleased

  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

如果資源可以被緩存领虹,則緩存到 memory cache规哪,否則對資源進(jìn)行回收。

2.4 磁盤緩存讀取

了解了上述三種緩存后我們分析下緩存的存取代碼塌衰。我們看下

public synchronized <R> LoadStatus load(...) {
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
      resourceClass, transcodeClass, options);

  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
  if (active != null) {
    cb.onResourceReady(active, DataSource.MEMORY_CACHE);
    return null;
  }

  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
  if (cached != null) {
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
    return null;
  }

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb, callbackExecutor);
    return new LoadStatus(cb, current);
  }

  EngineJob<R> engineJob =
      engineJobFactory.build(...);

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

  jobs.put(key, engineJob);

  engineJob.addCallback(cb, callbackExecutor);
  engineJob.start(decodeJob);

  return new LoadStatus(cb, engineJob);
}

緩存需要根據(jù)EngineKey去存取诉稍,先看下EngineKey的構(gòu)造方法

EngineKey(
      
      Object model,
      Key signature,
      int width
      int height,
      Map<Class<?>, Transformation<?>> transformations,
      Class<?> resourceClass,
      Class<?> transcodeClass,
      Options options)
  • model
    load方法傳的參數(shù)

  • signature
    BaseRequestOptions的成員變量,默認(rèn)會(huì)是EmptySignature.obtain()
    在加載本地resource資源時(shí)會(huì)變成ApplicationVersionSignature.obtain(context)

  • width最疆、height
    如果沒有指定override(int size)杯巨,那么將得到view的size

  • transformations
    默認(rèn)會(huì)基于ImageView的scaleType設(shè)置對應(yīng)的四個(gè)Transformation;
    如果指定了transform努酸,那么就基于該值進(jìn)行設(shè)置

  • resourceClass
    解碼后的資源服爷,如果沒有asBitmap、asGif获诈,一般會(huì)是Object

  • transcodeClass
    最終要轉(zhuǎn)換成的數(shù)據(jù)類型仍源,根據(jù)as方法確定,加載本地res或者網(wǎng)絡(luò)URL舔涎,都會(huì)調(diào)用asDrawable笼踩,所以為Drawable

  • options
    如果沒有設(shè)置過transform,此處會(huì)根據(jù)ImageView的scaleType默認(rèn)指定一個(gè)option

所以亡嫌,在多次加載同一個(gè)model的過程中嚎于,只要上述任何一個(gè)參數(shù)有改變,都不會(huì)認(rèn)為是同一個(gè)key挟冠。

回到Engine.load方法于购,從緩存加載成功后的回調(diào)cb.onResourceReady(cached, DataSource.MEMORY_CACHE);可以看到:active狀態(tài)的資源和memory cache狀態(tài)的資源都是DataSource.MEMORY_CACHE,并且加載的資源都是 EngineResource 對象知染,該對象內(nèi)部采用了引用計(jì)數(shù)去判斷資源是否被釋放肋僧,如果引用計(jì)數(shù)為0,那么會(huì)調(diào)用listener.onResourceReleased(key, this)方法通知外界此資源已經(jīng)釋放了。這里的listener是ResourceListener類型的接口嫌吠,只有一個(gè)onResourceReleased(Key key, EngineResource<?> resource)方法伪窖,Engine實(shí)現(xiàn)了該接口,此處的listener就是Engine居兆。在Engine.onResourceReleased方法中會(huì)判斷資源是否可緩存,可緩存則將此資源放入memory cache中竹伸,否則回收掉該資源泥栖,代碼如下:

  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    // 從activeResources中移除
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      // 存入 MemoryCache
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

繼續(xù)回到Engine.load方法,先來看下active資源獲取的方法

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
  
    // 設(shè)置skipMemoryCache(true)勋篓,則isMemoryCacheable為false吧享,跳過ActiveResources
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      // 命中緩存,引用計(jì)數(shù)+1
      active.acquire();
    }

    return active;
  }

繼續(xù)分析cached資源獲取的方法譬嚣,如果從active資源中沒有獲取到緩存钢颂,則繼續(xù)從內(nèi)存緩存中查找

  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
  
    // 設(shè)置skipMemoryCache(true),則isMemoryCacheable為false拜银,跳過ActiveResources
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      // 命中緩存殊鞭,引用計(jì)數(shù)+1
      cached.acquire();
      // 將此資源從memoryCache中移到activeResources中
      activeResources.activate(key, cached);
    }
    return cached;
  }

如果從memoryCache中獲取到資源則將此資源從memoryCache中移到activeResources中。第一次加載的時(shí)候activeResources和memoryCache中都沒有緩存的尼桶,后面繼續(xù)通過DecodeJob和EngineJob去加載資源操灿。DecoceJob實(shí)現(xiàn)了Runnable接口,然后會(huì)被EngineJob.start方法提交到對應(yīng)的線程池中去執(zhí)行泵督。在DecoceJob的run方法中趾盐,會(huì)依次從ResourceCacheGenerator和DataCacheGenerator中去取緩存數(shù)據(jù),當(dāng)這兩者都取不到的情況下小腊,會(huì)交給SourceGenerator加載網(wǎng)絡(luò)圖片或者本地資源救鲤。resource資源和data資源都是磁盤緩存中的資源。

先看下 ResourceCacheGenerator.startNext

@Override
  public boolean startNext() {
    // list里面只有一個(gè)GlideUrl對象
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    // 獲得了三個(gè)可以到達(dá)的registeredResourceClasses
    // GifDrawable秩冈、Bitmap本缠、BitmapDrawable
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    if (resourceClasses.isEmpty()) {
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
      throw new IllegalStateException(
         "Failed to find any load path from " + helper.getModelClass() + " to "
             + helper.getTranscodeClass());
    }
    
    // 遍歷sourceIds中的每一個(gè)key、resourceClasses中每一個(gè)class漩仙,以及其他的一些值組成key
    // 嘗試在磁盤緩存中以key找到緩存文件
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);
      // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
      // we only run until the first one succeeds, the loop runs for only a limited
      // number of iterations on the order of 10-20 in the worst case.
      
      // 構(gòu)造key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      // 查找緩存文件
      cacheFile = helper.getDiskCache().get(currentKey);
      
      // 如果找到了緩存文件搓茬,循環(huán)條件則會(huì)為false,退出循環(huán)
      if (cacheFile != null) {
        sourceKey = sourceId;
        // 1. 找出注入時(shí)以File.class為modelClass的注入代碼
        // 2. 調(diào)用所有注入的factory.build方法得到ModelLoader
        // 3 .過濾掉不可能處理model的ModelLoader
        // 此時(shí)的modelLoaders值為:
        // [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader]
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    // 如果找到了緩存文件队他,hasNextModelLoader()方法則會(huì)為true卷仑,可以執(zhí)行循環(huán)
    // 沒有找到緩存文件,則不會(huì)進(jìn)入循環(huán)麸折,會(huì)直接返回false
    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      // 在循環(huán)中會(huì)依次判斷某個(gè)ModelLoader能不能加載此文件
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        
        // 如果某個(gè)ModelLoader可以锡凝,那么就調(diào)用其fetcher進(jìn)行加載數(shù)據(jù)
        // 加載成功或失敗會(huì)通知自身
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }

該方法的相關(guān)注釋代碼里都有標(biāo)明。找緩存時(shí)key的類型為ResourceCacheKey垢啼,我們先來看下ResourceCacheKey的構(gòu)成

currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
ResourceCacheKey(
      ArrayPool arrayPool,
      Key sourceKey,
      Key signature,
      int width,
      int height,
      Transformation<?> appliedTransformation,
      Class<?> decodedResourceClass,
      Options options)
  • arrayPool
    默認(rèn)值是LruArrayPool窜锯,不參與key的equals方法

  • sourceKey
    如果請求的是URL张肾,此處就是GlideUrl(GlideUrl implements Key)

  • signature
    BaseRequestOptions的成員變量,默認(rèn)會(huì)是EmptySignature.obtain()
    在加載本地resource資源時(shí)會(huì)變成ApplicationVersionSignature.obtain(context)

  • width锚扎、height
    如果沒有指定override(int size)吞瞪,那么將得到view的size

  • appliedTransformation
    默認(rèn)會(huì)根據(jù)ImageView的scaleType設(shè)置對應(yīng)的BitmapTransformation;
    如果指定了transform驾孔,那么就會(huì)是指定的值

  • decodedResourceClass
    可以被編碼成的資源類型芍秆,如BitmapDrawable等

  • options
    如果沒有設(shè)置過transform,此處會(huì)根據(jù)ImageView的scaleType默認(rèn)指定一個(gè)option

在ResourceCacheKey中翠勉,arrayPool并沒有參與equals方法妖啥。

生成ResourceCacheKey之后會(huì)根據(jù)key去磁盤緩存中查找cacheFile = helper.getDiskCache().get(currentKey);
helper.getDiskCache()返回DiskCache接口,它的實(shí)現(xiàn)類是DiskLruCacheWrapper对碌,看下DiskLruCacheWrapper.get方法

  @Override
  public File get(Key key) {
    String safeKey = safeKeyGenerator.getSafeKey(key);
    ...
    File result = null;
    try {
      final DiskLruCache.Value value = getDiskCache().get(safeKey);
      if (value != null) {
        result = value.getFile(0);
      }
    } catch (IOException e) {
      ...
    }
    return result;
  }

這里調(diào)用SafeKeyGenerator生成了一個(gè)String類型的SafeKey荆虱,實(shí)際上就是對原始key中每個(gè)字段都使用SHA-256加密,然后將得到的字節(jié)數(shù)組轉(zhuǎn)換為16進(jìn)制的字符串朽们。生成SafeKey后怀读,接著根據(jù)SafeKey去DiskCache里面找對應(yīng)的緩存文件,然后返回文件骑脱。

回到ResourceCacheGenerator.startNext方法中愿吹,如果找到了緩存會(huì)調(diào)用loadData.fetcher.loadData(helper.getPriority(), this);這里的 fetcher 是 ByteBufferFetcher,ByteBufferFetcher的loadData方法中最終會(huì)執(zhí)行callback.onDataReady(result)這里callback是ResourceCacheGenerator

  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
        currentKey);
  }

ResourceCacheGenerator的onDataReady方法又會(huì)回調(diào)DecodeJob的onDataFetcherReady方法進(jìn)行后續(xù)的解碼操作惜姐。

如果ResourceCacheGenerator沒有找到緩存犁跪,就會(huì)交給DataCacheGenerator繼續(xù)查找緩存。該類大體流程和ResourceCacheGenerator一樣歹袁,有點(diǎn)不同的是坷衍,DataCacheGenerator的構(gòu)造器有兩個(gè)構(gòu)造器,其中的DataCacheGenerator(List<Key>, DecodeHelper<?>, FetcherReadyCallback)構(gòu)造器是給SourceGenerator準(zhǔn)備的条舔。因?yàn)槿绻麤]有磁盤緩存枫耳,那么從源頭加載后,肯定需要進(jìn)行磁盤緩存操作的孟抗。所以迁杨,SourceGenerator會(huì)將加載后的資源保存到磁盤中,然后轉(zhuǎn)交給DataCacheGenerator從磁盤中取出交給ImageView展示凄硼。

看下DataCacheGenerator.startNext

public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      ...
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      ...
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

這里的originalKey是DataCacheKey類型的铅协,DataCacheKey構(gòu)造方法如下

DataCacheKey(Key sourceKey, Key signature)

這里的sourceKey和signature與ResourceCacheKey中的兩個(gè)變量一致,從這里就可以看出:DataCache緩存的是原始的數(shù)據(jù)摊沉,ResourceCache緩存的是是被解碼狐史、轉(zhuǎn)換后的數(shù)據(jù)。

如果DataCacheGenerator沒有取到緩存,那么會(huì)交給SourceGenerator從源頭加載骏全〔园兀看下SourceGenerator的startNext方法

  @Override
  public boolean startNext() {
  
    // 首次運(yùn)行dataToCache為null
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    // 首次運(yùn)行sourceCacheGenerator為null
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

加載成功后依然會(huì)回調(diào)SourceGenerator的onDataReady方法

  @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // cb 為 DecodeJob
      cb.reschedule();
    } else {
      // cb 為 DecodeJob
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

先判斷獲取到的數(shù)據(jù)是否需要進(jìn)行磁盤緩存,如果需要磁盤緩存姜贡,則經(jīng)過DecodeJob试吁、EngineJob的調(diào)度,重新調(diào)用SourceGenerator.startNext方法楼咳,此時(shí)dataToCache已經(jīng)被賦值潘悼,則會(huì)調(diào)用cacheData(data);進(jìn)行磁盤緩存的寫入,并轉(zhuǎn)交給DataCacheGenerator完成后續(xù)的處理爬橡;否則就通知DecodeJob已經(jīng)加載成功。

先看下SourceGenerator的startNext方法中調(diào)用的SourceGenerator.cacheData(data)

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
      ...
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

cacheData方法先構(gòu)建了一個(gè)DataCacheKey將data寫入了磁盤棒动,然后new了一個(gè)DataCacheGenerator賦值給sourceCacheGenerator糙申。回到startNext繼續(xù)向下執(zhí)行船惨,此時(shí)sourceCacheGenerator不為空柜裸,就調(diào)用其startNext()方法從磁盤中加載剛寫入磁盤的數(shù)據(jù),并返回true讓DecodeJob停止嘗試獲取數(shù)據(jù)粱锐。此時(shí)疙挺,從磁盤緩存中讀取數(shù)據(jù)的邏輯已經(jīng)完成,接下來是寫磁盤緩存怜浅。

假如SourceGenerator的onDataReady方法中的磁盤緩存策略不可用铐然,則會(huì)回調(diào)DecodeJob.onDataFetcherReady方法

  // DecodeJob
  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }
  private void decodeFromRetrievedData() {
    ...
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

decodeFromRetrievedData();后續(xù)的方法調(diào)用鏈在之前的文章中分析過,主要做的事情就是:將原始的data數(shù)據(jù)轉(zhuǎn)變?yōu)榭梢怨㊣mageView顯示的resource數(shù)據(jù)并將其顯示在ImageView上恶座。

將原始的data數(shù)據(jù)轉(zhuǎn)變?yōu)閞esource數(shù)據(jù)后搀暑,會(huì)調(diào)用DecodeJob.onResourceDecoded(dataSource, decoded)

  @Synthetic
  @NonNull
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource,
      @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    
    // 不是 resource cache時(shí)要transform
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    // TODO: Make this the responsibility of the Transformation.
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }

    Resource<Z> result = transformed;
    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
    if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
        encodeStrategy)) {
      if (encoder == null) {
        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
      }
      final Key key;
      switch (encodeStrategy) {
        case SOURCE:
          key = new DataCacheKey(currentSourceKey, signature);
          break;
        case TRANSFORMED:
          key =
              new ResourceCacheKey(
                  decodeHelper.getArrayPool(),
                  currentSourceKey,
                  signature,
                  width,
                  height,
                  appliedTransformation,
                  resourceSubClass,
                  options);
          break;
        default:
          throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
      }

      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    return result;
  }

然后是此過程中的磁盤緩存過程,影響的因素有encodeStrategy跨琳、DiskCacheStrategy.isResourceCacheable。encodeStrategy根據(jù)resource數(shù)據(jù)的類型來判斷,如果是Bitmap或BitmapDrawable缩麸,那么就是TRANSFORMED跷睦;如果是GifDrawable,那么就是SOURCE溅潜。磁盤緩存策略默認(rèn)是DiskCacheStrategy.AUTOMATIC术唬。源碼如下:

    public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
        public boolean isDataCacheable(DataSource dataSource) {
            return dataSource == DataSource.REMOTE;
        }

        public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
            return (isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE || dataSource == DataSource.LOCAL) && encodeStrategy == EncodeStrategy.TRANSFORMED;
        }

        public boolean decodeCachedResource() {
            return true;
        }

        public boolean decodeCachedData() {
            return true;
        }
    };

只有dataSource為DataSource.LOCAL且encodeStrategy為EncodeStrategy.TRANSFORMED時(shí),才允許緩存滚澜。也就是只有本地的resource數(shù)據(jù)為Bitmap或BitmapDrawable的資源才可以緩存碴开。

在DecodeJob.onResourceDecoded中會(huì)調(diào)用deferredEncodeManager.init(key, encoder, lockedResult);去初始化deferredEncodeManager。

在DecodeJob的decodeFromRetrievedData();中拿到resource數(shù)據(jù)后會(huì)調(diào)用notifyEncodeAndRelease(resource, currentDataSource)利用deferredEncodeManager對象進(jìn)行磁盤緩存的寫入

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    ...

    // 通知回調(diào),資源已經(jīng)就緒
    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    
    onEncodeComplete();
  }

deferredEncodeManager.encode行磁盤緩存的寫入

// DecodeJob
private static class DeferredEncodeManager<Z> {
  private Key key;
  private ResourceEncoder<Z> encoder;
  private LockedResource<Z> toEncode;

  @Synthetic
  DeferredEncodeManager() { }

  // We just need the encoder and resource type to match, which this will enforce.
  @SuppressWarnings("unchecked")
  <X> void init(Key key, ResourceEncoder<X> encoder, LockedResource<X> toEncode) {
    this.key = key;
    this.encoder = (ResourceEncoder<Z>) encoder;
    this.toEncode = (LockedResource<Z>) toEncode;
  }

  void encode(DiskCacheProvider diskCacheProvider, Options options) {
    GlideTrace.beginSection("DecodeJob.encode");
    try {
      // 存入磁盤緩存
      diskCacheProvider.getDiskCache().put(key,
          new DataCacheWriter<>(encoder, toEncode, options));
    } finally {
      toEncode.unlock();
      GlideTrace.endSection();
    }
  }

  boolean hasResourceToEncode() {
    return toEncode != null;
  }

  void clear() {
    key = null;
    encoder = null;
    toEncode = null;
  }
}

diskCacheProvider.getDiskCache()獲取到DiskLruCacheWrapper潦牛,并調(diào)用DiskLruCacheWrapper的put寫入眶掌。DiskLruCacheWrapper在寫入的時(shí)候會(huì)使用到寫鎖DiskCacheWriteLocker,鎖對象由對象池WriteLockPool創(chuàng)建巴碗,寫鎖WriteLock實(shí)現(xiàn)是一個(gè)不公平鎖ReentrantLock朴爬。
在緩存寫入前,會(huì)判斷key對應(yīng)的value存不存在橡淆,若存在則不寫入召噩。緩存的真正寫入會(huì)由DataCacheWriter交給ByteBufferEncoderStreamEncoder兩個(gè)具體類來寫入,前者負(fù)責(zé)將ByteBuffer寫入到文件逸爵,后者負(fù)責(zé)將InputStream寫入到文件具滴。

到目前為止,磁盤緩存的讀寫流程都已分析完成师倔。

2.5 內(nèi)存緩存:ActiveResource與MemoryCache讀取

回到DecodeJob.notifyEncodeAndRelease方法中构韵,經(jīng)過notifyComplete、EngineJob.onResourceReady趋艘、notifyCallbacksOfResult方法中疲恢。
在該方法中一方面會(huì)將原始的resource包裝成一個(gè)EngineResource,然后通過回調(diào)傳給Engine.onEngineJobComplete

  @Override
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    
    // 設(shè)置資源的回調(diào)為自己瓷胧,這樣在資源釋放時(shí)會(huì)通知自己的回調(diào)方法
    if (resource != null) {
      resource.setResourceListener(key, this);

      // 將資源放入activeResources中显拳,資源變?yōu)閍ctive狀態(tài)
      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    // 將engineJob從Jobs中移除
    jobs.removeIfCurrent(key, engineJob);
  }

在這里會(huì)將資源放入activeResources中,資源變?yōu)閍ctive狀態(tài)搓萧。后面會(huì)使用Executors.mainThreadExecutor()調(diào)用SingleRequest.onResourceReady回調(diào)進(jìn)行資源的顯示杂数。在觸發(fā)回調(diào)前后各有一個(gè)地方會(huì)對engineResource進(jìn)行acquire()和release()操作,這兩個(gè)操作分別發(fā)生在notifyCallbacksOfResult()方法的incrementPendingCallbacks瘸洛、decrementPendingCallbacks()調(diào)用中

@Synthetic
void notifyCallbacksOfResult() {
  ResourceCallbacksAndExecutors copy;
  Key localKey;
  EngineResource<?> localResource;
  synchronized (this) {
    ...
    engineResource = engineResourceFactory.build(resource, isCacheable);
    ...
    hasResource = true;
    copy = cbs.copy();
    incrementPendingCallbacks(copy.size() + 1);

    localKey = key;
    localResource = engineResource;
  }

  listener.onEngineJobComplete(this, localKey, localResource);

  for (final ResourceCallbackAndExecutor entry : copy) {
    entry.executor.execute(new CallResourceReady(entry.cb));
  }
  decrementPendingCallbacks();
}

synchronized void incrementPendingCallbacks(int count) {
  ...
  if (pendingCallbacks.getAndAdd(count) == 0 && engineResource != null) {
    engineResource.acquire();
  }
}

synchronized void decrementPendingCallbacks() {
  ...
  int decremented = pendingCallbacks.decrementAndGet();
  if (decremented == 0) {
    if (engineResource != null) {
      engineResource.release();
    }

    release();
  }
}

private class CallResourceReady implements Runnable {

  private final ResourceCallback cb;

  CallResourceReady(ResourceCallback cb) {
    this.cb = cb;
  }

  @Override
  public void run() {
    synchronized (EngineJob.this) {
      if (cbs.contains(cb)) {
        // Acquire for this particular callback.
        engineResource.acquire();
        callCallbackOnResourceReady(cb);
        removeCallback(cb);
      }
      decrementPendingCallbacks();
    }
  }
}

CallResourceReady的run方法中也會(huì)調(diào)用engineResource.acquire()耍休,上面的代碼調(diào)用結(jié)束后,engineResource的引用計(jì)數(shù)為1货矮。engineResource的引用計(jì)數(shù)會(huì)在RequestManager.onDestory方法中最終調(diào)用SingleRequest.clear()方法羊精,SingleRequest.clear()內(nèi)部調(diào)用releaseResource()、Engine.release 進(jìn)行釋放囚玫,這樣引用計(jì)數(shù)就變?yōu)?喧锦。引用計(jì)數(shù)就變?yōu)?后會(huì)通知Engine將此資源從active狀態(tài)變成memory cache狀態(tài)。如果我們再次加載資源時(shí)可以從memory cache中加載抓督,那么資源又會(huì)從memory cache狀態(tài)變成active狀態(tài)燃少。也就是說,在資源第一次顯示后铃在,我們關(guān)閉頁面阵具,資源會(huì)由active變成memory cache碍遍;然后我們再次進(jìn)入頁面,加載時(shí)會(huì)命中memory cache阳液,從而又變成active狀態(tài)怕敬。

2.6 總結(jié)

四種緩存狀態(tài)
  1. memory cache和disk cache在Glide創(chuàng)建的時(shí)候也被創(chuàng)建。

  2. disk cache默認(rèn)會(huì)創(chuàng)建一個(gè)250M的緩存目錄(/data/data/{package}/cache/image_manager_disk_cache/)帘皿。

  3. ActiveResources在Engine的構(gòu)造器中被創(chuàng)建东跪,內(nèi)部維護(hù)了一個(gè) Map<Key, ResourceWeakReference>類型的activeEngineResources用來存儲(chǔ)包裹EngineResource的ResourceWeakReference,ResourceWeakReference構(gòu)造器中會(huì)傳入一個(gè)ReferenceQueue鹰溜,在ActiveResources的構(gòu)造器中會(huì)啟動(dòng)一個(gè)后臺(tái)線程虽填,在該線程中會(huì)循環(huán)從activeEngineResources清除ReferenceQueue中的將要被GC的Resource。

  4. ActiveResources被引用后曹动,其內(nèi)部的引用計(jì)數(shù)會(huì)+1斋日,當(dāng)被釋放后,其內(nèi)部的引用計(jì)數(shù)會(huì)-1墓陈,當(dāng)引用計(jì)數(shù)為0恶守,則表示該ActiveResources不再被引用,會(huì)將資源放入LruResourceCache中跛蛋。

  5. 首先從ActiveResources中獲取緩存資源,獲取不到再從LruResourceCache中查找痊硕。第一次沒有緩存赊级,會(huì)從網(wǎng)絡(luò)下載圖片成功后會(huì)存入磁盤緩存,然后再從磁盤緩存獲取資源存入ActiveResources中并將其交給ImageView展示岔绸。

  6. 緩存查找中涉及的三個(gè)類:ResourceCacheGenerator理逊、DataCacheGenerator、SourceGenerator盒揉。按照先后關(guān)系依次調(diào)用他們的startNext()方法查找緩存晋被,ResourceCacheGenerator獲取downsample、transform后的資源文件的緩存文件刚盈;DataCacheGenerator獲取原始的沒有修改過的資源文件的緩存文件羡洛;SourceGenerator獲取原始源數(shù)據(jù)。

  7. ActiveResources引用計(jì)數(shù)就變?yōu)?后會(huì)通知Engine將此資源從active狀態(tài)變成memory cache狀態(tài)藕漱。如果我們再次加載資源時(shí)可以從memory cache中加載欲侮,那么資源又會(huì)從memory cache狀態(tài)變成active狀態(tài)。也就是說肋联,在資源第一次顯示后威蕉,我們關(guān)閉頁面,資源會(huì)由active變成memory cache橄仍;然后我們再次進(jìn)入頁面韧涨,加載時(shí)會(huì)命中memory cache牍戚,從而又變成active狀態(tài)。

  8. 之所以需要ActiveResources虑粥,因?yàn)樗萌跻冒b資源如孝,隨時(shí)可能被回收,memory的強(qiáng)引用頻繁讀寫可能造成內(nèi)存激增頻繁GC舀奶,而造成內(nèi)存抖動(dòng)暑竟。資源在使用過程中保存在ActiveResources中,而ActiveResources是弱引用育勺,隨時(shí)被系統(tǒng)回收但荤,不會(huì)造成內(nèi)存過多使用和泄漏。

3 Bitmap復(fù)用池復(fù)用源碼流程

Glide源碼分析-網(wǎng)絡(luò)圖片加載主流程分析文章中分析過Glide加載圖片主要流程源碼涧至,在ByteBufferBitmapDecoder.decode方法中腹躁,先將ByteBuffer轉(zhuǎn)換成InputStream,然后在調(diào)用Downsampler.decode方法進(jìn)行解碼南蓬,代碼如下:

  // ByteBufferBitmapDecoder
  @Override
  public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
      @NonNull Options options)
      throws IOException {
    InputStream is = ByteBufferUtil.toStream(source);
    return downsampler.decode(is, width, height, options);
  }

繼續(xù)跟進(jìn)Downsampler.decode方法

public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {
    Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
        + " mark()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    
    // getDefaultOptions()中將inMutable設(shè)置為true
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    
    // inTempStorage是一個(gè)bitmap解析的參數(shù)纺非,帶入一個(gè)buffer,創(chuàng)建臨時(shí)文件赘方,將圖片存儲(chǔ)的臨時(shí)緩存空間
    bitmapFactoryOptions.inTempStorage = bytesForOptions;

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }

在getDefaultOptions()方法中會(huì)將inMutable設(shè)置為true烧颖,代碼如下:

  private static synchronized BitmapFactory.Options getDefaultOptions() {
    BitmapFactory.Options decodeBitmapOptions;
    synchronized (OPTIONS_QUEUE) {
      decodeBitmapOptions = OPTIONS_QUEUE.poll();
    }
    if (decodeBitmapOptions == null) {
      decodeBitmapOptions = new BitmapFactory.Options();
      resetOptions(decodeBitmapOptions);
    }

    return decodeBitmapOptions;
  }
  
    private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
    decodeBitmapOptions.inTempStorage = null;
    decodeBitmapOptions.inDither = false;
    decodeBitmapOptions.inScaled = false;
    decodeBitmapOptions.inSampleSize = 1;
    decodeBitmapOptions.inPreferredConfig = null;
    decodeBitmapOptions.inJustDecodeBounds = false;
    decodeBitmapOptions.inDensity = 0;
    decodeBitmapOptions.inTargetDensity = 0;
    decodeBitmapOptions.outWidth = 0;
    decodeBitmapOptions.outHeight = 0;
    decodeBitmapOptions.outMimeType = null;
    decodeBitmapOptions.inBitmap = null;
    decodeBitmapOptions.inMutable = true;
  }

回到Downsampler.decode方法中,繼續(xù)調(diào)用decodeFromWrappedStreams方法返回Bitmap窄陡,跟進(jìn)decodeFromWrappedStreams

// DownSampler
 private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    long startTime = LogTime.getLogTime();

    // 計(jì)算原始大小
    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;

    ...
    
    // 計(jì)算方向
    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

    // 計(jì)算目標(biāo)大小
    int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
    int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
    // 計(jì)算類型
    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

    ...
       
     // 使用復(fù)用池
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
      int expectedWidth;
      int expectedHeight;
      
      ...

      if (expectedWidth > 0 && expectedHeight > 0) {
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);

    ...
    
    return rotated;
  }
  
  private static void setInBitmap(
      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
    @Nullable Bitmap.Config expectedConfig = null;
     ...

    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
  }

setInBitmap方法里面的bitmapPool就是LruBitmapPool炕淮,在Glide構(gòu)造器里面被初始化,LruBitmapPool 就是Glide提供Bitmap復(fù)用池,真正的實(shí)現(xiàn)類是LruPoolStrategy

public class LruBitmapPool implements BitmapPool {

private final LruPoolStrategy strategy;

...

public synchronized void put(Bitmap bitmap) {
    if (bitmap == null) {
      throw new NullPointerException("Bitmap must not be null");
    }
    if (bitmap.isRecycled()) {
      throw new IllegalStateException("Cannot pool recycled bitmap");
    }
    if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
        || !allowedConfigs.contains(bitmap.getConfig())) {
      bitmap.recycle();
      return;
    }

    final int size = strategy.getSize(bitmap);
    strategy.put(bitmap);
    tracker.add(bitmap);

    puts++;
    currentSize += size;
    dump();
    evict();
  }

  public Bitmap get(int width, int height, Bitmap.Config config) {
    Bitmap result = getDirtyOrNull(width, height, config);
    if (result != null) {
      result.eraseColor(Color.TRANSPARENT);
    } else {
      result = createBitmap(width, height, config);
    }

    return result;
    }
}

看下LruBitmapPool.getDirty方法

// LruBitmapPool
  public Bitmap getDirty(int width, int height, Bitmap.Config config) {
    // 優(yōu)先獲取
    Bitmap result = getDirtyOrNull(width, height, config);
    if (result == null) { // 若沒有跳夭,新建一個(gè)bitmap
      result = createBitmap(width, height, config);
    }
    return result;
  }

如果可以使用bitmap池涂圆,就會(huì)調(diào)用bitmapPool的getDirty(),最后賦值給inBitmap币叹,總結(jié)一下:setInBitmap方法主要就是從LruBitmapPool中獲取可以被復(fù)用的Bitmap返回润歉,并賦值給BitmapFactory.Options的inBitmap。

那么可以被復(fù)用的Bitmap是什么時(shí)候加入Bitmap復(fù)用池呢颈抚?當(dāng)Resource資源沒有被引用并且不可被緩存的時(shí)候踩衩,會(huì)調(diào)用recycle()方法進(jìn)行回收,在BitmapDrawableResource的recycle()方法被調(diào)用的時(shí)候贩汉,會(huì)將當(dāng)前BitmapDrawableResource的bitmap放入復(fù)用池九妈,代碼如下:

  // BitmapDrawableResource
  @Override
  public void recycle() {
    bitmapPool.put(drawable.getBitmap());
  }

LruBitmapPool內(nèi)部利用了Lru算法,每次操作都自動(dòng)檢測是否刪除多余的緩存

// LruBitmapPool
private void evict() {
    trimToSize(maxSize);
  }

  private synchronized void trimToSize(long size) {
    while (currentSize > size) {
      final Bitmap removed = strategy.removeLast();
      // TODO: This shouldn't ever happen, see #331.
      if (removed == null) {
        if (Log.isLoggable(TAG, Log.WARN)) {
          Log.w(TAG, "Size mismatch, resetting");
          dumpUnchecked();
        }
        currentSize = 0;
        return;
      }
      tracker.remove(removed);
      currentSize -= strategy.getSize(removed);
      evictions++;
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
      }
      dump();
      removed.recycle();
    }
  }

再來看看Bitmap復(fù)用池對Bitmap具體的存取邏輯的類LruPoolStrategy雾鬼,在LruBitmapPool的put方法中會(huì)調(diào)用LruPoolStrategy的put方法萌朱,在LruBitmapPool的get方法中會(huì)調(diào)用getDirtyOrNull方法進(jìn)而調(diào)用LruPoolStrategy的get方法,LruPoolStrategy時(shí)一個(gè)接口策菜,在LruBitmapPool中為LruPoolStrategy類型的全局變量strategy其賦值的地方在LruBitmapPool構(gòu)造器中

  LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
    this.initialMaxSize = maxSize;
    this.maxSize = maxSize;
    this.strategy = strategy;
    this.allowedConfigs = allowedConfigs;
    this.tracker = new NullBitmapTracker();
  }
  
  public LruBitmapPool(long maxSize) {
    this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
  }
  
  private static LruPoolStrategy getDefaultStrategy() {
    final LruPoolStrategy strategy;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      strategy = new SizeConfigStrategy();
    } else {
      strategy = new AttributeStrategy();
    }
    return strategy;
  }

當(dāng)sdk版本在19之上晶疼,會(huì)創(chuàng)建SizeConfigStrategy實(shí)例并賦值給strategy酒贬,那么繼續(xù)看下SizeConfigStrategy的put和get方法相關(guān)代碼

  public class SizeConfigStrategy implements LruPoolStrategy {
  private static final int MAX_SIZE_MULTIPLE = 8;
  private final KeyPool keyPool = new KeyPool();
  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
  private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
  
  // 存入
  public void put(Bitmap bitmap) {
    int size = Util.getBitmapByteSize(bitmap);
    Key key = keyPool.get(size, bitmap.getConfig());

    groupedMap.put(key, bitmap);

    NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
    Integer current = sizes.get(key.size);
    sizes.put(key.size, current == null ? 1 : current + 1);
  }

  // 取出
  public Bitmap get(int width, int height, Bitmap.Config config) {
    int size = Util.getBitmapByteSize(width, height, config);
    // 查找出最合適的bitmap
    Key bestKey = findBestKey(size, config);
    // 取出
    Bitmap result = groupedMap.get(bestKey);
    if (result != null) {
      // Decrement must be called before reconfigure.
      decrementBitmapOfSize(bestKey.size, result);
      result.reconfigure(width, height,
          result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
    }
    return result;
  }
  
}

findBestKey()方法就是通過對size進(jìn)行匹配,找出最合適size的Bitmap的key翠霍,上面有提到過:在Android 4.4之后復(fù)用Bitmap有一個(gè)限制锭吨,就是被復(fù)用的Bitmap尺寸要大于新的Bitmap尺寸,findBestKey()方法就是實(shí)現(xiàn)這個(gè)邏輯

  private Key findBestKey(int size, Bitmap.Config config) {
    Key result = keyPool.get(size, config);
    for (Bitmap.Config possibleConfig : getInConfigs(config)) {
      NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
      
      // 返回大于或等于指定size的最小的符合要求的size
      Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
      if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
        if (possibleSize != size
            || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
          keyPool.offer(result);
          result = keyPool.get(possibleSize, possibleConfig);
        }
        break;
      }
    }
    return result;
  }

總結(jié)

  • Glide中使用Bitmap復(fù)用池來減少內(nèi)存抖動(dòng)
  • Bitmap復(fù)用池實(shí)現(xiàn)類是LruBitmapPool寒匙,通過LRU算法來管理Bitmap復(fù)用池
  • 具體的存儲(chǔ)和取出符合要求的可被復(fù)用的Bitmap的邏輯在SizeConfigStrategy中零如,主要是通過NavigableMap數(shù)據(jù)結(jié)構(gòu)的ceilingKey(K key)方法取出大于或等于新的Bitmap的size的最優(yōu)size對應(yīng)的key,進(jìn)而取出最優(yōu)的待復(fù)用的Bitmap

參考鏈接
https://muyangmin.github.io/glide-docs-cn/doc/caching.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锄弱,一起剝皮案震驚了整個(gè)濱河市考蕾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌会宪,老刑警劉巖肖卧,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掸鹅,居然都是意外死亡塞帐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門巍沙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葵姥,“玉大人,你說我怎么就攤上這事句携±菩遥” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵务甥,是天一觀的道長牡辽。 經(jīng)常有香客問我喳篇,道長敞临,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任麸澜,我火速辦了婚禮挺尿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炊邦。我一直安慰自己编矾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布馁害。 她就那樣靜靜地躺著窄俏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碘菜。 梳的紋絲不亂的頭發(fā)上凹蜈,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天限寞,我揣著相機(jī)與錄音,去河邊找鬼仰坦。 笑死履植,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悄晃。 我是一名探鬼主播玫霎,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妈橄!你這毒婦竟也來了庶近?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤眷细,失蹤者是張志新(化名)和其女友劉穎拦盹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溪椎,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡普舆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了校读。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沼侣。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歉秫,靈堂內(nèi)的尸體忽然破棺而出蛾洛,到底是詐尸還是另有隱情,我是刑警寧澤雁芙,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布轧膘,位于F島的核電站,受9級特大地震影響兔甘,放射性物質(zhì)發(fā)生泄漏谎碍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一洞焙、第九天 我趴在偏房一處隱蔽的房頂上張望蟆淀。 院中可真熱鬧,春花似錦澡匪、人聲如沸熔任。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疑苔。三九已至,卻和暖如春甸鸟,著一層夾襖步出監(jiān)牢的瞬間惦费,已是汗流浹背赛惩。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趁餐,地道東北人喷兼。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像后雷,于是被迫代替她去往敵國和親季惯。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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