Android SharedMemory 详细使用

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

运行

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

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

  1. SharedMemory.create() → 内核分配物理内存,fd 引用计数 = 1
  2. Binder 传递 ParcelFileDescriptor 给 B 进程,内核 fd 引用计数 +1
  3. 只要任意一个进程没有 close (),这块物理内存不会被内核回收,发生内存泄漏
  4. 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 不推荐,高版本权限受限,逐步废弃 旧兼容代码

车载开发最佳实践

  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

运行

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()
相关推荐
ii_best12 分钟前
ios手机开发安卓脚本开发软件[按键精灵教程]从「找最小值」到「Boss 最近刷新」再到「屏幕范围极值」
android·ios·智能手机·自动化·按键精灵
开开心心就好26 分钟前
安卓PDF处理工具,免费无广告零弹窗
android·服务器·开发语言·算法·pdf·ocr
墨狂之逸才32 分钟前
扫码枪响了,应用却没收到:一次 Android USB 线材问题排查
android
alexhilton12 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
hai_android18 小时前
把 Android App 打包成系统应用(System App)
android·kotlin·android jetpack
JMchen18 小时前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象32720 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid20 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan21 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!21 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp