【Android】Glide的缓存机制

Application Options(选项)

Glide允许应用通过AppGlideModule实现完全控制Glide的内存和磁盘应用缓存。Glide对大部分应用提供合理的默认选项,部分应用需要定制。

Memory cache(内存缓存)

  1. 自定义MemoryCache的大小

    在GlideModule中使用applyOptions方法配置MemorySizeCalculator

    java 复制代码
    @GlideModule(glideName = "GlideApp")
    public class MyGlideModule extends AppGlideModule {
        public void applyOptions(Context context, GlideBuilder builder){
            MemorySizeCalculator calculator=new MemorySizeCalculator.Builder(context)
                    .setMemoryCacheScreens(2)
                    .build();
            builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
        }
    }
  2. 直接覆盖缓存大小

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            int memoryCacheSizeBytes = 1024 * 1024 * 10; // 10mb
            builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
        }
    }
  3. 提供自己的MemoryCache实现

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            builder.setMemoryCache(new MyMemoryCacheImpl());
        }
    }

Disk Cache(磁盘缓存)

Glide 使用 DiskLruCacheWrapper 作为默认的磁盘缓存。 DiskLruCacheWrapper 是一个使用 LRU 算法的固定大小的磁盘缓存。默认磁盘大小为 250 MB ,位置是在应用的 缓存文件夹 中的一个 特定目录

  1. 如果显示的媒体是公开的,则应用程序可以将位置更改为外部存储

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
      @Override
      public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));
      }
    }
  2. 无论使用内部或外部磁盘存储,应用程序都可以改变磁盘缓存的大小

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
      @Override
      public void applyOptions(Context context, GlideBuilder builder) {
        int diskCacheSizeBytes = 1024 * 1024 * 100; // 100 MB
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes));
      }
    }
  3. 应用程序可以选择DiskCache接口的实现,并提供自己的DiskCache.Factory来创建缓存

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
      @Override
      public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDiskCache(new DiskCache.Factory() {
            @Override
            public DiskCache build() {
              return new MyCustomDiskCache();
            }
        });
      }
    }

BitmapPool(位图池)

BitmapPool 是 Glide 内部用于管理和复用 Bitmap 对象的内存缓存区。 它的主要目的是避免频繁创建和销毁 Bitmap 对象,从而减少内存抖动和垃圾回收(GC)压力,显著提升应用在加载大量图片时的流畅性和性能。

  1. 可以在它们的 AppGlideModule 中定制 BitmapPool 的尺寸,使用 applyOptions(Context, GlideBuilder) 方法并配置 MemorySizeCalculator

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
                    .setBitmapPoolScreens(3)
                    .build();
            builder.setBitmapPool(new LruBitmapPool(calculator.getBitmapPoolSize()));
        }
    }
  2. 直接复写这个池的大小

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            int bitmapPoolSizeBytes = 1024 * 1024 * 30; // 30mb
            builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));
        }
    }
  3. 提供自己的BitmapPool实现

    java 复制代码
    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void applyOptions(Context context, GlideBuilder builder) {
            builder.setBitmapPool(new MyBitmapPoolImpl());
        }
    }

缓存机制

默认情况下,Glide会在开始一个新的图片的请求之前检查以下多级的缓存:

  • 活动资源(Activity Resources):现在是否有另一个View正在展示这张图片
  • 内存缓存(Memory cache):该图片是否最近被加载过并存于内存
  • 资源缓存(Resource):该图片是否之前被解码、转换并写入磁盘缓存
  • 数据来源(Data):构建这个图片的资源是否之前曾被写入过文件缓存

如果四个步骤都未找到图片,则Glide会返回到原始资源以取回数据

三级缓存

  • 内存缓存:优先加载,速度最快
  • 本地缓存:其次加载,速度快
  • 网络缓存:最后加载,速度慢,浪费流量

Glide使用了ActiveResource(活动缓存弱引用)+MemoryCache(内存缓存Lru算法)+DiskCache(磁盘缓存Lru算法)

  • ActivityResources:存储当前界面使用的图片。界面不展示后,该Bitmap被缓存至MemoryCache,并从ActiveResources中删除
  • Memory Cache:存储当前没有使用到的Bitmap,当从MemoryCache中得到后,被存储到ActiveResources中,并从MemoryCache中删除
  • Disk Cache:持久缓存,图片被处理后会缓存到文件中,应用再次被打开时可以加载缓存直接使用

注意:ActiveResource+MemoryCache属于内存缓存,二者不共存,应用杀死后不存在

配置缓存

磁盘缓存策略

DiskCacheStrategy可被diskCacheStrategy方法应用到每一个单独的请求。目前支持的策略允许阻止加载过程使用或写入磁盘缓存,选择性的仅缓存无修改的原生数据或仅缓存变换过的缩略图或二者都有。

指定DiskCacheStrategy:

java 复制代码
    Glide.with(this)
            .load(uri)
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .into(imageView);

diskCacheStrategy方法可传入的参数有五种:

  • DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换后的图片
  • DiskCacheStrategy.NONE:不缓存任何内容
  • DiskCacheStrategy.DATA:只缓存原始图片
  • DiskCacheStrategy.RESULT:只缓存转换后的图片

仅从缓存加载图片

实现图片不在缓存中则加载直接失败,可以在单个请求的基础上使用onlyRetrieveFromCache方法

java 复制代码
Glide.with(this)
        .load(uri)
        .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
        .onlyRetrieveFromCache(true);
        .into(imageView);

跳过缓存

确保一个特定的请求跳过磁盘或内存缓存。

  • 仅跳过内存缓存,使用skipMemoryCache方法

    java 复制代码
    Glide.with(fragment)
      .load(url)
      .skipMemoryCache(true)
      .into(imageView);
  • 仅跳过磁盘缓存,使用DiskCacheStrategy.NONE

    java 复制代码
    Glide.with(fragment)
      .load(url)
      .diskCacheStrategy(DiskCacheStrategy.NONE)
      .into(imageView);
  • 以上两个选项可以同时使用

清理磁盘缓存

要尝试清理所有磁盘缓存条目,可使用clearDiskCache

java 复制代码
new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 必须在子线程上调用此方法。
                    Glide.get(AppGlobalUtils.getApplication()).clearDiskCache();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

Glide源码分析

加载流程

Engine类负责启动加载并管理活动资源和缓存资源,其中的load方法提供路径加载图片

java 复制代码
 public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    //EngineKey:用于多路传输加载的仅内存缓存密钥
    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
        //重点:调用了loadFromMemory方法
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            options,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache,
            cb,
            callbackExecutor,
            key,
            startTime);
      }
    }

    // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
    // deadlock.
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

下面来看loadFromMemory()

java 复制代码
@Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
     
    //根据key去活动缓存中找
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
     //找到了直接返回数据   
      return active;
    }
    //找不到接着去内存缓存中找
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }
    return null;
  }

之后我们分别来看活动缓存和内存缓存

活动缓存

java 复制代码
 private EngineResource<?> loadFromActiveResources(Key key) {
     // activeResources:活动缓存
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }
  
   @Nullable
  synchronized EngineResource<?> get(Key key) {
      //弱引用
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }
  
   @Synthetic
  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    synchronized (this) {
        //从活动缓存中删除资源
      activeEngineResources.remove(ref.key);

      if (!ref.isCacheable || ref.resource == null) {
        return;
      }
    }

    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);
  }
  
    @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
        //将活动缓存数据写入内存缓存
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
  }

内存缓存

java 复制代码
 private EngineResource<?> loadFromCache(Key key) {
     //从内存缓存中删除数据
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      //将数据保存至活动缓存
      activeResources.activate(key, cached);
    }
    return cached;
  }
  
  private EngineResource<?> getEngineResourceFromCache(Key key) {
      //cache:MemoryCache类型
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

如果两种缓存都未找到,说明图片未保存,就会调用waitForExistingOrStartNewJob方法

java 复制代码
private <R> LoadStatus waitForExistingOrStartNewJob(...) {
    //通过添加和删除加载的回调并通知来管理加载的类,在加载完成时回调
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
    //负责从缓存数据或原始源解码资源的类
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

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

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }
java 复制代码
class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable {
  }
  ...
  //DiskCacheProvider跟磁盘缓存有关
  DecodeJob(DiskCacheProvider diskCacheProvider, Pools.Pool<DecodeJob<?>> pool) {
    this.diskCacheProvider = diskCacheProvider;
    this.pool = pool;
  }
  ...

LRU算法

近期最少使用算法,核心思想就是当缓存满时,淘汰最少使用的缓存对象

实现内存缓存的LruCache和硬盘缓存的DisLruCache的核心思想都是LRU缓存算法。

相关推荐
某空m2 小时前
【Android】Glide的使用
android·glide
QING6182 小时前
Jetpack Compose 中的 ViewModel 作用域管理 —— 新手指南
android·kotlin·android jetpack
鹏多多2 小时前
flutter-使用EventBus实现组件间数据通信
android·前端·flutter
Silence_Jy2 小时前
cs336Lecture 5 and7
java·redis·缓存
ShayneLee83 小时前
Nginx修改请求头响应头
android·运维·nginx
廋到被风吹走3 小时前
【数据库】【MySQL】高可用与扩展方案深度解析
android·数据库·mysql
恋猫de小郭3 小时前
Flutter 官方正式解决 WebView 在 iOS 26 上有点击问题
android·前端·flutter
CaspianSea7 小时前
编译Android 16 TV模拟器(一)
android
廋到被风吹走11 小时前
【数据库】【MySQL】InnoDB外键解析:约束机制、性能影响与最佳实践
android·数据库·mysql