结合 Fresco 源码,深入分析 CloseableReference 的内存管理机制

59 阅读8分钟

结合 Fresco 源码,深入分析其在 Fresco 缓存机制中的作用CloseableReference 是 Fresco 内存管理的核心组件,用于确保图片资源(如 Bitmap 和 PooledByteBuffer)的安全分配和释放,防止内存泄漏,尤其在高并发和复杂图片加载场景下表现出色。


  1. CloseableReference 概述

CloseableReference 是 Fresco 用于管理内存敏感资源的引用计数机制,类似于 Java 的 AutoCloseable 和智能指针。它包装了需要释放的资源(如 Bitmap 或 PooledByteBuffer),通过引用计数跟踪资源的使用情况,确保资源在不再需要时被正确释放。CloseableReference 广泛应用于 Fresco 的缓存(如 CountingMemoryCache)、图片解码和渲染流程中。

核心目标:

  • 防止内存泄漏:确保 Bitmap 和字节缓冲区在不再使用时被回收。
  • 支持并发安全:在多线程环境下正确管理资源。
  • 简化资源管理:提供统一的 API,隐藏底层释放逻辑。

源码位置:

  • CloseableReference 定义在 com.facebook.common.references 包中,核心类包括:

    • CloseableReference:主类,管理资源的引用计数。
    • SharedReference:内部类,维护实际的引用计数。
    • ResourceReleaser:接口,定义资源的释放逻辑。

  1. CloseableReference 设计原理

CloseableReference 的设计基于 引用计数(Reference Counting),类似于 C++ 的 std::shared_ptr。其核心思想是:

  • 一个资源(如 Bitmap)被多个 CloseableReference 实例共享。
  • 每个 CloseableReference 持有一个对 SharedReference 的引用,SharedReference 维护资源的实际引用计数。
  • 当引用计数降为 0 时,调用 ResourceReleaser 释放资源。

关键组件:

  1. CloseableReference:

    • 包装资源对象(如 Bitmap 或 PooledByteBuffer)。
    • 提供 cloneOrNull 和 close 方法管理引用。
  2. SharedReference:

    • 存储资源实例和引用计数。
    • 使用 synchronized 确保线程安全。
  3. ResourceReleaser:

    • 定义资源的释放逻辑(如回收 Bitmap 或归还 PooledByteBuffer 到内存池)。

工作原理:

  • 创建 CloseableReference 时,引用计数 +1。
  • 克隆 CloseableReference(cloneOrNull)时,引用计数 +1。
  • 调用 close 或资源不再使用时,引用计数 -1。
  • 引用计数为 0 时,调用 ResourceReleaser.release 释放资源。

  1. 源码分析

以下是通过源码详细解析 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(); // 归还到内存池
        }
    };
    

  1. 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 释放引用,触发垃圾回收或资源池归还。

  1. CloseableReference 的内存管理流程

以下是一个典型的 CloseableReference 生命周期,结合 Fresco 的图片加载流程:

  1. 资源创建:

    • 解码图片生成 CloseableImage,用 CloseableReference.of 包装:

      java

      CloseableReference<CloseableImage> imageRef = CloseableReference.of(image, imageReleaser);
      
    • 引用计数初始化为 1。

  2. 资源传递:

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

      java

      CloseableReference<CloseableImage> clonedRef = imageRef.cloneOrNull();
      
    • 每次克隆,引用计数 +1。

  3. 资源使用:

    • DraweeController 使用 imageRef.get() 访问图片,渲染到 UI。
    • CloseableReference 确保资源在多线程环境下不被提前释放。
  4. 资源释放:

    • 使用完成后,调用 close:

      java

      CloseableReference.closeSafely(imageRef);
      
    • 引用计数 -1,若降为 0,调用 ResourceReleaser.release:

      • 释放 Bitmap:调用 bitmap.recycle()。
      • 释放 PooledByteBuffer:归还到内存池。
  5. 异常处理:

    • 如果调用方忘记 close,Fresco 使用 CloseableReference.closeSafely 提供安全释放:

      java

      public static void closeSafely(CloseableReference<?> ref) {
          if (ref != null) {
              ref.close();
          }
      }
      
    • SharedReference 的线程安全设计防止并发问题。


  1. 优点与挑战

优点

  1. 内存安全:

    • 引用计数确保资源仅在不再需要时释放,防止泄漏。
    • CloseableReference.closeSafely 提供容错机制。
  2. 线程安全:

    • SharedReference 使用 synchronized 保护引用计数,适合多线程环境。
  3. 统一管理:

    • 适用于 Bitmap、PooledByteBuffer 等多种资源,简化内存管理。
  4. 内存池集成:

    • 与 PooledByteBuffer 的内存池配合,减少分配和 GC 开销。
  5. 异常鲁棒性:

    • 即使调用方使用不当(如忘记 close),CloseableReference 提供安全释放路径。

挑战

  1. 使用复杂性:

    • 开发者需显式调用 close 或使用 try-finally,否则可能导致泄漏。

    • 源码示例:

      java

      CloseableReference<CloseableImage> ref = getImageRef();
      try {
          // 使用 ref
      } finally {
          CloseableReference.closeSafely(ref);
      }
      
  2. 性能开销:

    • 引用计数和 synchronized 引入轻微性能开销,尤其在高并发场景。
  3. 调试难度:

    • 如果引用未正确释放,排查内存泄漏需要深入分析引用链。
  4. 学习曲线:

    • 相比 Glide 的弱引用机制,CloseableReference 的引用计数模型对开发者要求更高。

  1. 与 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,可能在高负载下导致性能波动。

  1. 优化建议

基于 CloseableReference 的内存管理,以下是优化建议:

  1. 始终使用 try-finally:

    • 确保 CloseableReference 在使用后释放:

      java

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

    • 使用 LeakCanary 或 Android Profiler 检测未释放的 CloseableReference。

    • Fresco 提供 CloseableReferenceLeakTracker 调试引用泄漏:

      java

      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setCloseableReferenceLeakTracker(new DefaultCloseableReferenceLeakTracker())
          .build();
      
  3. 优化内存池:

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

      java

      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setEncodedMemoryCacheParamsSupplier(() -> new MemoryCacheParams(
              maxCacheSize, maxCacheEntries, maxEvictionSize, maxEvictionEntries, maxCacheEntrySize))
          .build();
      
  4. 异步释放:

    • 在高并发场景下,将 close 操作放入后台线程,避免阻塞主线程:

      java

      executor.execute(() -> CloseableReference.closeSafely(ref));
      

总结

CloseableReference 是 Fresco 内存管理的核心,通过引用计数和 SharedReference 实现资源的安全分配和释放。其设计结合了线程安全、内存池集成和异常鲁棒性,特别适合大型应用的复杂图片加载场景。与 Glide 的弱引用机制相比,CloseableReference 提供更严格的控制,但需要开发者显式管理引用。