以下将结合 Fresco 源码详细讲解其缓存机制,包括内存缓存、编码缓存和磁盘缓存的设计与实现,分析其工作原理、优缺点。Fresco 的缓存机制是其高效加载图片和优化内存管理的核心,采用多级缓存架构以最大化性能和用户体验。
Fresco 缓存机制概述
Fresco 的缓存机制分为三层,分别针对不同阶段的图片数据:
- Bitmap 缓存(内存缓存):存储解码后的 Bitmap 或 CloseableImage,用于快速渲染到 UI。
- 编码缓存(Encoded Cache,内存缓存):存储未解码的原始图片数据(字节流),避免重复下载或解码。
- 磁盘缓存(Disk Cache):持久化存储图片数据,支持小图和大图分开存储,减少网络请求。
这些缓存通过 CacheKey 唯一标识资源,ImagePipeline 按优先级(Bitmap 缓存 → 编码缓存 → 磁盘缓存 → 网络/本地)查询数据。以下是详细讲解。
- Bitmap 缓存(内存缓存)
实现原理
-
存储对象:解码后的 Bitmap 或 CloseableImage(如 CloseableBitmap 或 CloseableStaticBitmap)。
-
实现类:CountingMemoryCache,基于 LRU(最近最少使用)算法,管理内存中的 Bitmap。
-
管理机制:
- 使用 CloseableReference 包装 Bitmap,通过引用计数确保内存安全。
- 缓存大小通过 MemoryCacheParams 配置,限制最大条目数和内存占用。
- 支持缓存淘汰(eviction)和修剪(trim)以应对内存压力。
-
源码分析:
-
CountingMemoryCache 的核心方法:
java
public class CountingMemoryCache<K, V> implements MemoryCache<K, V> { private final LruCache<K, ValueDescriptor<V>> mCachedEntries = new LruCache<>(); 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); } public CloseableReference<V> get(K key) { ValueDescriptor<V> value = mCachedEntries.get(key); return value != null ? CloseableReference.cloneOrNull(value.get()) : null; } } -
cache 方法存储 Bitmap,get 方法查询缓存,CloseableReference 确保资源正确释放。
-
内存压力下,trimToNothing 或 evictAll 清空缓存:
java
public void trim(MemoryTrimType trimType) { if (trimType == MemoryTrimType.OnCloseToDalvikHeapLimit) { evictOldest(maxEvictionSize); } else if (trimType == MemoryTrimType.OnSystemLowMemory) { trimToNothing(); } }
-
查询流程
-
ImagePipeline 通过 CacheKey 查询 BitmapMemoryCache:
java
CloseableReference<CloseableImage> cachedImage = mBitmapMemoryCache.get(cacheKey); if (cachedImage != null) { return DataSourceSupplier.createSuccessfulDataSource(cachedImage); } -
如果命中,直接返回 CloseableImage,用于渲染。
- 编码缓存(Encoded Cache,内存缓存)
实现原理
-
存储对象:未解码的图片数据(PooledByteBuffer),通常是 JPEG 或 PNG 的字节流。
-
实现类:BufferedDiskCache(尽管名字中有 Disk,编码缓存通常在内存中)或 MemoryCache。
-
管理机制:
- 使用 PooledByteBuffer 管理字节流,基于内存池复用缓冲区,减少内存分配。
- 缓存大小通过 MemoryCacheParams 配置,LRU 策略淘汰。
- 编码缓存命中后,数据需解码为 Bitmap。
-
源码分析:
-
EncodedMemoryCache 的存储逻辑:
java
public CloseableReference<PooledByteBuffer> cache(CacheKey key, CloseableReference<PooledByteBuffer> value) { return mMemoryCache.cache(key, value); } -
查询逻辑:
java
CloseableReference<PooledByteBuffer> encodedImage = mEncodedMemoryCache.get(cacheKey); if (encodedImage != null) { return decodeEncodedImage(encodedImage); }
-
查询流程
-
如果 Bitmap 缓存未命中,ImagePipeline 查询编码缓存:
java
CloseableReference<PooledByteBuffer> encodedImage = mEncodedMemoryCache.get(cacheKey); if (encodedImage != null) { return mImageDecoder.decode(encodedImage, imageRequest.getImageDecodeOptions()); } -
命中后,调用 ImageDecoder 解码为 CloseableImage。
- 磁盘缓存(Disk Cache)
实现原理
-
存储对象:未解码的图片数据,存储在设备文件系统中。
-
实现类:DiskStorageCache,基于 DiskStorage(如 DynamicDefaultDiskStorage)。
-
管理机制:
- 支持小图(small)和大图(default)分开存储,优化 IO 性能。
- 缓存大小通过 DiskCacheConfig 配置,限制最大文件数和存储空间。
- 使用 LRU 淘汰策略,定期清理过期或不常用的文件。
- 磁盘缓存支持版本控制,防止旧版本数据冲突。
-
源码分析:
-
DiskStorageCache 的存储逻辑:
java
public class DiskStorageCache implements FileCache { private final DiskStorage mStorage; public void insert(CacheKey key, WriterCallback callback) throws IOException { String resourceId = key.getUriString(); mStorage.insert(resourceId, callback); } public BinaryResource getResource(CacheKey key) { String resourceId = key.getUriString(); return mStorage.getResource(resourceId, key); } } -
缓存清理:
java
public void clearOldEntries(long cacheExpirationMs) { mStorage.purgeUnexpectedResources(); mStorage.clearOldEntries(cacheExpirationMs); }
-
查询流程
-
如果内存缓存未命中,查询磁盘缓存:
java
BinaryResource resource = mMainDiskCache.getResource(cacheKey); if (resource != null) { PooledByteBuffer buffer = readResourceToBuffer(resource); return decodeEncodedImage(CloseableReference.of(buffer)); } -
命中后,数据读入 PooledByteBuffer,解码为 CloseableImage。
缓存查询完整流程
ImagePipeline 的缓存查询遵循以下顺序:
- Bitmap 缓存:查询 CountingMemoryCache,命中直接返回 CloseableImage。
- 编码缓存:查询 EncodedMemoryCache,命中后解码为 CloseableImage。
- 磁盘缓存:查询 DiskStorageCache,命中后读取数据,存入编码缓存并解码。
- 网络/本地:缓存未命中,触发 NetworkFetcher 或其他 Producer 下载/读取数据,存入各级缓存。
源码示例(ImagePipeline):
java
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest request) {
CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(request);
// 1. 查询 Bitmap 缓存
CloseableReference<CloseableImage> cachedImage = mBitmapMemoryCache.get(cacheKey);
if (cachedImage != null) {
return ImmediateDataSource.create(cachedImage);
}
// 2. 查询编码缓存
CloseableReference<PooledByteBuffer> encodedImage = mEncodedMemoryCache.get(cacheKey);
if (encodedImage != null) {
return decodeFromEncodedImage(encodedImage, request);
}
// 3. 查询磁盘缓存
return fetchFromDiskCacheOrNetwork(request, cacheKey);
}
缓存机制的优缺点
优点
-
高效性:
- 多级缓存减少重复下载和解码,Bitmap 缓存直接提供渲染数据,编码缓存避免重复解码,磁盘缓存减少网络请求。
- LRU 策略确保高频图片驻留缓存,提升命中率。
-
内存优化:
- CloseableReference 和 PooledByteBuffer 严格管理内存,防止泄漏。
- 内存池复用缓冲区,减少 GC 压力。
-
灵活性:
- DiskCacheConfig 支持小图和大图分开存储,优化 IO。
- CacheKeyFactory 允许自定义缓存键,支持复杂场景(如不同分辨率图片)。
-
渐进式加载支持:
- 编码缓存和磁盘缓存支持渐进式 JPEG,部分数据即可解码显示。
-
可扩展性:
- 开发者可自定义 DiskStorage 或 CacheKeyFactory,适配特定需求。
缺点
-
复杂性:
- 多级缓存和 Producer 链增加了代码复杂度和维护成本,开发者需理解 ImagePipeline 的工作原理。
- 源码中的异步回调和线程切换可能导致调试困难。
-
内存占用:
- Bitmap 缓存存储解码后的图片,在低内存设备上可能触发频繁的缓存淘汰或 OOM。
- 编码缓存和内存池也占用额外内存,需仔细配置 MemoryCacheParams。
-
磁盘 IO 开销:
- 磁盘缓存的读写操作可能成为瓶颈,尤其在高并发场景或低性能设备上。
- 小图和大图分开存储增加了管理复杂度。
-
缓存一致性:
- 缓存更新机制依赖 CacheKey,如果图片内容变化但 URI 未变,可能导致显示旧数据。
- 开发者需手动清理缓存或实现版本控制。
-
学习曲线:
- 相较于 Glide 或 Picasso,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()); } }
-
-
优化磁盘缓存:
-
启用小图和大图分开存储,减少 IO:
java
DiskCacheConfig smallCache = DiskCacheConfig.newBuilder(context) .setBaseDirectoryName("small_images") .setMaxCacheSize(10 * 1024 * 1024) // 10MB .build();
-
-
监控缓存命中率:
-
使用 ImagePipeline 的 CacheEventListener 监控缓存性能,调整策略:
java
public class CustomCacheEventListener implements CacheEventListener { public void onHit(CacheEvent cacheEvent) { Log.d("Cache", "Cache hit: " + cacheEvent.getCacheKey()); } }
-
总结
Fresco 的缓存机制通过 Bitmap 缓存、编码缓存和磁盘缓存三层架构,结合 CountingMemoryCache、BufferedDiskCache 和 DiskStorageCache 的实现,实现了高效的图片加载和内存管理。其优点在于高性能、内存安全和灵活性,但缺点包括复杂性、内存占用和磁盘 IO 开销。开发者可通过配置 ImagePipelineConfig 和自定义 CacheKey 优化缓存行为。