深入浅出安卓匿名共享内存
一、匿名共享内存的本质
你可以把匿名共享内存(Ashmem)想象成一个"魔法黑板":
- 多个应用可以同时在这块黑板上读写
- 不需要知道对方是谁(匿名性)
- 数据直接保存在内存中,速度极快
- 系统会自动管理这块黑板的生命周期
二、底层实现原理
- 内核驱动:Ashmem实际上是Linux内核的一个驱动模块
- 内存映射:通过mmap系统调用将内存映射到进程地址空间
- 文件描述符传递:使用Binder机制传递文件描述符实现跨进程共享
三、详细工作流程
-
创建阶段:
- 调用
ashmem_create_region创建共享内存区域 - 返回一个文件描述符(fd)作为操作句柄
- 调用
-
映射阶段:
- 使用
mmap将共享内存映射到进程地址空间 - 获得可直接操作的内存指针
- 使用
-
共享阶段:
- 通过Binder将fd传递给其他进程
- 接收方同样使用mmap映射这块内存
四、完整代码示例
// 创建端
fun createSharedMemory(): ParcelFileDescriptor {
// 1. 创建共享内存(1KB大小)
val sharedMemory = SharedMemory.create("demo_memory", 1024)
// 2. 映射为可读写
val buffer = sharedMemory.mapReadWrite()
// 3. 写入数据
buffer.putInt(0, 123) // 在位置0写入整数123
buffer.putString(4, "Hello") // 在位置4写入字符串
// 4. 返回文件描述符
return sharedMemory.fd
}
// 接收端
fun readSharedMemory(fd: ParcelFileDescriptor) {
// 1. 从文件描述符创建SharedMemory
val sharedMemory = SharedMemory.fromFileDescriptor(fd.fileDescriptor)
// 2. 映射为只读(安全考虑)
val buffer = sharedMemory.mapReadOnly()
// 3. 读取数据
val number = buffer.getInt(0) // 读取位置0的整数
val text = buffer.getString(4) // 读取位置4的字符串
Log.d("Ashmem", "收到数据: $number, $text")
}
五、性能优化技巧
- 适当大小:不要分配过大内存,按需分配
- 合理映射:只在需要时映射,用完及时unmap
- 数据对齐:按4字节或8字节对齐提高访问效率
- 批量操作:尽量减少小数据量的频繁读写
六、高级特性
-
内存回收:
- 使用
SharedMemory.setProtect()设置内存保护 - 可标记为"可丢弃",系统在内存紧张时优先回收
- 使用
-
原子操作:
- 通过内存屏障保证多进程读写一致性
- 可使用AtomicInteger等原子类
-
类型安全:
- 推荐使用Parcelable或ProtoBuf结构化数据
- 避免直接操作原始内存
七、常见问题解答
Q: 和普通文件共享有什么区别? A: 完全内存操作,不经过磁盘IO,速度快10倍以上
Q: 最多能共享多大内存? A: 理论上是系统内存限制,但建议不超过几十MB
Q: 如何保证线程安全? A: 需要自行加锁,或使用原子变量
Q: 数据会持久化吗? A: 不会,进程退出后内存会被回收
八、实际应用案例
-
跨进程图片传输:
- 相机应用捕获照片后直接写入Ashmem
- 图片编辑应用直接从内存读取,避免文件IO
-
实时数据共享:
- 传感器数据实时共享给多个分析应用
- 游戏状态同步
-
插件化框架:
- 宿主与插件间高效通信
- 共享大型资源数据
记住:能力越大责任越大,匿名共享内存虽强大,但要注意安全性和资源管理!