Fresco
对比
Fresco 采用 Ashmem 内存存储 Bitmap,更适合超大图片 Fresco 用 DraweeView 替代 ImageView,底层用 AndroidBitmapFactory 避免 OOM(将图片存储在堆外内存),适合大图 / 长列表,但接入成本高
Glide 更轻量,API 更简洁,适合大多数应用。 Glide 更轻量,接入简单,满足绝大多数场景。
Ashmem 内存存储(匿名共享内存)
Ashmem(Anonymous Shared Memory,匿名共享内存)是 Android 系统基于 Linux 共享内存机制封装的跨进程内存共享方案,核心特点是内存不归属于单个进程、可被内核回收、低拷贝 / 零拷贝,最典型的应用是 Fresco 框架用它存储 Bitmap 以规避 OOM。
Ashmem 核心特性
1. 内存归属与生命周期
- 普通内存(如 Java 堆 Bitmap):归属于进程自身,进程占用的内存会计入「PSS / 私有内存」,内存满时易触发 OOM。
- Ashmem 内存:由 Linux 内核管理,属于「共享内存」,不计入单个进程的 Java 堆 / 本地堆,进程仅持有内存的「文件描述符」,大幅降低进程自身内存占用。
- 内核可主动回收:Ashmem 支持设置「内存区域大小」和「回收优先级」,系统内存紧张时,内核可直接回收未锁定的 Ashmem 内存,进程可通过重新映射恢复(需提前备份数据)。
2. 跨进程共享能力
Ashmem 可通过文件描述符(fd)在多个进程间传递,实现内存数据共享(如 App 进程 ↔ 图片解码进程),且数据无需拷贝(零拷贝),相比「进程间序列化传输」性能提升显著。
3. 内存映射(mmap)机制
Ashmem 基于 mmap() 系统调用实现:将内核中的共享内存区域直接映射到进程的虚拟地址空间,进程读写该内存时,本质是操作内核空间的内存,避免「用户态 ↔ 内核态」的数据拷贝。
Ashmem 在 Android 中的典型应用
1. Fresco 的 Bitmap 存储(核心场景)
Fresco 是 Facebook 推出的图片加载框架,其「Drawee」组件将 Bitmap 存储在 Ashmem 中,而非 Java 堆,核心优势:
- 规避 OOM:Bitmap 内存不计入 App 进程的 Java 堆,即使加载大量超大图片,也不易触发 OOM;
- 跨进程解码:Fresco 可将图片解码任务放到独立进程(ImagePipeline 进程),解码后的 Bitmap 存储在 Ashmem 中,主进程直接映射使用,避免主进程内存占用过高;
- 内存自动回收:系统内存紧张时,内核可回收 Ashmem 中的 Bitmap 内存,Fresco 会自动重新解码恢复。
2. 系统级应用
- SurfaceFlinger 进程(负责屏幕渲染):用 Ashmem 共享图层数据;
- 跨进程通信(IPC):如 ContentProvider 传递大文件数据时,用 Ashmem 替代 Binder 传输(Binder 有 1MB 数据限制)。
Ashmem vs 普通内存(Java 堆 / 本地堆)
| 特性 | Ashmem 内存 | 普通 Java 堆内存 | 本地堆(Native)内存 |
|---|---|---|---|
| 内存归属 | 内核管理(共享) | 进程私有 | 进程私有 |
| 计入进程内存统计 | 不计入(仅占文件描述符) | 计入(PSS / 私有内存) | 计入(PSS / 私有内存) |
| OOM 风险 | 极低(内核可回收) | 高(堆内存满则 OOM) | 高(本地堆满也会 OOM) |
| 跨进程共享 | 支持(零拷贝) | 不支持 | 不支持 |
| 回收机制 | 内核主动回收(可锁定) | GC 回收(被动) | 手动释放(易内存泄漏) |
| 使用复杂度 | 高(需处理映射 / 锁定 / 解锁) | 低(直接 new Bitmap) | 中(需 JNI 操作) |
Ashmem 简单使用流程(Native 层)
Ashmem 主要通过 Native 层 API 操作(Java 层无直接封装),核心步骤:
- 创建 Ashmem 区域:
ashmem_create_region("bitmap_data", size); - 锁定内存:
ashmem_pin_region(fd, 0, size)(防止被内核回收); - 映射内存:
mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); - 写入数据(如解码后的 Bitmap 像素数据);
- 解锁内存:
ashmem_unpin_region(fd, 0, size)(允许内核回收); - 跨进程传递:通过 Binder 传递文件描述符 fd,其他进程映射使用。
Android 主流的跨进程通信方式
Ashmem(匿名共享内存)、AIDL、Messenger 是 Android 主流的跨进程通信方式,但设计目标和核心能力差异显著。Ashmem 的核心优势聚焦于大数据传输和性能效率,而 AIDL/Messenger 更适配「小数据、方法调用 / 消息传递」场景。
核心原理先理清
| 通信方式 | 底层实现 | 核心特点 |
|---|---|---|
| Ashmem | Linux 共享内存 + mmap 映射 | 内核管理共享内存区域,进程通过内存映射直接读写,无数据拷贝 |
| AIDL | Binder 驱动 | 定义接口,跨进程调用方法,数据通过 Binder 序列化 / 反序列化传输 |
| Messenger | Binder + Message 队列 | 基于 AIDL 封装,以「消息」为单位通信,单线程处理,简单易用 |
Ashmem 对比 AIDL/Messenger 的核心优势
1. 超大数据传输能力(最核心优势)
Binder 传输限制:AIDL/Messenger 基于 Binder 实现,Binder 内核缓冲区默认限制为 1MB(实际可用约 896KB),传输超过该大小的数据会直接抛出
TransactionTooLargeException。Ashmem 无大小限制:仅受系统剩余内存限制,可轻松传输几十 MB 甚至上百 MB 的数据(如高清图片、视频帧、大文件数据)。
典型场景:Fresco 跨进程解码 Bitmap(几十 MB)、相机进程向 App 进程传输预览帧、跨进程传输视频切片。
2. 零拷贝 / 低拷贝,性能碾压级优势
AIDL/Messenger 的数据拷贝:数据需经历「用户态(发送进程)→ 内核态(Binder 缓冲区)→ 用户态(接收进程)」两次拷贝,且大对象的序列化 / 反序列化会额外消耗 CPU。
Ashmem 的零拷贝:
发送进程创建 Ashmem 区域并写入数据;
仅将 Ashmem 的「文件描述符(fd)」通过 Binder 传递(fd 是小整数,无大小限制);
接收进程通过 fd 将 Ashmem 内存映射到自身虚拟地址空间,直接读写内存,
数据全程不拷贝。
性能对比:传输 10MB 数据时,Ashmem 耗时仅为 AIDL 的 1/10 左右,CPU 占用降低 80%+。
3. 内存复用与低开销
- AIDL/Messenger:每次传输数据都需重新序列化 / 分配内存,传输完成后数据占用接收进程的堆内存,易触发 GC / 内存抖动。
- Ashmem:
- 内存由内核管理,不计入单个进程的私有内存,降低进程 OOM 风险;
- 共享内存可复用(如多次传输同类型数据时,无需重复创建内存区域);
- 内核可主动回收闲置 Ashmem 内存,避免内存浪费。
4. 实时性更高
- AIDL/Messenger:消息 / 方法调用需排队等待 Binder 处理,且序列化 / 反序列化有延迟,实时性差。
- Ashmem:进程直接读写共享内存,数据更新后接收方可立即感知,无中间环节延迟,适合「实时数据同步」场景(如音视频流传输)。
Ashmem 的劣势(AIDL/Messenger 更优的场景)
Ashmem 并非 “全能”,在以下场景中不如 AIDL/Messenger:
1. 简单的方法调用 / 小数据通信
- Ashmem 仅解决 “数据存储共享”,无法直接跨进程调用方法;而 AIDL 可直接定义接口(如
void setUserName(String name)),调用方像调用本地方法一样使用,开发效率高。 - 传输小数据(如字符串、整型、简单对象)时,Ashmem 的 “创建内存区域 + 映射” 成本高于 AIDL/Messenger 的序列化传输。
2. 安全性与数据同步
- AIDL/Messenger:Binder 自带权限校验(如
checkCallingPermission),可限制非法进程调用;Messenger 天然单线程处理消息,无需担心多线程同步问题。 - Ashmem:
- 无内置权限校验,需手动通过 UID/PID 验证进程合法性;
- 多进程同时读写 Ashmem 时,需手动加锁(如 mutex / 信号量),否则会出现数据错乱,开发复杂度高。
3. 易用性
- AIDL/Messenger 是 Android 上层封装的 API,Java 层可直接使用,开发成本低;
- Ashmem 主要暴露 Native 层 API(Java 层无官方封装),需通过 JNI 调用,且需处理内存映射、锁定 / 解锁、跨进程传递 fd 等细节,上手难度大。
适用场景总结
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 传输超大数据(图片、视频帧、大文件) | Ashmem | 无大小限制,零拷贝,性能高 |
| 跨进程方法调用(如服务端提供接口给客户端) | AIDL | 直接定义接口,调用方式直观 |
| 简单消息传递(如通知、状态同步) | Messenger | 单线程安全,开发简单,无需处理多线程 |
| 实时数据同步(如音视频流、传感器数据) | Ashmem | 实时性高,无数据拷贝延迟 |
| 多进程共享可复用数据(如配置缓存) | Ashmem | 内存复用,降低整体内存占用 |
Android 中 Ashmem(匿名共享内存)的典型应用场景
系统核心组件(最核心的原生应用)
1. SurfaceFlinger 进程(屏幕渲染核心)
- 作用:SurfaceFlinger 是 Android 负责合成所有屏幕图层(Surface)的核心进程,所有 App 的 UI、状态栏、壁纸等都需通过它渲染到屏幕。
- Ashmem 应用:
- 各个 App 进程的 Surface 数据(如 Activity 界面的像素数据)会存储在 Ashmem 中,SurfaceFlinger 直接映射这些 Ashmem 区域进行图层合成,避免跨进程拷贝大量像素数据;
- 合成后的最终帧数据也会通过 Ashmem 传递给显示驱动(如 GPU),实现 “零拷贝” 渲染,大幅提升屏幕刷新效率。
- 核心优势:解决 “多进程图层数据共享 + 超大像素数据传输” 的问题,避免 Binder 1MB 限制和频繁数据拷贝。
2. 多媒体框架(MediaCodec/MediaPlayer/ 相机)
- 音视频解码 / 编码:
- MediaCodec 处理音视频数据时,会将原始码流、解码后的 YUV/RGB 帧数据存储在 Ashmem 中,跨进程(如 App 进程 ↔ 媒体服务进程)共享,避免拷贝;
- 例如播放 4K 视频时,单帧数据可达几十 MB,用 Ashmem 可避免频繁的内存拷贝和 OOM。
- 相机预览 / 拍照:
- 相机 HAL 层(硬件抽象层)采集的预览帧、拍照后的原始数据(如 YUV 格式)会存入 Ashmem,App 进程通过 Camera API 映射该内存区域获取数据,实时性和性能远高于 Binder 传输。
3. 输入法(IME)进程
- Android 输入法通常运行在独立进程(如搜狗输入法、系统输入法),输入法的候选词列表、键盘布局渲染数据等,会通过 Ashmem 与 App 进程共享:
- 避免频繁通过 Binder 传输大量 UI 渲染数据;
- 输入法界面的像素缓存也存储在 Ashmem,提升键盘弹出 / 切换的流畅度。
4. 跨进程大数据传输(ContentProvider/SyncAdapter)
- ContentProvider 是 Android 跨进程共享数据的标准组件,当传输超大数据(如相册的高清图片、大文件)时:
- 不会直接通过 Binder 传输数据本身,而是将数据写入 Ashmem,仅传递 Ashmem 的文件描述符(fd),接收方映射后读取;
- 典型场景:系统相册的
MediaStore向 App 提供超大图片时,底层依赖 Ashmem 实现高效传输。
Android 框架层的间接应用
1. BitmapRegionDecoder(超大图片局部解码)
- 虽然
BitmapRegionDecoder本身不直接使用 Ashmem,但部分定制 ROM(如华为、小米)会对其优化:- 将超大图片的原始数据存入 Ashmem,解码局部区域时直接映射内存,避免将整张图片加载到 Java 堆,降低 OOM 风险;
- 第三方图库应用(如快图浏览)也会基于此思路,结合 Ashmem 实现超大图片的流畅缩放 / 滑动。
2. 内存共享的 Cursor(大数据集查询)
- 当通过
Cursor查询超大数据集(如通讯录、日志文件)时,系统会将数据集缓存到 Ashmem:- 多个进程查询同一数据集时,无需重复读取磁盘和分配内存,直接共享 Ashmem 中的缓存;
- 避免每个进程都持有一份数据集副本,节省系统整体内存。
3. RenderScript(高性能计算)
- RenderScript 是 Android 用于并行计算的框架(如图片滤镜、图像处理),其计算过程中的临时数据会存储在 Ashmem:
- 计算进程与 App 进程共享 Ashmem 中的输入 / 输出数据,避免数据拷贝;
- 充分利用多核 CPU/GPU,同时降低内存占用。
第三方框架 / 应用的典型使用
1. Fresco(图片加载框架)
- 最经典的第三方应用:Fresco 将解码后的 Bitmap 存储在 Ashmem 而非 Java 堆,核心优势:
- Bitmap 内存不计入 App 进程的 Java 堆 / 本地堆,加载大量超大图片也不易 OOM;
- 图片解码任务可放到独立进程,解码后的 Bitmap 通过 Ashmem 共享给主进程,主进程仅持有映射引用。
2. 音视频类 App(抖音 / 快手 / 腾讯视频)
- 短视频 / 长视频 App 的音视频帧处理:
- 采集的视频帧、解码后的画面数据存储在 Ashmem,跨进程(采集进程 ↔ 编码进程 ↔ 渲染进程)共享;
- 直播场景中,推流前的视频帧预处理(如美颜、滤镜)也依赖 Ashmem 实现数据共享,提升实时性。
3. 大型游戏 / 3D 应用
- 游戏的资源加载(如纹理、模型数据):
- 将超大纹理贴图、3D 模型数据存入 Ashmem,游戏主进程与渲染进程共享,避免重复加载;
- 游戏多进程架构(如主进程 ↔ 资源加载进程)中,Ashmem 是核心的数据共享方式。
4. 多进程应用(微信 / 支付宝)
- 微信的 “小程序进程”“公众号进程” 与主进程之间:
- 共享图片、缓存数据等超大资源时,使用 Ashmem 替代 Binder,降低进程间通信开销;
- 支付宝的支付控件、安全校验进程也依赖 Ashmem 传输敏感数据(避免数据拷贝泄露风险)。
Ashmem 应用的核心共性
所有使用 Ashmem 的场景都满足以下特征:
- 大数据传输 / 共享:数据量超过 Binder 1MB 限制,或频繁传输会导致性能瓶颈;
- 低拷贝 / 零拷贝需求:避免 “用户态 ↔ 内核态” 的多次数据拷贝,提升性能;
- 跨进程 / 跨线程复用:多个进程 / 线程需要访问同一份数据,避免重复存储;
- 内存占用敏感:需降低单个进程的内存占用,规避 OOM(如超大图片、视频帧)。
面试延伸:为什么 Android 系统大量依赖 Ashmem 而非其他 IPC 方式?
- 性能:零拷贝特性远超 Binder/AIDL 的 “两次拷贝”,大数据场景下性能提升一个数量级;
- 内存效率:内核管理内存,不计入单个进程的私有内存,降低系统整体内存压力;
- 灵活性:可按需锁定 / 解锁内存,系统内存紧张时内核可主动回收,平衡性能与内存占用;
- 兼容性:基于 Linux 底层机制,跨 Android 版本稳定,无需上层适配。
问题
1. Fresco 为什么比 Glide 更适合超大图片加载?
核心原因是 Fresco 用 Ashmem 存储 Bitmap,而 Glide 将 Bitmap 存储在 Java 堆(通过 BitmapPool 复用):
- 超大图片(如 1080P/4K 图片)的 Bitmap 占用内存可达几十 MB,存储在 Java 堆易触发 OOM;
- Ashmem 不计入进程堆内存,即使加载多张超大图片,主进程内存占用仍可控;
- 但 Fresco 体积更大(≈4MB),API 更复杂,Glide 更轻量(≈1.5MB),适合绝大多数普通场景。
2. 为什么 Glide 不用 Ashmem?
- 设计目标不同:Glide 主打「轻量、易用、适配性」,Ashmem 增加了使用复杂度和框架体积;
- 场景适配:绝大多数 App 加载的是缩略图 / 中等尺寸图片,通过 BitmapPool 复用 + 自适应采样率即可避免 OOM,无需引入 Ashmem;
- 兼容性:Ashmem 虽为系统接口,但跨版本适配成本高于普通内存操作。
3. 如何结合 Ashmem 和 AIDL 使用?
核心思路:用 AIDL 传递 “控制指令 + Ashmem 的 fd”,Ashmem 存储实际大数据:
服务端创建 Ashmem 区域,写入大数据,获取 fd;
通过 AIDL 将 fd(整型)和数据长度、偏移量等元信息传递给客户端;
客户端通过 fd 映射 Ashmem 内存,读取数据;
数据传输完成后,通过 AIDL 通知服务端释放 Ashmem。
优势:兼顾 AIDL 的 “方法调用便利性” 和 Ashmem 的 “大数据传输性能”。
4. 为什么 Binder 有传输大小限制,而 Ashmem 没有?
- Binder 的核心是 “进程间方法调用”,内核缓冲区设计为小容量(1MB),目的是避免单个进程占用过多内核资源;
- Ashmem 是 “共享内存”,数据存储在内核态的共享区域,仅传递 fd(小整数),不占用 Binder 缓冲区,因此无大小限制。