深入分析 Fresco 库的 SharedReference 设计原理

94 阅读8分钟

SharedReference 是 CloseableReference 的内部组件,负责管理资源的引用计数和释放逻辑,是 Fresco 实现内存安全和线程安全的关键。


  1. SharedReference 概述

SharedReference 是 Fresco 中用于管理共享资源的引用计数类,位于 com.facebook.common.references 包。它是 CloseableReference 的核心组成部分,负责跟踪资源的引用计数,并在计数降为 0 时触发资源释放。SharedReference 的设计灵感来源于 C++ 的 std::shared_ptr,通过引用计数实现资源的共享和自动清理,特别适合管理内存敏感资源(如 Bitmap 和 PooledByteBuffer)。

核心功能:

  • 引用计数:跟踪资源被多少个 CloseableReference 引用。
  • 线程安全:使用同步机制确保多线程环境下引用计数的正确性。
  • 资源释放:当引用计数为 0 时,调用 ResourceReleaser 释放资源。
  • 内存安全:防止资源过早释放或泄漏。

源码位置:

  • SharedReference 是 CloseableReference 的静态内部类,定义在 CloseableReference.java 中。

  • 相关类:

    • CloseableReference:包装 SharedReference,提供用户接口。
    • ResourceReleaser:定义资源释放逻辑。

  1. SharedReference 设计原理

SharedReference 的设计基于 引用计数(Reference Counting)模型,核心思想是:

  • 一个资源(如 Bitmap 或 PooledByteBuffer)由多个 CloseableReference 共享。
  • SharedReference 持有资源的实际引用和引用计数,管理资源的生命周期。
  • 当所有 CloseableReference 释放引用(引用计数降为 0)时,SharedReference 触发资源释放。

关键组件:

  1. mValue:存储资源对象(如 CloseableImage 或 PooledByteBuffer)。
  2. mRefCount:引用计数,记录当前有多少 CloseableReference _引用该资源。
  3. mResourceReleaser:ResourceReleaser 实例,定义资源的释放逻辑。
  4. 同步机制:使用 synchronized 确保引用计数和资源访问的线程安全。

工作流程:

  • 创建:SharedReference 初始化时,持有资源并设置引用计数为 1。
  • 增加引用:CloseableReference 克隆时,调用 addReference,引用计数 +1。
  • 减少引用:CloseableReference 关闭时,调用 deleteReference,引用计数 -1。
  • 释放资源:引用计数降为 0 时,调用 mResourceReleaser.release 释放资源。

  1. SharedReference 源码分析

以下通过源码详细解析 SharedReference 的实现。

源码定义

SharedReference 是 CloseableReference 的静态内部类:

java

public class CloseableReference<T> {
    static 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 int getRefCount() {
            return mRefCount;
        }

        public synchronized void addReference() {
            if (mRefCount <= 0) {
                throw new IllegalStateException("Cannot add reference to finalized SharedReference");
            }
            mRefCount++;
        }

        public synchronized void deleteReference() {
            if (mRefCount <= 0) {
                throw new IllegalStateException("Cannot delete reference from finalized SharedReference");
            }
            if (--mRefCount == 0) {
                Preconditions.checkNotNull(mValue);
                T temp = mValue;
                mValue = null;
                mResourceReleaser.release(temp);
            }
        }

        public synchronized boolean isValid() {
            return mRefCount > 0;
        }
    }
}

关键方法解析:

  1. 构造方法:

    • 初始化资源(mValue)、释放器(mResourceReleaser)和引用计数(mRefCount = 1)。
    • 使用 Preconditions.checkNotNull 确保参数非空。
  2. get:

    • 返回资源对象,同步访问确保线程安全。
  3. addReference:

    • 增加引用计数,检查 mRefCount > 0 防止操作已释放的资源。
  4. deleteReference:

    • 减少引用计数,若降为 0,释放资源并清空 mValue。
    • 使用临时变量 temp 确保释放前资源不被修改。
  5. isValid:

    • 检查资源是否有效(mRefCount > 0)。

与 CloseableReference 的交互

CloseableReference 是 SharedReference 的外层包装,提供用户友好的接口:

  • 创建:

    java

    public static <T> CloseableReference<T> of(T t, ResourceReleaser<T> resourceReleaser) {
        if (t == null) {
            return null;
        }
        return new CloseableReference<>(new SharedReference<>(t, resourceReleaser));
    }
    
    • 创建 SharedReference,并用 CloseableReference 包装。
  • 克隆:

    java

    public synchronized CloseableReference<T> cloneOrNull() {
        if (isValid()) {
            return new CloseableReference<>(mSharedReference);
        }
        return null;
    }
    
    • 新建 CloseableReference,调用 mSharedReference.addReference()。
  • 关闭:

    java

    public synchronized void close() {
        if (!mIsClosed) {
            mIsClosed = true;
            mSharedReference.deleteReference();
        }
    }
    
    • 调用 mSharedReference.deleteReference() 减少引用计数。

ResourceReleaser 示例

ResourceReleaser 定义资源的释放逻辑,例如:

  • 释放 Bitmap:

    java

    ResourceReleaser<Bitmap> bitmapReleaser = bitmap -> {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
    };
    
  • 释放 PooledByteBuffer:

    java

    ResourceReleaser<PooledByteBuffer> bufferReleaser = buffer -> {
        if (buffer != null) {
            buffer.close(); // 归还到内存池
        }
    };
    

  1. SharedReference 在 Fresco 中的工作流程

SharedReference 是 CloseableReference 的核心,贯穿 Fresco 的缓存、解码和渲染流程。以下是一个典型的工作流程:

  1. 资源创建:

    • ImagePipeline 解码图片,生成 CloseableImage:

      java

      CloseableImage image = mImageDecoder.decode(encodedImage, decodeOptions);
      CloseableReference<CloseableImage> imageRef = CloseableReference.of(image, imageReleaser);
      
    • 创建 SharedReference,初始化 mRefCount = 1。

  2. 资源传递:

    • ImagePipeline 返回 imageRef 给 DraweeController,可能多次克隆:

      java

      CloseableReference<CloseableImage> clonedRef = imageRef.cloneOrNull();
      
    • 每次克隆,SharedReference.addReference() 增加 mRefCount。

  3. 资源使用:

    • DraweeController 使用 imageRef.get() 获取图片,渲染到 DraweeView:

      java

      mSettableDraweeHierarchy.setImage(imageRef.get(), progress, true);
      
    • SharedReference.get() 提供线程安全的资源访问。

  4. 资源释放:

    • 使用完成后,调用 CloseableReference.close():

      java

      CloseableReference.closeSafely(imageRef);
      
    • SharedReference.deleteReference() 减少 mRefCount,若降为 0,调用 mResourceReleaser.release。

  5. 异常处理:

    • 如果调用方忘记 close,Fresco 使用 CloseableReference.closeSafely:

      java

      public static void closeSafely(CloseableReference<?> ref) {
          if (ref != null) {
              ref.close();
          }
      }
      

示例场景(缓存管理):

  • CountingMemoryCache 存储 CloseableReference:

    java

    CloseableReference<CloseableImage> cache(K key, CloseableReference<CloseableImage> value) {
        mCachedEntries.put(key, value);
        return CloseableReference.cloneOrNull(value);
    }
    
  • 查询时返回克隆的引用,增加 mRefCount:

    java

    CloseableReference<CloseableImage> get(K key) {
        ValueDescriptor<CloseableImage> value = mCachedEntries.get(key);
        return value != null ? CloseableReference.cloneOrNull(value.get()) : null;
    }
    
  • 缓存淘汰时释放引用:

    java

    CloseableReference.closeSafely(evictedValue);
    

  1. SharedReference 的内存管理机制

SharedReference 通过以下机制实现高效的内存管理:

  1. 引用计数:

    • mRefCount 精确跟踪资源的使用情况,仅当 mRefCount == 0 时释放资源。
    • 防止资源过早释放或泄漏。
  2. 线程安全:

    • 所有方法(get、addReference、deleteReference)使用 synchronized 保护,确保多线程环境下 mRefCount 和 mValue 的一致性。

    • 示例(多线程克隆):

      java

      CloseableReference<CloseableImage> ref = getImageRef();
      executor.execute(() -> {
          CloseableReference<CloseableImage> cloned = ref.cloneOrNull();
          try {
              // 使用 cloned
          } finally {
              CloseableReference.closeSafely(cloned);
          }
      });
      
  3. 资源释放:

    • deleteReference 在 mRefCount == 0 时调用 mResourceReleaser.release,支持不同类型资源的释放逻辑。
    • 释放后,mValue = null 防止后续访问。
  4. 内存池集成:

    • 对于 PooledByteBuffer,SharedReference 与内存池配合,释放时归还缓冲区:

      java

      ResourceReleaser<PooledByteBuffer> bufferReleaser = buffer -> buffer.close();
      
  5. 异常鲁棒性:

    • 检查 mRefCount > 0 防止操作已释放的资源。
    • Preconditions.checkNotNull 确保资源和释放器有效。

  1. SharedReference 的优点与挑战

优点

  1. 内存安全:

    • 引用计数确保资源仅在不再需要时释放,防止泄漏或过早回收。
  2. 线程安全:

    • synchronized 机制支持高并发场景,适合 Fresco 的异步图片加载。
  3. 灵活性:

    • 通过 ResourceReleaser 支持不同资源类型(Bitmap、PooledByteBuffer 等)。
  4. 与缓存集成:

    • 在 CountingMemoryCache 和 EncodedMemoryCache 中高效管理引用。
  5. 调试支持:

    • getRefCount 和 isValid 方法便于调试引用状态。

挑战

  1. 性能开销:

    • synchronized 锁在高并发场景下可能引入轻微性能开销。
    • 引用计数操作(addReference、deleteReference)增加计算成本。
  2. 使用复杂性:

    • 依赖 CloseableReference 的显式 close,开发者需正确管理引用。

    • 示例(错误用法可能导致泄漏):

      java

      CloseableReference<CloseableImage> ref = getImageRef();
      // 忘记 close,可能导致泄漏
      
  3. 调试难度:

    • 如果引用未正确释放,排查 mRefCount 异常需要分析引用链。
  4. 内存占用:

    • SharedReference 实例本身占用少量内存,大量资源可能累积开销。

  1. 与其他机制的对比

为了更全面理解 SharedReference,以下将其与 Glide 的内存管理机制对比:

Glide 的 WeakReference

  • 机制:

    • 使用 WeakReference 管理活跃资源(ActiveResources),依赖 JVM GC 回收。
    • LruResourceCache 存储非活跃资源,无显式引用计数。
  • 源码示例:

    java

    class ActiveResources {
        private final Map<Key, ResourceWeakReference> activeResources = new HashMap<>();
        
        void activate(Key key, Resource<?> resource) {
            activeResources.put(key, new ResourceWeakReference(resource));
        }
    }
    
  • 特点:

    • 简单轻量,GC 自动回收。
    • 弱引用可能导致资源过早回收,需重新加载。

对比

特性SharedReference (Fresco)WeakReference (Glide)
管理方式引用计数,显式释放弱引用,依赖 GC
线程安全synchronized保证需额外同步机制
内存安全严格,需手动close依赖 GC,可能延迟或过早回收
性能开销轻微(锁和计数)低(但 GC 可能影响性能)
适用场景高并发、复杂资源管理简单加载、内存非敏感场景
复杂性较高(需显式管理)较低(自动回收)
  • SharedReference:提供精确控制,适合大型应用的复杂内存管理,但需要显式释放。
  • WeakReference:简单易用,适合中小型应用,但依赖 GC,可能导致性能波动。

  1. SharedReference 在 Fresco 缓存中的具体应用

SharedReference 在 Fresco 的缓存机制中起到关键作用,以下是具体场景:

  1. Bitmap 缓存(CountingMemoryCache):

    • 存储 CloseableReference,每个条目共享一个 SharedReference。

    • 缓存查询返回克隆的 CloseableReference,增加 mRefCount:

      java

      CloseableReference<CloseableImage> cached = mBitmapMemoryCache.get(cacheKey);
      
    • 缓存淘汰时,调用 close 减少 mRefCount。

  2. 编码缓存(EncodedMemoryCache):

    • 存储 CloseableReference,SharedReference 管理字节缓冲区。

    • 解码时,克隆引用传递给 ImageDecoder:

      java

      CloseableReference<PooledByteBuffer> encoded = mEncodedMemoryCache.get(cacheKey);
      CloseableReference<CloseableImage> decoded = decodeEncodedImage(encoded);
      
  3. 磁盘缓存(DiskStorageCache):

    • 磁盘缓存数据加载到内存后,创建 CloseableReference,由 SharedReference 管理。

    • 示例:

      java

      BinaryResource resource = mMainDiskCache.getResource(cacheKey);
      PooledByteBuffer buffer = readResourceToBuffer(resource);
      CloseableReference<PooledByteBuffer> bufferRef = CloseableReference.of(buffer, bufferReleaser);
      

  1. 优化建议

基于 SharedReference 的实现,以下是优化建议:

  1. 确保正确关闭:

    • 使用 try-finally 确保 CloseableReference 释放:

      java

      CloseableReference<CloseableImage> ref = getImageRef();
      try {
          // 使用 ref
      } finally {
          CloseableReference.closeSafely(ref);
      }
      
  2. 监控引用泄漏:

    • 启用 CloseableReferenceLeakTracker 检测未释放的引用:

      java

      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setCloseableReferenceLeakTracker(new DefaultCloseableReferenceLeakTracker())
          .build();
      
  3. 减少同步开销:

    • 在高并发场景下,尽量减少 cloneOrNull 和 close 的调用频率,合并操作:

      java

      List<CloseableReference<CloseableImage>> refs = fetchImages();
      try {
          // 批量处理
      } finally {
          for (CloseableReference<CloseableImage> ref : refs) {
              CloseableReference.closeSafely(ref);
          }
      }
      
  4. 优化 ResourceReleaser:

    • 为 PooledByteBuffer 配置合适的内存池大小,减少分配开销:

      java

      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setPoolFactory(new PoolFactory(PoolConfig.newBuilder()
              .setPooledByteBufferPoolParams(maxSize, maxBucketSize)
              .build()))
          .build();
      

  1. 总结

SharedReference 是 Fresco 内存管理的核心组件,通过引用计数和线程安全的同步机制,管理共享资源的生命周期。其设计结合了 mRefCount、mValue 和 ResourceReleaser,确保资源在不再需要时安全释放。源码分析表明,SharedReference 在 CloseableReference 的支持下,贯穿 Fresco 的缓存、解码和渲染流程,特别适合高并发和内存敏感场景。与 Glide 的 WeakReference 相比,SharedReference 提供更严格的控制,但需要显式管理引用。