Android SharedMemory 详细使用总结
注意:
android.os.SharedMemoryAndroid 8.0 (API26) 新增 ,属于系统提供的匿名共享内存,跨进程共享一块物理内存;区别于老的MemoryFile,也区别于 MMAP 文件、Binder、Socket。车载开发经常用来做大字节流跨进程传输(图片、二进制帧、CAN 大数据),避免 Binder 传输大数据触发 TransactionTooLarge 异常。
核心概念
- SharedMemory :一块匿名共享内存,没有磁盘文件,在内核,通过
fd文件描述符跨进程传递。 - 本进程:创建 -> 获取 fd -> map 映射到本进程虚拟地址,读写。
- 跨进程:把 fd 通过 Binder 传给另外进程,对方拿到 fd 再 map 映射,同一块物理内存,两个进程虚拟地址不一样,物理是同一块。
- 内存本身没有同步锁 !SharedMemory 只负责内存共享,多进程读写需要自己加锁(如 Futex、文件锁),否则会出现竞态脏数据。
- 不再使用必须关闭 fd、unmap,否则内存泄漏。
重要:
TransactionTooLargeExceptionBinder 最大传输限制约 1MB,超过优先用 SharedMemory。
关键 API
表格
| API | 作用 |
|---|---|
SharedMemory.create(name, size) |
创建一块匿名共享内存,name 仅调试用,内核不做区分 |
getFd() |
获取底层文件描述符 ParcelFileDescriptor,Binder 可以序列化传递 |
map(prot, offset, size) |
将共享内存映射到当前进程,返回 MemoryBlock |
MemoryBlock.getBuffer() |
获取 ByteBuffer,直接读写这块共享内存 |
close() |
关闭 SharedMemory,减少 fd 引用计数;所有进程全部 close,内核才释放物理内存 |
unmap() |
解除本进程虚拟地址映射,释放本进程虚拟内存,不影响其他进程 |
⚠️:
map()得到的 MemoryBlock,用完一定要 unmap ();SharedMemory 对象用完一定要 close ()。
完整示例代码
1)创建共享内存(进程 A:写端)
java
运行
ini
// 1. 创建 1024字节共享内存
SharedMemory sharedMemory = SharedMemory.create("my_shm_demo", 1024);
// 2. 映射:PROT_READ | PROT_WRITE 可读可写
MemoryBlock memoryBlock = sharedMemory.map(SharedMemory.PROT_READ | SharedMemory.PROT_WRITE,0,1024);
ByteBuffer buffer = memoryBlock.getBuffer();
// 写数据
String content = "hello shm cross process";
buffer.put(content.getBytes(StandardCharsets.UTF_8));
// 3. 获取fd,用于Binder传给进程B
ParcelFileDescriptor pfd = sharedMemory.getFd();
// ⚠️ 重点:map只是虚拟映射,不要马上close!close会减少fd计数,对方还没使用不能close
// 写完之后,把pfd通过AIDL传给另外进程
2)接收方(进程 B,读端,拿到 ParcelFileDescriptor)
java
运行
ini
// 拿到AIDL传过来的 ParcelFileDescriptor pfd
// 根据fd构建SharedMemory对象
SharedMemory readShm = SharedMemory.fromFileDescriptor(pfd);
// 映射到本进程虚拟地址空间
MemoryBlock readBlock = readShm.map(SharedMemory.PROT_READ,0,1024);
ByteBuffer readBuf = readBlock.getBuffer();
byte[] data = new byte[1024];
readBuf.get(data);
String res = new String(data, StandardCharsets.UTF_8).trim();
Log.d("SHM","读取:"+ res);
// 使用完毕释放资源
readBlock.unmap();
readShm.close();
pfd.close();
3)写端用完释放(进程 A)
java
运行
scss
memoryBlock.unmap(); // 解除本进程映射
sharedMemory.close(); // 关闭fd
生命周期(非常关键,车载开发踩坑点)
SharedMemory.create()→ 内核分配物理内存,fd 引用计数 = 1- Binder 传递 ParcelFileDescriptor 给 B 进程,内核 fd 引用计数 +1
- 只要任意一个进程没有 close (),这块物理内存不会被内核回收,发生内存泄漏
unmap():只解除当前进程虚拟地址映射,不影响内核物理内存、不改变 fd 引用计数。
误区:调用 unmap 不等于释放共享内存!必须全部进程调用 close () 才释放物理内存。
AIDL 传递 fd 模板
AIDL 接口,直接使用ParcelFileDescriptor,系统会自动序列化 fd 跨进程:
aidl
csharp
// IShmService.aidl
interface IShmService {
void sendShm(in ParcelFileDescriptor pfd, int shmSize);
}
常见坑总结
坑 1:缺少同步,多进程并发读写数据错乱
SharedMemory本身不带锁。
- 方案 1:共享内存头部预留 4 字节,放 futex 锁;
- 方案 2:Binder 传递信号,写完再通知对方读;
❌ 不要直接两边同时读写,读到半更新脏数据。
坑 2:map 之后直接 close SharedMemory
java
运行
ini
SharedMemory shm = SharedMemory.create("test",1024);
MemoryBlock block = shm.map(PROT_READ|PROT_WRITE,0,1024);
shm.close(); // ❌ 错误!本进程fd关闭,MemoryBlock的ByteBuffer还在用,会crash
正确:全部 unmap 之后,再 close。
坑 3:忘记 unmap,虚拟地址泄漏
map()会占用进程虚拟内存,反复 map 不 unmap,虚拟内存耗尽 OOM。
每一次
map(),必须对应一次memoryBlock.unmap()。
坑 4:API 版本限制
SharedMemory API26+;低于 26 只能用旧的MemoryFile,但 MemoryFile 在高版本 Android 被限制,车载 Android8 以下很少。
坑 5:大小越界
ByteBuffer 读写超过创建时 size,直接 crash。创建时 size 必须大于等于实际二进制数据最大长度。
坑 6:ParcelFileDescriptor 用完没有 close
跨进程传递过来的ParcelFileDescriptor,使用完成必须调用.close(),否则 fd 泄漏。
SharedMemory vs Binder vs MemoryFile
表格
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SharedMemory | 大内存,不占用 Binder 缓冲区,速度快 | 需要手动同步锁,需要管理 fd 生命周期 | 图片、CAN 大报文、二进制数据流跨进程 |
| Binder | 简单自带同步 | 单包限制约 1MB,大数据抛 TransactionTooLarge | 小参数、普通接口调用 |
| MemoryFile | 老版本共享内存 | API 不推荐,高版本权限受限,逐步废弃 | 旧兼容代码 |
车载开发最佳实践
- 业务流程:A 进程填充二进制 → 创建 SharedMemory → AIDL 传递
ParcelFileDescriptor + size→ B 进程拿到 fd 映射读取;读完 B 主动关闭 fd;A 使用完 unmap+close。 - 不要把业务数据全部塞 Binder,>500KB 优先走 SharedMemory。
- 读写同步:建议约定:写进程写完数据,再通过 Binder 调用通知读进程去读取,避免竞态,简单业务不用搞 futex 锁。
- 异常流程:捕获异常,
finally块做 unmap、close,防止 fd 泄漏。
finally 释放模板(推荐模板)
java
运行
ini
SharedMemory shm = null;
MemoryBlock block = null;
try{
shm = SharedMemory.create("demo", 2048);
block = shm.map(SharedMemory.PROT_READ|SharedMemory.PROT_WRITE,0,2048);
// 业务读写逻辑
}catch (Exception e){
e.printStackTrace();
}finally {
if(block != null){
block.unmap();
}
if(shm != null){
shm.close();
}
}
补充:Kotlin 版本简要示例
kotlin
scss
val shm = SharedMemory.create("kotlin_shm", 1024)
val block = shm.map(SharedMemory.PROT_READ or SharedMemory.PROT_WRITE,0,1024)
val byteBuffer = block.buffer
byteBuffer.put("test".toByteArray())
val pfd = shm.fd
// 传给另外进程
// 释放
block.unmap()
shm.close()