结合 Fresco 和 Glide 的源码,详细对比两者的缓存机制,分析其设计、实现、优缺点以及适用场景。Fresco 和 Glide 都是 Android 平台上优秀的图片加载库,缓存机制是它们性能优化的核心,但两者的设计理念和实现细节有显著差异。以下是基于源码的对比分析。
- 缓存机制概述
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),支持多种图片格式和变换。
- 源码对比
内存缓存
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 的配置更复杂但功能强大。
- 优缺点对比
Fresco 缓存机制
优点:
- 多级缓存:Bitmap、编码和磁盘缓存分层,优化命中率和性能。
- 内存管理严格:CloseableReference 和内存池(PooledByteBuffer)防止泄漏,适合大型应用。
- 渐进式加载:编码缓存支持部分数据解码,提升用户体验。
- 高并发优化:小图/大图分离和异步 IO 适合复杂场景。
- 可扩展性:支持自定义 CacheKeyFactory 和 DiskStorage,适应特殊需求。
缺点:
- 复杂性:三层缓存和异步回调增加学习和维护成本。
- 内存占用:编码缓存和内存池占用额外内存,低内存设备可能触发频繁淘汰。
- 磁盘 IO 开销:小图/大图分离增加管理复杂性,高并发下 IO 仍可能成为瓶颈。
- 配置繁琐:需要细致配置 ImagePipelineConfig,初学者不易上手。
Glide 缓存机制
优点:
- 简单易用:两层缓存和 DiskCacheStrategy 直观,适合快速开发。
- 灵活性:支持多种缓存策略,适应不同场景(如只缓存变换后的图片)。
- 轻量级:弱引用和单一内存缓存减少内存占用,适合中小型应用。
- 生态成熟:集成 DiskLruCache 和 OkHttp,稳定性和兼容性强。
- 动态调整:MemorySizeCalculator 自动适配设备内存。
缺点:
- 功能局限:不支持渐进式 JPEG,复杂后处理需要额外实现。
- 内存管理较弱:弱引用可能导致频繁 GC,活跃资源管理不如 Fresco 严格。
- 高并发不足:单一磁盘缓存未优化高并发 IO,性能可能逊于 Fresco。
- 扩展性有限:缓存机制较简单,自定义复杂场景需额外工作。
- 适用场景对比
Fresco
-
适合场景:
- 大型应用:如社交媒体(Facebook、Instagram),需要处理海量图片、高并发请求。
- 复杂图片处理:支持渐进式加载、自定义后处理(如滤镜、裁剪)。
- 低内存设备:严格的内存管理和缓存修剪适合内存受限场景。
- 高性能需求:多级缓存和异步 IO 优化复杂 UI 和列表滑动。
-
示例:新闻 Feed 应用,包含动态图片尺寸、渐进式加载和复杂变换。
Glide
-
适合场景:
- 中小型应用:如电商、博客,图片加载需求简单。
- 快速开发:默认配置和简单 API 适合快速迭代。
- 标准图片加载:无需渐进式加载或复杂后处理。
- 跨平台迁移:Glide 的简单性和兼容性适合多平台项目。
-
示例:电商应用,加载固定尺寸的商品图片。
- 性能对比(基于源码分析)
内存使用
- Fresco:编码缓存和内存池增加内存开销,但 CloseableReference 确保安全释放,适合内存敏感场景。
- Glide:单一内存缓存和弱引用减少占用,但在高频加载下可能触发频繁 GC。
缓存命中率
- Fresco:三层缓存(尤其是编码缓存)提高命中率,渐进式加载进一步优化体验。
- Glide:两层缓存命中率略低,但灵活的 DiskCacheStrategy 可优化特定场景。
磁盘 IO
- Fresco:小图/大图分离和异步 IO 优化高并发,但管理复杂。
- Glide:单一 DiskLruCache 简单高效,但高并发下 IO 性能稍逊。
加载速度
- Fresco:多级缓存和渐进式加载在复杂场景下更快,但初始配置成本高。
- Glide:简单场景下加载速度接近 Fresco,但在复杂变换或高并发下略逊。
- 优化建议
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 的两层缓存(内存和磁盘)简单高效,易于上手,适合中小型应用和标准图片加载场景,但缺乏渐进式加载和高并发优化。
| 特性 | Fresco | Glide |
|---|---|---|
| 缓存层级 | 三层(Bitmap、编码、磁盘) | 两层(内存、磁盘) |
| 内存管理 | CloseableReference、内存池 | 弱引用、LruResourceCache |
| 磁盘缓存 | 小图/大图分离,复杂配置 | DiskLruCache,策略灵活 |
| 渐进式加载 | 支持 | 不支持 |
| 复杂场景支持 | 强大(后处理、高并发) | 一般(需额外实现) |
| 易用性 | 较复杂 | 简单易用 |
| 适用场景 | 大型应用、复杂 UI | 中小型应用、标准加载 |