Glide 4.x 三级缓存模块的实现原理

结合 Glide 4.x 的源码,详细讲解 Glide 三级缓存模块(内存缓存、磁盘缓存和网络加载)的实现细节。Glide 的缓存机制是其高效性和低内存占用的关键,通过内存缓存(LruResourceCacheActiveResources)、磁盘缓存(DiskLruCache)以及网络加载的协同工作,实现了快速的图片加载和资源管理。以下是每个缓存模块的详细分析,包括源码、设计原理和优化策略。


1. 内存缓存

内存缓存是 Glide 的第一级缓存,用于存储已加载并解码的图片资源(如 BitmapDrawable),以实现快速访问。内存缓存由两部分组成:LruResourceCacheActiveResources

1.1 LruResourceCache

LruResourceCache 是基于 LRU(Least Recently Used)算法的内存缓存,存储未被当前 Target 使用的资源。

源码分析:LruResourceCache
java 复制代码
public class LruResourceCache extends LruCache<Key, Resource<?>> {
    private final ResourceRemovedListener listener;

    public LruResourceCache(long size) {
        super(size);
        this.listener = resource -> resource.recycle();
    }

    @Override
    protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
        if (item != null) {
            listener.onResourceRemoved(item);
        }
    }
}
  • 作用 :存储加载完成的资源,基于内存大小限制(size)进行 LRU 淘汰。
  • 实现细节
    • LruResourceCache 继承自 LruCache,使用键值对(Key -> Resource)存储资源。
    • Key 是一个 EngineKey 实例,包含图片的 URI、尺寸、变换等信息,确保缓存的唯一性。
    • 当缓存超出大小限制时,调用 onItemEvicted,触发 Resource.recycle(),释放资源(如回收 BitmapBitmapPool)。
    • size 默认基于设备可用内存计算(通常为可用内存的 1/8,参考 MemorySizeCalculator)。
关键点
  • LruResourceCache 只存储未被 Target 引用的资源,一旦资源被 Target 使用,会转移到 ActiveResources
  • 缓存清理通过 onTrimMemoryonLowMemory 响应系统内存压力。

1.2 ActiveResources

ActiveResources 是内存缓存的另一部分,存储当前正在被 Target 使用的资源,防止被垃圾回收。

源码分析:ActiveResources
java 复制代码
final class ActiveResources {
    private final Map<Key, ResourceWeakReference> activeResources = new HashMap<>();
    private final ReferenceQueue<Resource<?>> referenceQueue = new ReferenceQueue<>();

    @Nullable
    Resource<?> get(Key key) {
        ResourceWeakReference ref = activeResources.get(key);
        if (ref != null) {
            Resource<?> resource = ref.get();
            if (resource == null) {
                activeResources.remove(key);
            }
            return resource;
        }
        return null;
    }

    void deactivate(Key key) {
        ResourceWeakReference removed = activeResources.remove(key);
        if (removed != null) {
            Resource<?> resource = removed.get();
            if (resource != null) {
                listener.onResourceReleased(key, resource);
            }
        }
    }
}
  • 作用 :存储被 Target 使用的资源,使用弱引用(WeakReference)管理,允许 GC 回收未使用的资源。
  • 实现细节
    • activeResources 是一个 HashMap,键为 Key,值为 ResourceWeakReference(包装了 Resource 的弱引用)。
    • Target 使用资源时,资源从 LruResourceCache 移到 ActiveResources
    • 当资源不再被使用(Target 调用 clear),deactivate 将资源移回 LruResourceCache 或回收。
    • ReferenceQueue 用于监控弱引用被 GC 回收的情况,自动清理无效条目。
关键点
  • ActiveResources 使用弱引用避免内存泄漏,当 ImageViewTarget 被销毁时,资源可被 GC 回收。
  • 资源从 ActiveResources 移回 LruResourceCache 时,会重新进入 LRU 管理。

内存缓存流程

  1. 检查缓存Engine.load() 首先调用 memoryCache.get(key) 检查内存缓存。
  2. 命中 ActiveResources :如果资源在 ActiveResources 中,直接返回。
  3. 命中 LruResourceCache :如果在 LruResourceCache 中,移到 ActiveResources 并返回。
  4. 未命中:继续检查磁盘缓存或网络加载。
  5. 存储 :加载完成的新资源存入 LruResourceCache,使用时移到 ActiveResources

优化策略

  • BitmapPool :Glide 使用 LruBitmapPool 复用 Bitmap,减少内存分配。被回收的 Bitmap 会进入 BitmapPool

    java 复制代码
    public class LruBitmapPool implements BitmapPool {
        private final LruPoolStrategy strategy;
    
        @Override
        public void put(Bitmap bitmap) {
            if (!bitmap.isMutable()) {
                bitmap.recycle();
                return;
            }
            strategy.put(bitmap);
        }
    }
  • 动态内存管理 :通过 MemorySizeCalculator 根据设备内存动态调整缓存大小。

  • 弱引用ActiveResources 使用弱引用,降低内存泄漏风险。


2. 磁盘缓存

磁盘缓存是 Glide 的第二级缓存,用于存储图片的原始数据或转换后的资源。Glide 使用 DiskLruCache 实现磁盘缓存,支持多种缓存策略(如 DiskCacheStrategy.ALL)。

源码分析:DiskLruCache

java 复制代码
public class DiskLruCache implements DiskCache {
    private final File directory;
    private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
    private final DiskLruCacheWrapper cache;

    public static DiskCache get(File directory, long maxSize) {
        return new DiskLruCache(directory, maxSize);
    }

    @Override
    public void put(Key key, Writer writer) {
        String safeKey = safeKeyGenerator.getSafeKey(key);
        writeLocker.acquire(safeKey);
        try {
            cache.put(safeKey, writer);
        } finally {
            writeLocker.release(safeKey);
        }
    }

    @Override
    public File get(Key key) {
        String safeKey = safeKeyGenerator.getSafeKey(key);
        return cache.get(safeKey);
    }
}
  • 作用:存储图片数据到磁盘,基于 LRU 算法管理缓存文件。
  • 实现细节
    • directory 是缓存目录(默认在应用的缓存目录下)。
    • maxSize 是磁盘缓存的最大大小(默认 250MB,可通过 GlideBuilder 配置)。
    • safeKeyGeneratorKey(包含 URI、变换等)转换为安全的文件名(通过 SHA-256 哈希)。
    • DiskCacheWriteLocker 确保线程安全的文件写入。

缓存策略:DiskCacheStrategy

DiskCacheStrategy 控制缓存行为:

  • DiskCacheStrategy.ALL:缓存原始数据和转换后的资源。
  • DiskCacheStrategy.DATA:仅缓存原始数据。
  • DiskCacheStrategy.RESOURCE:仅缓存转换后的资源。
  • DiskCacheStrategy.NONE:不使用磁盘缓存。
  • DiskCacheStrategy.AUTOMATIC(默认):根据数据源和编码策略智能选择。

磁盘缓存流程

  1. 检查缓存DecodeJob 调用 diskCache.get(key) 检查磁盘缓存。
  2. 命中 :读取缓存文件,解码为 Resource(如 Bitmap)。
  3. 未命中:从网络加载数据。
  4. 存储 :加载完成的数据通过 diskCache.put(key, writer) 写入缓存。
源码分析:DecodeJob.decodeFromCache
java 复制代码
private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    if (diskCacheStrategy.isResourceCacheable()) {
        result = decodeFromResourceCache();
    }
    if (result == null && diskCacheStrategy.isDataCacheable()) {
        result = decodeFromDataCache();
    }
    return result;
}
  • 作用 :根据 DiskCacheStrategy,优先尝试加载转换后的资源(RESOURCE),再尝试原始数据(DATA)。
  • 实现细节
    • decodeFromResourceCache:加载转换后的资源(如已裁剪的 Bitmap)。
    • decodeFromDataCache:加载原始数据(如 JPEG 文件),然后解码和转换。

优化策略

  • 线程安全DiskCacheWriteLocker 使用锁机制,防止多线程写入冲突。
  • 文件命名:通过 SHA-256 哈希生成唯一文件名,避免非法字符问题。
  • 异步写入:磁盘写入在后台线程执行,不阻塞主线程。
  • 缓存清理 :当缓存超过 maxSize 时,DiskLruCache 自动删除最久未使用的文件。

3. 网络加载

网络加载是 Glide 的第三级缓存,当内存和磁盘缓存都未命中时,Glide 通过 DataFetcher 从网络获取数据。

源码分析:HttpUrlFetcher

java 复制代码
public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private final GlideUrl glideUrl;
    private final int timeout;

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
        HttpURLConnection urlConnection = null;
        try {
            urlConnection = buildAndConfigureConnection();
            InputStream result = urlConnection.getInputStream();
            callback.onDataReady(result);
        } catch (IOException e) {
            callback.onLoadFailed(e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
    }
}
  • 作用:通过 HTTP 请求获取图片数据流。
  • 实现细节
    • GlideUrl 封装了图片的 URL 和请求头。
    • buildAndConfigureConnection 配置连接参数(如超时、缓存控制)。
    • 数据以 InputStream 形式返回,交给解码器处理。

网络加载流程

  1. 触发DecodeJob 调用 DataFetcher.loadData() 发起网络请求。
  2. 数据获取HttpUrlFetcher 使用 HttpURLConnection 下载数据。
  3. 解码 :数据流通过 StreamDecoder(如 StreamBitmapDecoder)解码为 Bitmap 或其他资源。
  4. 缓存 :原始数据或转换后的资源根据 DiskCacheStrategy 存入磁盘缓存。

优化策略

  • 连接复用 :Glide 使用 OkHttp(通过 OkHttpIntegration)或自定义 HttpUrlConnection 优化网络性能。
  • 异步执行 :网络请求在 sourceExecutor 线程池中运行,避免阻塞。
  • 错误重试HttpUrlFetcher 支持超时和重试机制,提高成功率。

三级缓存协同工作

  1. 优先级
    • 内存缓存(LruResourceCacheActiveResources):最快,优先检查。
    • 磁盘缓存(DiskLruCache):次快,检查原始数据或转换资源。
    • 网络加载(HttpUrlFetcher):最慢,仅在缓存未命中时触发。
  2. 流程
    • Engine.load() 按顺序检查 memoryCachediskCache,最后发起网络请求。
    • 加载完成的数据根据配置存入内存和磁盘缓存。
  3. 缓存键
    • 所有缓存使用统一的 KeyEngineKeyDataCacheKey),包含 URI、尺寸、变换等,确保缓存一致性。

关键设计亮点

  1. 模块化
    • 缓存模块通过接口(MemoryCacheDiskCacheDataFetcher)解耦,支持自定义实现。
  2. 高效性
    • 内存缓存使用 LRU 和弱引用,平衡速度和内存占用。
    • 磁盘缓存通过 DiskLruCache 实现高效的文件管理。
    • 网络加载支持连接复用和异步执行。
  3. 灵活性
    • DiskCacheStrategy 提供多种缓存选项,满足不同场景。
    • BitmapPool 复用 Bitmap,减少 GC 压力。
  4. 线程安全
    • 内存缓存使用同步机制,磁盘缓存使用锁,网络加载在独立线程运行。

源码中的典型调用链

以下是三级缓存的典型调用链:

java 复制代码
// Engine.load() 检查内存缓存
EngineResource<?> cached = memoryCache.get(key);
if (cached != null) {
    callback.onResourceReady(cached);
    return;
}

// DecodeJob.run() 检查磁盘缓存
Resource<?> resource = decodeFromCache();
if (resource != null) {
    return resource;
}

// HttpUrlFetcher.loadData() 发起网络请求
InputStream data = urlConnection.getInputStream();
resource = decoder.decode(data, width, height);

// 存储到缓存
memoryCache.put(key, resource);
diskCache.put(key, writer);

常见问题与优化建议

  1. 内存缓存占用过多
    • 调整 MemorySizeCalculator 的缓存大小,或通过 GlideBuilder 设置更小的缓存。
    • 增加 BitmapPool 使用率,减少新 Bitmap 分配。
  2. 磁盘缓存命中率低
    • 检查 DiskCacheStrategy 配置,确保缓存原始数据和转换资源。
    • 使用一致的 Key(避免频繁变换导致缓存失效)。
  3. 网络加载慢
    • 集成 OkHttp 或其他高效网络库,优化连接复用。
    • 配置合理的超时和重试策略。
相关推荐
_一条咸鱼_17 分钟前
深度剖析:Android NestedScrollView 惯性滑动原理大揭秘
android·面试·android jetpack
_一条咸鱼_17 分钟前
深度揭秘!Android NestedScrollView 绘制原理全解析
android·面试·android jetpack
_一条咸鱼_17 分钟前
揭秘 Android CoordinatorLayout:从源码深度解析其协同工作原理
android·面试·android jetpack
_一条咸鱼_18 分钟前
揭秘 Android View 的 TranslationY 位移原理:源码深度剖析
android·面试·android jetpack
_一条咸鱼_19 分钟前
揭秘 Android NestedScrollView 滑动原理:源码深度剖析
android·面试·android jetpack
_一条咸鱼_19 分钟前
深度揭秘:Android NestedScrollView 拖动原理全解析
android·面试·android jetpack
_一条咸鱼_20 分钟前
揭秘!Android RecyclerView 预取(Prefetch)原理深度剖析
android·面试·android jetpack
_一条咸鱼_20 分钟前
揭秘 Android ImageView:从源码深度剖析使用原理
android·面试·android jetpack
_一条咸鱼_23 分钟前
深入剖析 Android AppBarLayout:从源码洞悉其工作原理
android·面试·android jetpack
_一条咸鱼_23 分钟前
揭秘!Android NestedScrollView 布局原理深度剖析
android·面试·android jetpack