Android SharedMemory 详细使用

3 阅读5分钟

Android SharedMemory 详细使用总结

注意:android.os.SharedMemory Android 8.0 (API26) 新增,属于系统提供的匿名共享内存,跨进程共享一块物理内存;区别于老的 MemoryFile,也区别于 MMAP 文件、Binder、Socket。车载开发经常用来做大字节流跨进程传输(图片、二进制帧、CAN 大数据),避免 Binder 传输大数据触发 TransactionTooLarge 异常。

核心概念

  1. SharedMemory:一块匿名共享内存,没有磁盘文件,在内核,通过 fd 文件描述符跨进程传递。
  2. 本进程:创建 -> 获取 fd -> map 映射到本进程虚拟地址,读写。
  3. 跨进程:把 fd 通过 Binder 传给另外进程,对方拿到 fd 再 map 映射,同一块物理内存,两个进程虚拟地址不一样,物理是同一块。
  4. 内存本身没有同步锁!SharedMemory 只负责内存共享,多进程读写需要自己加锁(如 Futex、文件锁),否则会出现竞态脏数据。
  5. 不再使用必须关闭 fd、unmap,否则内存泄漏。

重要:TransactionTooLargeException Binder 最大传输限制约 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

运行

// 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

运行

// 拿到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

运行

memoryBlock.unmap(); // 解除本进程映射
sharedMemory.close(); // 关闭fd

生命周期(非常关键,车载开发踩坑点)

  1. SharedMemory.create() → 内核分配物理内存,fd 引用计数 = 1
  2. Binder 传递 ParcelFileDescriptor 给 B 进程,内核 fd 引用计数 +1
  3. 只要任意一个进程没有 close (),这块物理内存不会被内核回收,发生内存泄漏
  4. unmap():只解除当前进程虚拟地址映射,不影响内核物理内存、不改变 fd 引用计数。

误区:调用 unmap 不等于释放共享内存!必须全部进程调用 close () 才释放物理内存。

AIDL 传递 fd 模板

AIDL 接口,直接使用ParcelFileDescriptor,系统会自动序列化 fd 跨进程:

aidl

// IShmService.aidl
interface IShmService {
    void sendShm(in ParcelFileDescriptor pfd, int shmSize);
}

常见坑总结

坑 1:缺少同步,多进程并发读写数据错乱

SharedMemory本身不带锁。

  • 方案 1:共享内存头部预留 4 字节,放 futex 锁;
  • 方案 2:Binder 传递信号,写完再通知对方读;

❌ 不要直接两边同时读写,读到半更新脏数据。

坑 2:map 之后直接 close SharedMemory

java

运行

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 不推荐,高版本权限受限,逐步废弃旧兼容代码

车载开发最佳实践

  1. 业务流程:A 进程填充二进制 → 创建 SharedMemory → AIDL 传递ParcelFileDescriptor + size → B 进程拿到 fd 映射读取;读完 B 主动关闭 fd;A 使用完 unmap+close。
  2. 不要把业务数据全部塞 Binder,>500KB 优先走 SharedMemory。
  3. 读写同步:建议约定:写进程写完数据,再通过 Binder 调用通知读进程去读取,避免竞态,简单业务不用搞 futex 锁。
  4. 异常流程:捕获异常,finally块做 unmap、close,防止 fd 泄漏。

finally 释放模板(推荐模板)

java

运行

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

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()