Ashmem 是 Android 提供的一种高效内存管理机制,Fresco 利用它优化 Bitmap 的存储,减少 Java 堆内存占用,提升性能和稳定性。
- 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 引用。
- Ashmem 在 Fresco 中的设计原理
Fresco 在 Android 5.0+ 使用 Ashmem 存储 Bitmap 的核心目标是:
- 减少 Java 堆占用:将 Bitmap 的像素数据存储在原生内存(Ashmem),降低 GC 压力。
- 高效内存管理:通过内存池复用 Ashmem 分配的内存块。
- 内存安全:结合 CloseableReference,确保 Bitmap 资源正确释放。
- 性能优化:Ashmem 的直接内存访问提升解码和渲染性能。
关键机制:
-
PlatformBitmapFactory:
- 抽象 Bitmap 创建逻辑,适配不同 Android 版本。
- 在 Android 5.0+,优先使用 Ashmem 分配 Bitmap。
-
AshmemMemoryChunk:
- 封装 Ashmem 内存块,管理原生内存分配和释放。
- 通过 MemoryFile 或直接 Ashmem API 操作。
-
BitmapPool:
- 内存池,复用 Ashmem 分配的 Bitmap 或内存块。
-
CloseableReference:
- 管理 Bitmap 的引用计数,确保内存安全释放。
-
CountingMemoryCache:
- 存储 CloseableBitmap,支持 Ashmem 存储的 Bitmap。
工作原理:
- 分配:PlatformBitmapFactory 创建 Bitmap,像素数据存储在 Ashmem(通过 AshmemMemoryChunk)。
- 缓存:Bitmap 包装为 CloseableBitmap,存入 CountingMemoryCache。
- 使用:通过 CloseableReference 访问 Bitmap,渲染到 UI。
- 释放:CloseableReference.close() 触发 Ashmem 内存回收或归还到 BitmapPool。
- 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();
- 底层原理与关键机制
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(); } } }
- 优点与挑战
优点
-
低 GC 压力:
- Ashmem 存储 Bitmap 像素数据,减少 Java 堆占用。
- 内存池复用降低分配和回收开销。
-
高效性能:
- 直接内存访问(Ashmem)提升解码和渲染速度。
- 分桶策略优化内存分配。
-
内存安全:
- CloseableReference 确保 Ashmem 和 Bitmap 正确释放。
-
跨版本兼容:
- PlatformBitmapFactory 适配 Android 5.0+,fallback 到堆内存。
-
高并发支持:
- 线程安全的内存池和引用计数适合复杂场景。
Challenges
-
复杂性:
- Ashmem 和内存池增加代码和调试复杂性。
- 需要正确管理 CloseableReference,否则可能泄漏。
-
内存占用:
- Ashmem 内存块占用原生内存,低内存设备需谨慎配置。
-
版本限制:
- 仅在 Android 5.0+ 有效,低版本 fallback 到堆内存。
-
配置要求:
- BitmapPool 和 AshmemMemoryChunkPool 的桶大小需优化。
- 与 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 压力较高,适合中小型应用。
- 优化建议
基于 Fresco 的 Ashmem 机制,以下是优化建议:
-
调整内存池大小:
-
配置 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();
-
-
确保资源释放:
-
使用 try-finally 释放 CloseableReference:
java
CloseableReference<CloseableBitmap> ref = dataSource.getResult(); try { // 使用 ref } finally { CloseableReference.closeSafely(ref); }
-
-
监控内存使用:
-
使用 PoolStatsTracker 跟踪 Ashmem 分配:
java
PoolStatsTracker tracker = new PoolStatsTracker() { @Override public void onAlloc(int size) { Log.d("Ashmem", "Allocated: " + size); } };
-
-
适配低版本:
-
检查 Android 版本,fallback 到堆内存:
java
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { config.setBitmapMemoryCacheParamsSupplier(() -> new MemoryCacheParams(...)); }
-
- 总结
Fresco 在 Android 5.0+ 使用 Ashmem 存储 Bitmap,通过 PlatformBitmapFactory、AshmemMemoryChunk 和 BitmapPool 将像素数据存储在原生内存,结合 CloseableReference 实现高效的内存管理和安全释放。源码分析表明,Ashmem 机制显著降低 GC 压力和堆内存占用,适合高分辨率图片和内存敏感场景。与 Glide 的堆内存存储相比,Fresco 的 Ashmem 方案性能更优,但复杂度较高。优化时需关注内存池配置、资源释放和版本兼容性。