Glide源码分析五——缓存相关

2022-06-22  本文已影响0人  GIndoc

DataSource(数据源)

数据源有5种类型:

  1. LOCAL:本地数据,例如本地图片文件,也可能是通过ContentProvider共享的远程数据,比如在ContentProvider中进行网络请求。
  2. REMOTE:远程数据,例如通过网络请求数据。
  3. DATA_DISK_CACHE:缓存在磁盘的原始数据。
  4. RESOURCE_DISK_CACHE:缓存在磁盘的解码、变换后的数据。
  5. MEMORY_CACHE:存储在内存中的数据。

DiskCacheStrategy(磁盘缓存策略)

我们知道,在使用Glide进行请求时,可以设置磁盘缓存策略。之后在DataFetcherGenerator请求数据成功后,会根据磁盘缓存策略DiskCacheStrategy和LoadData的Fetcher对应的DataSource,来决定是否缓存数据。

主要有:

  1. ALL(原始数据和处理后的数据都缓存)
  2. NONE(都不缓存)
  3. DATA(只缓存原始数据)
  4. RESOURCE(只缓存处理后的数据,即经过缩放等转换后的数据)
  5. AUTOMATIC(它会尝试对本地和远程图片使用最佳的策略。

当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。默认使用这种磁盘缓存策略)

// DiskCacheStrategy.java
public abstract class DiskCacheStrategy {
  
  // 原始数据是否可以缓存
  public abstract boolean isDataCacheable(DataSource dataSource);
  
  // 解码(变换)后的数据是否可以缓存
  public abstract boolean isResourceCacheable(
      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
      
  // 是否可以解码之前缓存的解码(变换)后的数据
  public abstract boolean decodeCachedResource();
  
  // 是否可以解码之前缓存的原始数据
  public abstract boolean decodeCachedData();

  public static final DiskCacheStrategy ALL =
      new DiskCacheStrategy() {
      
        // 如果是远程数据,则可以缓存成原始数据
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }

        // 如果是本地数据、远程数据和缓存在磁盘中的原始数据,则可以缓存成解码、变换后的数据RESOURCE_DISK_CACHE
        // 因为缓存在磁盘中的解码、变换后的数据RESOURCE_DISK_CACHE没有必要再次缓存,
        // 而内存中如果有数据的话,说明之前已经通过其他4种方式加载过资源了,如果可以缓存的话已经缓存了,也就没必要缓存内存数据了
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return dataSource != DataSource.RESOURCE_DISK_CACHE
              && dataSource != DataSource.MEMORY_CACHE;
        }

        // 可以解码缓存在磁盘中的解码、变换后的数据
        @Override
        public boolean decodeCachedResource() {
          return true;
        }

        // 可以解码缓存在磁盘中的原始数据
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };
      
  public static final DiskCacheStrategy AUTOMATIC =
      new DiskCacheStrategy() {
      
        // 如果是远程数据,则可以缓存成原始数据
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }

        // 如果是缓存在磁盘的原始数据或 需要transform的本地数据,则可以缓存成解码、变换后的数据
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
                  || dataSource == DataSource.LOCAL)
              && encodeStrategy == EncodeStrategy.TRANSFORMED;
        }

        // 可以解码缓存在磁盘中的解码、变换后的数据
        @Override
        public boolean decodeCachedResource() {
          return true;
        }

        // 可以解码缓存在磁盘中的原始数据
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };
}

BitmapPool

BitmapPool.png
  • Android 2.3.3(API 级别 10)及以下的版本:Bitmap内存在Native层,使用Bitmap对象的recycle方法回收内存,无法复用。

  • 从Android3.0开始到8.0以前,bitmap对象存储在java堆内存中,java虚拟机会自动回收内存。

  • 在8.0以后Bitmap内存在Native层,所以我们要手动释放bitmap。

在 Android 4.4(API 级别 19)之前(3.0-4.3)复用bitmap的前提是:

  1. 被解码的图像必须是 JPEG 或 PNG 格式
  2. 被复用的图像宽高必须等于 解码后的图像宽高
  3. 解码图像的 BitmapFactory.Options.inSampleSize 设置为 1 , 也就是不能缩放
  4. 被复用的图像的像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 BitmapFactory.Options.inPreferredConfig 参数

在 4.4 之后只要 inBitmap 的大小比目标 Bitmap 大即可,且解码图像的BitmapFactory.Options.inSampleSize 可以大于1。

BitmapPool是一个Bitmap对象池,用于复用已有的未recycle的Bitmap对象。
它主要有两个子类:

  1. BitmapPoolAdapter,空实现
  2. LruBitmapPool,基于LruPoolStrategy策略来缓存对象。

GlideBuilder在创建Glide时,根据memorySizeCalculator.getBitmapPoolSize()获取设备推荐的bitmap缓存池大小(字节为单位),如果大于0,则使用LruBitmapPool,否则使用BitmapPoolAdapter。

我们也可以自己实现BitmapPool,设置给Glide。

MemoryCache(二级内存缓存)

MemoryCache.png

主要有两个子类:

  1. MemoryCacheAdapter:空实现
  2. LruResourceCache:默认实现,继承自LruCache,实现了MemoryCache,内部使用LinkedHashMap来实现LRU。

LruResourceCache的key是一个自定义的Key类对象,维护了width、height、url等信息。
LruResourceCache的value是一个Resource<?>包装类,因为Glide支持多种类型的结果,例如Bitmap、Drawable等。

对于低内存设备,默认的缓存大小是应用内存(系统为每个应用分配的近似内存值)的33%,非低内存设备则是40%。ps:这里的缓存包括:Resouce的缓存、Bitmap对象池、数组对象池,这三种都采用了Lru算法进行缓存。

我们也可以自己实现MemoryCache类设置给Glide。

ActiveResources(一级内存缓存)

ActiveResources.png

正在被使用的资源会被放入ActiveResources。

// ActiveResources.java
final class ActiveResources {

  // 是否允许正在使用的资源被保留(如果是的话,在ResourceWeakReference中会强引用该资源,release时需要手动置空)
  private final boolean isActiveResourceRetentionAllowed;
  
  // 用于将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
  private final Executor monitorClearedResourcesExecutor;
  
  // 正在被使用的资源
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  
  // 当ResourceWeakReference引用的资源被回收时,ResourceWeakReference会被放入该队列
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

  // 资源释放监听
  private ResourceListener listener;

  // 是否中断,是的话,monitorClearedResourcesExecutor将会停止工作
  private volatile boolean isShutdown;
  
  
  ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            // 执行清理工作,将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
            cleanReferenceQueue();
          }
        });
  }
  
  // 将正在使用的资源包装成ResourceWeakReference,并放入activeEngineResources
  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    // 如果该key之前已经有对应的资源了,则清理旧资源
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

  // 将key对应的ResourceWeakReference从activeEngineResources中移除,并清理旧资源
  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }
  
  // 将ResourceWeakReference从activeEngineResources中移除,如果ResourceWeakReference强引用了资源,则回调通知Engine放入MemoryCache中
  void cleanupActiveReference(ResourceWeakReference ref) {
    synchronized (this) {
    
      // 将key对应的ResourceWeakReference从activeEngineResources中移除
      activeEngineResources.remove(ref.key);

      // 如果资源没有被强引用则return
      if (!ref.isCacheable || ref.resource == null) {
        return;
      }
    }

    // 如果资源被强引用了,则回调通知Engine放入MemoryCache中
    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);
  }
  
  // 执行清理工作,将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
  void cleanReferenceQueue() {
    while (!isShutdown) {
        
        // 从队列中获取被回收的资源,当队列没数据时会阻塞在这里
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        
        // 清理ResourceWeakReference及其引用资源
        cleanupActiveReference(ref);

        // ... 回调通知ResourceWeakReference从resourceReferenceQueue中移除了
      
    }
  }
  
  // 关闭清理资源的线程池
  void shutdown() {
    isShutdown = true;
    if (monitorClearedResourcesExecutor instanceof ExecutorService) {
      ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;
      Executors.shutdownAndAwaitTermination(service);
    }
  }
  
  // 弱引用类,用于引用正在使用的资源,当资源被回收时会被放入resourceReferenceQueue,之后会在线程池中将它从activeEngineResources中移除,如果被引用的资源是可缓存在内存中的,则会强引用资源,在被清理时,会回调到Engine中放入MemoryCache中
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    
    // 资源的key
    final Key key;

    // 资源是否可以缓存在内存中
    final boolean isCacheable;

    // 被引用的资源(如果资源是可缓存在内存中的,则不为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);
      
      // 如果资源可以缓存在内存中,且允许持有正在使用的资源,则强引用资源
      this.resource =
          referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource())
              : null;
              
      isCacheable = referent.isMemoryCacheable();
    }

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

通过上面代码的注释,可以知道:

  1. 正在使用的资源会被转成弱引用ResourceWeakReference,保存在ActiveResouces的activeEngineResources中。
  2. 当资源被jvm回收时,ResourceWeakReference会被放入队列中。
  3. ActiveResources中有个线程池,会循环从队列中取被回收资源的ResourceWeakReference,将它从activeEngineResources中移除。如果ResourceWeakReference.resource不为null(条件:isActiveResourceRetentionAllowed为true,且该资源可以缓存在内存中),则会回调到Engine中将资源加入MemoryCache。
上一篇下一篇

猜你喜欢

热点阅读