结合 Fresco 源码,深入分析其在 Fresco 缓存机制中的作用CloseableReference 是 Fresco 内存管理的核心组件,用于确保图片资源(如 Bitmap 和 PooledByteBuffer)的安全分配和释放,防止内存泄漏,尤其在高并发和复杂图片加载场景下表现出色。
- CloseableReference 概述
CloseableReference 是 Fresco 用于管理内存敏感资源的引用计数机制,类似于 Java 的 AutoCloseable 和智能指针。它包装了需要释放的资源(如 Bitmap 或 PooledByteBuffer),通过引用计数跟踪资源的使用情况,确保资源在不再需要时被正确释放。CloseableReference 广泛应用于 Fresco 的缓存(如 CountingMemoryCache)、图片解码和渲染流程中。
核心目标:
- 防止内存泄漏:确保 Bitmap 和字节缓冲区在不再使用时被回收。
- 支持并发安全:在多线程环境下正确管理资源。
- 简化资源管理:提供统一的 API,隐藏底层释放逻辑。
源码位置:
-
CloseableReference 定义在 com.facebook.common.references 包中,核心类包括:
- CloseableReference:主类,管理资源的引用计数。
- SharedReference:内部类,维护实际的引用计数。
- ResourceReleaser:接口,定义资源的释放逻辑。
- CloseableReference 设计原理
CloseableReference 的设计基于 引用计数(Reference Counting),类似于 C++ 的 std::shared_ptr。其核心思想是:
- 一个资源(如 Bitmap)被多个 CloseableReference 实例共享。
- 每个 CloseableReference 持有一个对 SharedReference 的引用,SharedReference 维护资源的实际引用计数。
- 当引用计数降为 0 时,调用 ResourceReleaser 释放资源。
关键组件:
-
CloseableReference:
- 包装资源对象(如 Bitmap 或 PooledByteBuffer)。
- 提供 cloneOrNull 和 close 方法管理引用。
-
SharedReference:
- 存储资源实例和引用计数。
- 使用 synchronized 确保线程安全。
-
ResourceReleaser:
- 定义资源的释放逻辑(如回收 Bitmap 或归还 PooledByteBuffer 到内存池)。
工作原理:
- 创建 CloseableReference 时,引用计数 +1。
- 克隆 CloseableReference(cloneOrNull)时,引用计数 +1。
- 调用 close 或资源不再使用时,引用计数 -1。
- 引用计数为 0 时,调用 ResourceReleaser.release 释放资源。
- 源码分析
以下是通过源码详细解析 CloseableReference 的实现。
CloseableReference 类结构
java
public class CloseableReference<T> implements Cloneable, Closeable {
private boolean mIsClosed = false;
private final SharedReference<T> mSharedReference;
private CloseableReference(SharedReference<T> sharedReference) {
this.mSharedReference = Preconditions.checkNotNull(sharedReference);
sharedReference.addReference();
}
public static <T> CloseableReference<T> of(T t, ResourceReleaser<T> resourceReleaser) {
if (t == null) {
return null;
}
return new CloseableReference<>(new SharedReference<>(t, resourceReleaser));
}
@Override
public synchronized T get() {
Preconditions.checkState(!mIsClosed);
return mSharedReference.get();
}
@Override
public synchronized void close() {
if (!mIsClosed) {
mIsClosed = true;
mSharedReference.deleteReference();
}
}
public synchronized CloseableReference<T> cloneOrNull() {
if (isValid()) {
return new CloseableReference<>(mSharedReference);
}
return null;
}
public boolean isValid() {
return !mIsClosed;
}
}
- 构造方法:CloseableReference 持有一个 SharedReference,创建时增加引用计数。
- of 方法:静态工厂方法,创建 CloseableReference,传入资源和 ResourceReleaser。
- get 方法:获取包装的资源,检查是否已关闭。
- close 方法:减少引用计数,可能触发资源释放。
- cloneOrNull 方法:克隆引用,增加引用计数。
SharedReference 类
SharedReference 是引用计数的实际管理类,确保线程安全:
java
public class SharedReference<T> {
private T mValue;
private int mRefCount;
private final ResourceReleaser<T> mResourceReleaser;
public SharedReference(T value, ResourceReleaser<T> resourceReleaser) {
mValue = Preconditions.checkNotNull(value);
mResourceReleaser = Preconditions.checkNotNull(resourceReleaser);
mRefCount = 1;
}
public synchronized T get() {
return mValue;
}
public synchronized void addReference() {
if (mRefCount <= 0) {
throw new IllegalStateException("Cannot add reference to finalized SharedReference");
}
mRefCount++;
}
public synchronized void deleteReference() {
if (--mRefCount <= 0) {
T value = mValue;
mValue = null;
mResourceReleaser.release(value);
}
}
}
- 引用计数:mRefCount 记录引用数,addReference 增加,deleteReference 减少。
- 资源释放:当 mRefCount 降为 0,调用 mResourceReleaser.release 释放资源。
- 线程安全:使用 synchronized 保护 mRefCount 和 mValue。
ResourceReleaser 接口
ResourceReleaser 定义资源的释放逻辑:
java
public interface ResourceReleaser<T> {
void release(T value);
}
-
示例实现(释放 Bitmap):
java
ResourceReleaser<Bitmap> bitmapReleaser = bitmap -> { if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } };
-
示例实现(释放 PooledByteBuffer):
java
ResourceReleaser<PooledByteBuffer> bufferReleaser = buffer -> { if (buffer != null) { buffer.close(); // 归还到内存池 } };
- CloseableReference 在 Fresco 中的应用
CloseableReference 在 Fresco 的缓存、图片解码和渲染中广泛使用,以下是其在关键场景的内存管理流程。
4.1 缓存管理(CountingMemoryCache)
-
场景:CountingMemoryCache 存储解码后的 CloseableImage。
-
流程:
-
存储时,CloseableReference 包装 CloseableImage,存入缓存:
java
CloseableReference<CloseableImage> cache(K key, CloseableReference<CloseableImage> value) { mCachedEntries.put(key, value); return CloseableReference.cloneOrNull(value); }
-
查询时,返回克隆的 CloseableReference:
java
CloseableReference<CloseableImage> get(K key) { ValueDescriptor<CloseableImage> value = mCachedEntries.get(key); return value != null ? CloseableReference.cloneOrNull(value.get()) : null; }
-
缓存淘汰时,调用 close 释放引用:
java
protected void onEviction(K key, CloseableReference<CloseableImage> value) { CloseableReference.closeSafely(value); }
-
-
内存管理:
- 每个缓存条目持有一个 CloseableReference,克隆时增加引用计数。
- 缓存淘汰或条目移除时,close 减少引用计数,可能触发资源释放。
4.2 图片解码(ImagePipeline)
-
场景:ImagePipeline 从编码缓存或磁盘缓存解码图片。
-
流程:
-
解码后,创建 CloseableReference 包装 CloseableImage:
java
CloseableReference<CloseableImage> decodeFromEncodedImage(CloseableReference<PooledByteBuffer> encoded) { CloseableImage image = mImageDecoder.decode(encoded, decodeOptions); return CloseableReference.of(image, imageReleaser); }
-
传递给 DataSource,最终到达 DraweeController:
java
DataSource<CloseableReference<CloseableImage>> dataSource = fetchDecodedImage(request); dataSource.subscribe(new BaseDataSubscriber<CloseableReference<CloseableImage>>() { @Override public void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) { CloseableReference<CloseableImage> imageRef = dataSource.getResult(); try { // 使用图片 } finally { CloseableReference.closeSafely(imageRef); } } });
-
-
内存管理:
- 解码后的 CloseableImage 通过 CloseableReference 传递,调用方负责 close。
- 如果解码失败,CloseableReference 确保资源不泄漏。
4.3 图片渲染(DraweeController)
-
场景:DraweeController 将图片渲染到 DraweeView。
-
流程:
-
DraweeController 接收 CloseableReference,传递给 DraweeHierarchy:
java
public void setImage(CloseableReference<CloseableImage> imageRef, float progress) { if (CloseableReference.isValid(imageRef)) { mSettableDraweeHierarchy.setImage(imageRef.get(), progress, true); } }
-
渲染后,CloseableReference 被释放:
java
CloseableReference.closeSafely(imageRef);
-
-
内存管理:
- 渲染期间,CloseableReference 确保 Bitmap 不被意外回收。
- 渲染完成后,close 释放引用,触发垃圾回收或资源池归还。
- CloseableReference 的内存管理流程
以下是一个典型的 CloseableReference 生命周期,结合 Fresco 的图片加载流程:
-
资源创建:
-
解码图片生成 CloseableImage,用 CloseableReference.of 包装:
java
CloseableReference<CloseableImage> imageRef = CloseableReference.of(image, imageReleaser);
-
引用计数初始化为 1。
-
-
资源传递:
-
ImagePipeline 返回 imageRef 给 DraweeController,可能多次克隆:
java
CloseableReference<CloseableImage> clonedRef = imageRef.cloneOrNull();
-
每次克隆,引用计数 +1。
-
-
资源使用:
- DraweeController 使用 imageRef.get() 访问图片,渲染到 UI。
- CloseableReference 确保资源在多线程环境下不被提前释放。
-
资源释放:
-
使用完成后,调用 close:
java
CloseableReference.closeSafely(imageRef);
-
引用计数 -1,若降为 0,调用 ResourceReleaser.release:
- 释放 Bitmap:调用 bitmap.recycle()。
- 释放 PooledByteBuffer:归还到内存池。
-
-
异常处理:
-
如果调用方忘记 close,Fresco 使用 CloseableReference.closeSafely 提供安全释放:
java
public static void closeSafely(CloseableReference<?> ref) { if (ref != null) { ref.close(); } }
-
SharedReference 的线程安全设计防止并发问题。
-
- 优点与挑战
优点
-
内存安全:
- 引用计数确保资源仅在不再需要时释放,防止泄漏。
- CloseableReference.closeSafely 提供容错机制。
-
线程安全:
- SharedReference 使用 synchronized 保护引用计数,适合多线程环境。
-
统一管理:
- 适用于 Bitmap、PooledByteBuffer 等多种资源,简化内存管理。
-
内存池集成:
- 与 PooledByteBuffer 的内存池配合,减少分配和 GC 开销。
-
异常鲁棒性:
- 即使调用方使用不当(如忘记 close),CloseableReference 提供安全释放路径。
挑战
-
使用复杂性:
-
开发者需显式调用 close 或使用 try-finally,否则可能导致泄漏。
-
源码示例:
java
CloseableReference<CloseableImage> ref = getImageRef(); try { // 使用 ref } finally { CloseableReference.closeSafely(ref); }
-
-
性能开销:
- 引用计数和 synchronized 引入轻微性能开销,尤其在高并发场景。
-
调试难度:
- 如果引用未正确释放,排查内存泄漏需要深入分析引用链。
-
学习曲线:
- 相比 Glide 的弱引用机制,CloseableReference 的引用计数模型对开发者要求更高。
- 与 Glide 的内存管理对比
为了更全面理解 CloseableReference,以下将其与 Glide 的内存管理机制对比:
Glide 内存管理
-
机制:
- 使用 LruResourceCache(LRU 缓存)和 ActiveResources(弱引用)。
- 活跃资源通过 WeakReference 管理,依赖 JVM GC 回收。
-
源码示例(ActiveResources):
java
class ActiveResources { private final Map<Key, ResourceWeakReference> activeResources = new HashMap<>(); void activate(Key key, Resource<?> resource) { activeResources.put(key, new ResourceWeakReference(resource)); } Resource<?> deactivate(Key key) { ResourceWeakReference ref = activeResources.remove(key); return ref != null ? ref.get() : null; } }
-
特点:
- 简单轻量,依赖 GC 自动回收。
- 弱引用适合中小型应用,但高并发下可能触发频繁 GC。
对比
特性 | CloseableReference (Fresco) | WeakReference (Glide) |
---|---|---|
管理方式 | 引用计数,显式释放 | 弱引用,依赖 GC |
内存安全 | 严格,需手动close | 依赖 GC,可能延迟回收 |
线程安全 | synchronized保证安全 | 需额外同步机制 |
性能开销 | 轻微(引用计数和锁) | 低(但 GC 可能影响性能) |
适用场景 | 大型应用、高并发、复杂资源管理 | 中小型应用、简单加载 |
复杂性 | 较高(需显式管理) | 较低(自动回收) |
- Fresco:CloseableReference 提供精确控制,适合内存敏感的大型应用,但需要开发者正确管理引用。
- Glide:弱引用机制简单易用,但依赖 GC,可能在高负载下导致性能波动。
- 优化建议
基于 CloseableReference 的内存管理,以下是优化建议:
-
始终使用 try-finally:
-
确保 CloseableReference 在使用后释放:
java
CloseableReference<CloseableImage> ref = pipeline.fetchDecodedImage(request).getResult(); try { // 使用 ref } finally { CloseableReference.closeSafely(ref); }
-
-
监控引用泄漏:
-
使用 LeakCanary 或 Android Profiler 检测未释放的 CloseableReference。
-
Fresco 提供 CloseableReferenceLeakTracker 调试引用泄漏:
java
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context) .setCloseableReferenceLeakTracker(new DefaultCloseableReferenceLeakTracker()) .build();
-
-
优化内存池:
-
配置 PooledByteBuffer 的内存池大小,减少分配开销:
java
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context) .setEncodedMemoryCacheParamsSupplier(() -> new MemoryCacheParams( maxCacheSize, maxCacheEntries, maxEvictionSize, maxEvictionEntries, maxCacheEntrySize)) .build();
-
-
异步释放:
-
在高并发场景下,将 close 操作放入后台线程,避免阻塞主线程:
java
executor.execute(() -> CloseableReference.closeSafely(ref));
-
总结
CloseableReference 是 Fresco 内存管理的核心,通过引用计数和 SharedReference 实现资源的安全分配和释放。其设计结合了线程安全、内存池集成和异常鲁棒性,特别适合大型应用的复杂图片加载场景。与 Glide 的弱引用机制相比,CloseableReference 提供更严格的控制,但需要开发者显式管理引用。