结合 Fresco 和 Glide 的源码详细对比两者的缓存机制

215 阅读8分钟

结合 Fresco 和 Glide 的源码,详细对比两者的缓存机制,分析其设计、实现、优缺点以及适用场景。Fresco 和 Glide 都是 Android 平台上优秀的图片加载库,缓存机制是它们性能优化的核心,但两者的设计理念和实现细节有显著差异。以下是基于源码的对比分析。


  1. 缓存机制概述

Fresco 缓存机制

Fresco 采用三层缓存架构:

  • Bitmap 缓存(内存缓存):存储解码后的 Bitmap 或 CloseableImage,使用 CountingMemoryCache(LRU 算法)。
  • 编码缓存(内存缓存):存储未解码的图片数据(PooledByteBuffer),基于 MemoryCache。
  • 磁盘缓存:存储未解码的图片数据,使用 DiskStorageCache,支持小图和大图分开存储。
  • 查询顺序:Bitmap 缓存 → 编码缓存 → 磁盘缓存 → 网络/本地。
  • 特点:异步、线程安全,严格的内存管理(CloseableReference),支持渐进式 JPEG 和复杂后处理。

Glide 缓存机制

Glide 采用两层缓存架构:

  • 内存缓存:存储解码后的 Bitmap 或 Resource(包装的图片资源),使用 LruResourceCache(LRU 算法)和 ActiveResources(弱引用缓存)。
  • 磁盘缓存:存储未解码或解码后的图片数据,使用 DiskLruCache(基于 Okio 和 DiskLruCache 库)。
  • 查询顺序:内存缓存(ActiveResources → LruResourceCache)→ 磁盘缓存 → 网络/本地。
  • 特点:简单易用,灵活的缓存策略(DiskCacheStrategy),支持多种图片格式和变换。

  1. 源码对比

内存缓存

Fresco:

  • 实现类:CountingMemoryCache(Bitmap 缓存)和 MemoryCache(编码缓存)。

  • 存储对象:

    • Bitmap 缓存:CloseableImage(如 CloseableStaticBitmap)。
    • 编码缓存:PooledByteBuffer(未解码字节流)。
  • 管理机制:

    • 使用 CloseableReference 管理资源,引用计数确保内存安全。
    • 支持缓存修剪(trim)和淘汰(evict),应对内存压力。
    • 通过 MemoryCacheParams 配置缓存大小和条目数。
  • 源码示例(CountingMemoryCache):

    java

    public CloseableReference<V> cache(K key, CloseableReference<V> value) {
        ValueDescriptor<V> oldValue = mCachedEntries.put(key, value);
        if (oldValue != null) {
            CloseableReference.closeSafely(oldValue);
        }
        return CloseableReference.cloneOrNull(value);
    }
    
  • 特点:

    • 分层存储(解码和未解码数据分开),适合复杂场景。
    • 内存池(PooledByteBuffer)减少分配开销。
    • 渐进式 JPEG 支持,编码缓存可存储部分数据。

Glide:

  • 实现类:LruResourceCache(主内存缓存)和 ActiveResources(活动资源缓存)。

  • 存储对象:

    • Resource(包装 Bitmap 或其他图片资源)。
    • ActiveResources 使用弱引用存储正在使用的图片。
  • 管理机制:

    • LruResourceCache 基于 LRU 算法,限制内存大小。
    • ActiveResources 使用 WeakReference 管理活跃资源,防止频繁 GC。
    • 通过 MemoryCache 接口支持自定义缓存实现。
  • 源码示例(LruResourceCache):

    java

    public class LruResourceCache extends LruCache<String, Resource<?>> {
        public LruResourceCache(long size) {
            super(size);
        }
        
        @Override
        protected void onItemEvicted(String key, Resource<?> item) {
            if (item != null) {
                item.release();
            }
        }
    }
    
  • 特点:

    • 简单高效,内存缓存仅存储解码后的图片。
    • ActiveResources 优化活跃资源管理,减少重复加载。
    • 支持动态调整缓存大小(MemorySizeCalculator)。

对比:

  • 存储内容:Fresco 区分解码(Bitmap)和未解码(编码)数据,适合渐进式加载和后处理;Glide 只缓存解码后的图片,简化实现。
  • 内存管理:Fresco 的 CloseableReference 更严格,适合大型应用;Glide 的弱引用机制更轻量,适合中小型应用。
  • 配置复杂性:Fresco 需要配置两层内存缓存(MemoryCacheParams),Glide 的单一缓存更易上手。

磁盘缓存

Fresco:

  • 实现类:DiskStorageCache,基于 DiskStorage(如 DynamicDefaultDiskStorage)。

  • 存储对象:未解码的图片数据(字节流)。

  • 管理机制:

    • 支持小图和大图分开存储(DiskCacheConfig),优化 IO。
    • LRU 淘汰,限制文件数和存储空间。
    • 版本控制防止数据冲突。
  • 源码示例(DiskStorageCache):

    java

    public void insert(CacheKey key, WriterCallback callback) throws IOException {
        String resourceId = key.getUriString();
        mStorage.insert(resourceId, callback);
    }
    
  • 特点:

    • 小图和大图分开存储,适合高并发场景。
    • 支持渐进式 JPEG,部分数据可缓存。
    • 复杂配置(DiskCacheConfig)支持细粒度控制。

Glide:

  • 实现类:DiskLruCacheWrapper,基于 Jake Wharton 的 DiskLruCache。

  • 存储对象:解码后的 Bitmap 或原始数据(取决于 DiskCacheStrategy)。

  • 管理机制:

    • 支持多种缓存策略(DiskCacheStrategy):

      • ALL:缓存原始和变换后的图片。
      • DATA:仅缓存原始数据。
      • RESOURCE:仅缓存变换后的图片。
      • NONE:不缓存。
    • LRU 淘汰,限制缓存大小(默认 250MB)。

  • 源码示例(DiskLruCacheWrapper):

    java

    public class DiskLruCacheWrapper implements DiskCache {
        private final DiskLruCache diskLruCache;
        
        public void put(Key key, Writer writer) {
            Editor editor = diskLruCache.edit(key.getSafeKey());
            if (editor != null) {
                writer.write(new FileOutputStream(editor.getFile(0)));
                editor.commit();
            }
        }
    }
    
  • 特点:

    • 灵活的缓存策略,适应不同场景。
    • 简单易用,默认配置适合大多数应用。
    • 依赖成熟的 DiskLruCache 库,稳定性高。

对比:

  • 存储内容:Fresco 只缓存未解码数据,解码开销推迟到运行时;Glide 可缓存解码或未解码数据,策略更灵活。
  • 性能:Fresco 的小图/大图分离优化高并发 IO,Glide 的单一缓存更适合简单场景。
  • 配置:Glide 的 DiskCacheStrategy 简单直观,Fresco 的配置更复杂但功能强大。

  1. 优缺点对比

Fresco 缓存机制

优点:

  1. 多级缓存:Bitmap、编码和磁盘缓存分层,优化命中率和性能。
  2. 内存管理严格:CloseableReference 和内存池(PooledByteBuffer)防止泄漏,适合大型应用。
  3. 渐进式加载:编码缓存支持部分数据解码,提升用户体验。
  4. 高并发优化:小图/大图分离和异步 IO 适合复杂场景。
  5. 可扩展性:支持自定义 CacheKeyFactory 和 DiskStorage,适应特殊需求。

缺点:

  1. 复杂性:三层缓存和异步回调增加学习和维护成本。
  2. 内存占用:编码缓存和内存池占用额外内存,低内存设备可能触发频繁淘汰。
  3. 磁盘 IO 开销:小图/大图分离增加管理复杂性,高并发下 IO 仍可能成为瓶颈。
  4. 配置繁琐:需要细致配置 ImagePipelineConfig,初学者不易上手。

Glide 缓存机制

优点:

  1. 简单易用:两层缓存和 DiskCacheStrategy 直观,适合快速开发。
  2. 灵活性:支持多种缓存策略,适应不同场景(如只缓存变换后的图片)。
  3. 轻量级:弱引用和单一内存缓存减少内存占用,适合中小型应用。
  4. 生态成熟:集成 DiskLruCache 和 OkHttp,稳定性和兼容性强。
  5. 动态调整:MemorySizeCalculator 自动适配设备内存。

缺点:

  1. 功能局限:不支持渐进式 JPEG,复杂后处理需要额外实现。
  2. 内存管理较弱:弱引用可能导致频繁 GC,活跃资源管理不如 Fresco 严格。
  3. 高并发不足:单一磁盘缓存未优化高并发 IO,性能可能逊于 Fresco。
  4. 扩展性有限:缓存机制较简单,自定义复杂场景需额外工作。

  1. 适用场景对比

Fresco

  • 适合场景:

    • 大型应用:如社交媒体(Facebook、Instagram),需要处理海量图片、高并发请求。
    • 复杂图片处理:支持渐进式加载、自定义后处理(如滤镜、裁剪)。
    • 低内存设备:严格的内存管理和缓存修剪适合内存受限场景。
    • 高性能需求:多级缓存和异步 IO 优化复杂 UI 和列表滑动。
  • 示例:新闻 Feed 应用,包含动态图片尺寸、渐进式加载和复杂变换。

Glide

  • 适合场景:

    • 中小型应用:如电商、博客,图片加载需求简单。
    • 快速开发:默认配置和简单 API 适合快速迭代。
    • 标准图片加载:无需渐进式加载或复杂后处理。
    • 跨平台迁移:Glide 的简单性和兼容性适合多平台项目。
  • 示例:电商应用,加载固定尺寸的商品图片。


  1. 性能对比(基于源码分析)

内存使用

  • Fresco:编码缓存和内存池增加内存开销,但 CloseableReference 确保安全释放,适合内存敏感场景。
  • Glide:单一内存缓存和弱引用减少占用,但在高频加载下可能触发频繁 GC。

缓存命中率

  • Fresco:三层缓存(尤其是编码缓存)提高命中率,渐进式加载进一步优化体验。
  • Glide:两层缓存命中率略低,但灵活的 DiskCacheStrategy 可优化特定场景。

磁盘 IO

  • Fresco:小图/大图分离和异步 IO 优化高并发,但管理复杂。
  • Glide:单一 DiskLruCache 简单高效,但高并发下 IO 性能稍逊。

加载速度

  • Fresco:多级缓存和渐进式加载在复杂场景下更快,但初始配置成本高。
  • Glide:简单场景下加载速度接近 Fresco,但在复杂变换或高并发下略逊。

  1. 优化建议

Fresco

  • 调整缓存大小:通过 ImagePipelineConfig 配置 MemoryCacheParams 和 DiskCacheConfig,适配设备:

    java

    ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
        .setBitmapMemoryCacheParamsSupplier(() -> new MemoryCacheParams(
            maxCacheSize, maxCacheEntries, maxEvictionSize, maxEvictionEntries, maxCacheEntrySize))
        .setMainDiskCacheConfig(DiskCacheConfig.newBuilder(context)
            .setMaxCacheSize(maxDiskCacheSize)
            .build())
        .build();
    
  • 优化 CacheKey:自定义 CacheKeyFactory 区分不同分辨率或变换:

    java

    public class CustomCacheKeyFactory implements CacheKeyFactory {
        public CacheKey getBitmapCacheKey(ImageRequest request, Object callerContext) {
            return new SimpleCacheKey(request.getSourceUri() + "_" + request.getResizeOptions());
        }
    }
    

Glide

  • 选择合适的缓存策略:根据需求设置 DiskCacheStrategy:

    java

    Glide.with(context)
        .load(url)
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .into(imageView);
    
  • 动态内存管理:使用 MemorySizeCalculator 调整缓存大小:

    java

    MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();
    Glide.get(context).setMemoryCategory(MemoryCategory.NORMAL);
    

总结

Fresco 的缓存机制通过三层架构(Bitmap、编码、磁盘缓存)提供高性能和严格内存管理,适合大型、复杂应用,尤其是需要渐进式加载和后处理的场景。其缺点是复杂性和较高的内存/配置成本。Glide 的两层缓存(内存和磁盘)简单高效,易于上手,适合中小型应用和标准图片加载场景,但缺乏渐进式加载和高并发优化。

特性FrescoGlide
缓存层级三层(Bitmap、编码、磁盘)两层(内存、磁盘)
内存管理CloseableReference、内存池弱引用、LruResourceCache
磁盘缓存小图/大图分离,复杂配置DiskLruCache,策略灵活
渐进式加载支持不支持
复杂场景支持强大(后处理、高并发)一般(需额外实现)
易用性较复杂简单易用
适用场景大型应用、复杂 UI中小型应用、标准加载