深入分析 Fresco 在 Android 5.0+中使用 Ashmem(匿名共享内存)存储 Bitmap 的机制

197 阅读7分钟

Ashmem 是 Android 提供的一种高效内存管理机制,Fresco 利用它优化 Bitmap 的存储,减少 Java 堆内存占用,提升性能和稳定性。


  1. Ashmem 概述

Ashmem 是 Android 内核提供的一种匿名共享内存机制,允许进程在内核分配的内存区域中存储数据,并通过文件描述符(File Descriptor)共享。相比 Java 堆内存,Ashmem 有以下优势:

  • 低 GC 压力:数据存储在原生内存,减少 Java 堆分配和垃圾回收开销。
  • 高效共享:支持跨进程共享,适合多进程场景(如 Android 的多进程应用)。
  • 灵活管理:通过 MemoryFile 或直接操作文件描述符管理内存。

在 Android 5.0+,Bitmap 支持存储像素数据到 Ashmem(通过 Bitmap.Config.HARDWARE 或原生分配),Fresco 利用这一特性优化 Bitmap 存储,尤其在高分辨率图片加载和缓存场景下。

Fresco 中的 Ashmem 使用:

  • Fresco 通过 Bitmap 的原生内存分配(结合 AshmemBitmap 或 CloseableBitmap)将像素数据存储在 Ashmem。
  • 结合 CloseableReference 和内存池(如 BitmapPool),实现高效的内存管理和复用。
  • 主要用于 CountingMemoryCache(Bitmap 缓存)和图片解码流程。

源码位置:

  • 核心类:

    • com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory:管理 Bitmap 创建。
    • com.facebook.imagepipeline.memory.AshmemMemoryChunkPool:Ashmem 内存池。
    • com.facebook.imagepipeline.image.CloseableBitmap:包装 Ashmem 存储的 Bitmap。
  • 相关工具:

    • com.facebook.common.memory.MemoryChunk:抽象内存块接口。
    • com.facebook.common.references.CloseableReference:管理 Bitmap 引用。

  1. Ashmem 在 Fresco 中的设计原理

Fresco 在 Android 5.0+ 使用 Ashmem 存储 Bitmap 的核心目标是:

  • 减少 Java 堆占用:将 Bitmap 的像素数据存储在原生内存(Ashmem),降低 GC 压力。
  • 高效内存管理:通过内存池复用 Ashmem 分配的内存块。
  • 内存安全:结合 CloseableReference,确保 Bitmap 资源正确释放。
  • 性能优化:Ashmem 的直接内存访问提升解码和渲染性能。

关键机制:

  1. PlatformBitmapFactory:

    • 抽象 Bitmap 创建逻辑,适配不同 Android 版本。
    • 在 Android 5.0+,优先使用 Ashmem 分配 Bitmap。
  2. AshmemMemoryChunk:

    • 封装 Ashmem 内存块,管理原生内存分配和释放。
    • 通过 MemoryFile 或直接 Ashmem API 操作。
  3. BitmapPool:

    • 内存池,复用 Ashmem 分配的 Bitmap 或内存块。
  4. CloseableReference:

    • 管理 Bitmap 的引用计数,确保内存安全释放。
  5. CountingMemoryCache:

    • 存储 CloseableBitmap,支持 Ashmem 存储的 Bitmap。

工作原理:

  • 分配:PlatformBitmapFactory 创建 Bitmap,像素数据存储在 Ashmem(通过 AshmemMemoryChunk)。
  • 缓存:Bitmap 包装为 CloseableBitmap,存入 CountingMemoryCache。
  • 使用:通过 CloseableReference 访问 Bitmap,渲染到 UI。
  • 释放:CloseableReference.close() 触发 Ashmem 内存回收或归还到 BitmapPool。

  1. Fresco 使用 Ashmem 的工作流程

以下是 Fresco 在 Android 5.0+ 使用 Ashmem 存储 Bitmap 的完整流程,结合源码分析:

3.1 配置 Ashmem 支持

  • Fresco 在初始化时通过 ImagePipelineConfig 配置 Bitmap 存储策略:

    java

    ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
        .setBitmapMemoryCacheParamsSupplier(() -> new MemoryCacheParams(...))
        .setPoolFactory(new PoolFactory(PoolConfig.newBuilder()
            .setBitmapPoolParams(new PoolParams(maxSize, bucketSizes))
            .build()))
        .build();
    
  • PoolFactory 创建 BitmapPool,在 Android 5.0+ 启用 Ashmem 支持:

    java

    public class PoolFactory {
        public BitmapPool getBitmapPool() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                return new LollipopBitmapPool(mPoolParams.maxSize, mPoolParams.bucketSizes);
            }
            return new GingerbreadBitmapPool(...);
        }
    }
    

3.2 Bitmap 创建

  • PlatformBitmapFactory 负责创建 Bitmap,在 Android 5.0+ 使用 Ashmem:

    java

    public class PlatformBitmapFactory {
        private final BitmapPool mBitmapPool;
    
        public CloseableReference<CloseableBitmap> createBitmap(int width, int height, Bitmap.Config config) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                return createAshmemBitmap(width, height, config);
            }
            return createHeapBitmap(width, height, config);
        }
    
        private CloseableReference<CloseableBitmap> createAshmemBitmap(int width, int height, Bitmap.Config config) {
            AshmemMemoryChunk memoryChunk = mAshmemMemoryChunkPool.get(width * height * getBytesPerPixel(config));
            Bitmap bitmap = Bitmap.createBitmap(width, height, config);
            bitmap.setHasAlpha(true); // 启用 alpha 通道
            memoryChunk.writeBitmap(bitmap); // 将像素数据存储到 Ashmem
            return CloseableReference.of(new CloseableStaticBitmap(bitmap, memoryChunk, QualityInfoImpl.full()), bitmapReleaser);
        }
    }
    
  • 关键逻辑:

    • AshmemMemoryChunkPool 分配 Ashmem 内存块。
    • Bitmap.createBitmap 创建 Bitmap,像素数据绑定到 Ashmem。
    • 包装为 CloseableStaticBitmap,通过 CloseableReference 管理。

3.3 AshmemMemoryChunk

  • AshmemMemoryChunk 封装 Ashmem 内存块:

    java

    public class AshmemMemoryChunk implements MemoryChunk {
        private final MemoryFile mMemoryFile;
        private final int mSize;
        private boolean mIsClosed;
    
        public AshmemMemoryChunk(int size) {
            mMemoryFile = new MemoryFile(null, size);
            mSize = size;
            mIsClosed = false;
        }
    
        @Override
        public ByteBuffer getByteBuffer() {
            return mMemoryFile.getByteBuffer();
        }
    
        @Override
        public void writeBitmap(Bitmap bitmap) {
            ByteBuffer buffer = getByteBuffer();
            bitmap.copyPixelsToBuffer(buffer);
        }
    
        @Override
        public void close() {
            if (!mIsClosed) {
                mIsClosed = true;
                mMemoryFile.close();
            }
        }
    }
    
  • 作用:

    • 使用 MemoryFile 创建 Ashmem 内存块。
    • writeBitmap 将 Bitmap 像素数据写入 Ashmem。
    • close 释放 Ashmem 内存。

3.4 缓存存储

  • 解码后的 Bitmap 存入 CountingMemoryCache:

    java

    CloseableReference<CloseableBitmap> cache(K key, CloseableReference<CloseableBitmap> value) {
        mCachedEntries.put(key, value);
        return CloseableReference.cloneOrNull(value);
    }
    
  • CloseableBitmap 包含 Ashmem 存储的 Bitmap,通过 CloseableReference 管理。

3.5 图片渲染

  • DraweeController 从 ImagePipeline 获取 CloseableReference:

    java

    public void onNewResultImpl(DataSource<CloseableReference<CloseableBitmap>> dataSource) {
        CloseableReference<CloseableBitmap> imageRef = dataSource.getResult();
        try {
            mSettableDraweeHierarchy.setImage(imageRef.get(), 1.0f, true);
        } finally {
            CloseableReference.closeSafely(imageRef);
        }
    }
    
  • Bitmap 的像素数据从 Ashmem 读取,渲染到 DraweeView。

3.6 资源释放

  • 使用完成后,释放 CloseableReference:

    java

    CloseableReference.closeSafely(imageRef);
    
  • 触发 AshmemMemoryChunk.close(),释放 Ashmem 内存:

    java

    bitmapReleaser.release(bitmap);
    memoryChunk.close();
    

  1. 底层原理与关键机制

Fresco 使用 Ashmem 存储 Bitmap 依赖以下核心机制:

4.1 MemoryFile 与 Ashmem

  • MemoryFile 是 Android 提供的 Ashmem 包装类:

    java

    MemoryFile memoryFile = new MemoryFile(null, size);
    
  • 底层通过 ashmem_create_region 创建匿名共享内存区域,分配的文件描述符映射到进程地址空间。

  • Bitmap 的像素数据通过 copyPixelsToBuffer 写入 Ashmem:

    java

    bitmap.copyPixelsToBuffer(memoryFile.getByteBuffer());
    

4.2 AshmemMemoryChunkPool

  • AshmemMemoryChunkPool 是内存池,复用 Ashmem 内存块:

    java

    public class AshmemMemoryChunkPool extends BasePool<AshmemMemoryChunk> {
        public AshmemMemoryChunkPool(
                MemoryTrimmableRegistry memoryTrimmableRegistry,
                PoolParams poolParams,
                PoolStatsTracker poolStatsTracker) {
            super(memoryTrimmableRegistry, poolParams, poolStatsTracker);
        }
    
        @Override
        protected AshmemMemoryChunk alloc(int bucketedSize) {
            return new AshmemMemoryChunk(bucketedSize);
        }
    
        @Override
        protected void free(AshmemMemoryChunk value) {
            value.close();
        }
    }
    
  • 分桶策略:按 Bitmap 大小分桶(如 1MB、2MB),减少碎片。

  • 复用:归还的 AshmemMemoryChunk 清理后重新分配。

4.3 CloseableReference

  • CloseableReference 管理 CloseableBitmap 和 AshmemMemoryChunk:

    java

    CloseableReference<CloseableBitmap> ref = CloseableReference.of(
        new CloseableStaticBitmap(bitmap, memoryChunk, QualityInfoImpl.full()),
        bitmapReleaser);
    
  • 引用计数确保 Bitmap 和 Ashmem 内存安全释放:

    java

    public void close() {
        mSharedReference.deleteReference(); // 减少引用计数
    }
    

4.4 BitmapPool

  • LollipopBitmapPool 复用 Bitmap 对象:

    java

    public class LollipopBitmapPool extends BasePool<Bitmap> {
        @Override
        protected Bitmap alloc(int size) {
            return Bitmap.createBitmap(1, size / getBytesPerPixel(Bitmap.Config.ARGB_8888), Bitmap.Config.ARGB_8888);
        }
    
        @Override
        protected void free(Bitmap value) {
            value.recycle();
        }
    }
    
  • Ashmem 存储的 Bitmap 可直接归还到池中,减少重新分配。

4.5 内存修剪

  • AshmemMemoryChunkPool 响应内存压力,清理空闲内存块:

    java

    public void trim(MemoryTrimType trimType) {
        synchronized (this) {
            for (Bucket<AshmemMemoryChunk> bucket : mBuckets.values()) {
                bucket.trimToNothing();
            }
        }
    }
    

  1. 优点与挑战

优点

  1. 低 GC 压力:

    • Ashmem 存储 Bitmap 像素数据,减少 Java 堆占用。
    • 内存池复用降低分配和回收开销。
  2. 高效性能:

    • 直接内存访问(Ashmem)提升解码和渲染速度。
    • 分桶策略优化内存分配。
  3. 内存安全:

    • CloseableReference 确保 Ashmem 和 Bitmap 正确释放。
  4. 跨版本兼容:

    • PlatformBitmapFactory 适配 Android 5.0+,fallback 到堆内存。
  5. 高并发支持:

    • 线程安全的内存池和引用计数适合复杂场景。

Challenges

  1. 复杂性:

    • Ashmem 和内存池增加代码和调试复杂性。
    • 需要正确管理 CloseableReference,否则可能泄漏。
  2. 内存占用:

    • Ashmem 内存块占用原生内存,低内存设备需谨慎配置。
  3. 版本限制:

    • 仅在 Android 5.0+ 有效,低版本 fallback 到堆内存。
  4. 配置要求:

    • BitmapPool 和 AshmemMemoryChunkPool 的桶大小需优化。

  1. 与 Glide 的对比

为了更全面理解 Fresco 的 Ashmem 机制,以下将其与 Glide 的 Bitmap 存储对比:

Glide 的 Bitmap 存储

  • 机制:

    • Bitmap 存储在 Java 堆或 BitmapPool(LruBitmapPool)。
    • Android 5.0+ 支持 Bitmap.Config.HARDWARE,但 Glide 默认使用堆内存。
  • 源码示例:

    java

    public class LruBitmapPool implements BitmapPool {
        public Bitmap get(int width, int height, Bitmap.Config config) {
            Bitmap bitmap = mPool.get(width * height * getBytesPerPixel(config));
            if (bitmap == null) {
                bitmap = Bitmap.createBitmap(width, height, config);
            }
            return bitmap;
        }
    }
    
  • 特点:

    • 简单易用,依赖 Java 堆或 BitmapPool。
    • 不直接使用 Ashmem,GC 压力较高。

对比

特性Fresco (Ashmem)Glide (Heap/BitmapPool)
存储位置Ashmem(原生内存)Java 堆或 BitmapPool
GC 压力低(原生内存)中(堆内存分配)
内存管理CloseableReference、内存池WeakReference、LruBitmapPool
性能高(直接内存访问)中(堆内存访问)
复杂性较高(Ashmem 和池管理)较低(简单池化)
适用场景高分辨率图片、内存敏感标准图片加载、简单场景
  • Fresco:Ashmem 优化内存和性能,适合大型应用。
  • Glide:简单但 GC 压力较高,适合中小型应用。

  1. 优化建议

基于 Fresco 的 Ashmem 机制,以下是优化建议:

  1. 调整内存池大小:

    • 配置 BitmapPool 和 AshmemMemoryChunkPool:

      java

      PoolParams params = new PoolParams(
          20 * 1024 * 1024, // 20MB
          4 * 1024 * 1024, // 4MB 最大桶
          new SparseIntArray() {{ put(1024 * 1024, 10); }});
      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setPoolFactory(new PoolFactory(PoolConfig.newBuilder()
              .setBitmapPoolParams(params)
              .build()))
          .build();
      
  2. 确保资源释放:

    • 使用 try-finally 释放 CloseableReference:

      java

      CloseableReference<CloseableBitmap> ref = dataSource.getResult();
      try {
          // 使用 ref
      } finally {
          CloseableReference.closeSafely(ref);
      }
      
  3. 监控内存使用:

    • 使用 PoolStatsTracker 跟踪 Ashmem 分配:

      java

      PoolStatsTracker tracker = new PoolStatsTracker() {
          @Override
          public void onAlloc(int size) {
              Log.d("Ashmem", "Allocated: " + size);
          }
      };
      
  4. 适配低版本:

    • 检查 Android 版本,fallback 到堆内存:

      java

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
          config.setBitmapMemoryCacheParamsSupplier(() -> new MemoryCacheParams(...));
      }
      

  1. 总结

Fresco 在 Android 5.0+ 使用 Ashmem 存储 Bitmap,通过 PlatformBitmapFactory、AshmemMemoryChunk 和 BitmapPool 将像素数据存储在原生内存,结合 CloseableReference 实现高效的内存管理和安全释放。源码分析表明,Ashmem 机制显著降低 GC 压力和堆内存占用,适合高分辨率图片和内存敏感场景。与 Glide 的堆内存存储相比,Fresco 的 Ashmem 方案性能更优,但复杂度较高。优化时需关注内存池配置、资源释放和版本兼容性。