做设备协同或者多进程传感器采集时,数据频率一旦上去,普通 IPC 就会暴露短板。每一帧传感器读数通过 rpc.MessageSequence 从服务进程传到消费进程,都要做一次序列化和反序列化。100Hz 意味着每秒一百次拷贝,数据量再大一点,消费进程的主线程光处理 IPC 回调就占满了。

鸿蒙的 IPC 设计里有一个关键机制被很多人忽略了:Ashmem(匿名共享内存)。它允许两个进程映射同一块物理内存,写入方直接写,读取方直接读,数据本身不经过 IPC 通道。IPC 只负责“传递共享内存的句柄”和“同步信号”,不负责“搬运数据”。

服务端创建共享内存,只传句柄不传数据

服务进程在 RemoteObject 的请求处理逻辑里创建 Ashmem,把传感器帧写进去,然后把共享内存对象通过 reply.writeAshmem() 传给消费进程。后续高频更新只写这块内存,不再产生 IPC 开销。

typescript

import { rpc } from '@kit.IPCKit'

const ASHMEM_NAME = 'sensor_frame_buf'
const BUF_SIZE = 4096

function buildAshmemReply(reply: rpc.MessageSequence): void {
  const ashmem = rpc.Ashmem.create(ASHMEM_NAME, BUF_SIZE)
  ashmem.mapReadWriteAshmem(BUF_SIZE, 0)

  const frame = new ArrayBuffer(64)
  const view = new DataView(frame)
  view.setFloat32(0, 1.23)  // 陀螺仪 x
  view.setFloat32(4, 4.56)  // 陀螺仪 y
  ashmem.writeDataToAshmem(frame, frame.byteLength, 0)

  reply.writeAshmem(ashmem)
}

rpc.Ashmem.create 创建共享内存区域,mapReadWriteAshmem 建立可读写映射,writeDataToAshmem 把数据写入。reply.writeAshmem(ashmem) 传递的是共享内存的句柄,不是 frame 里的字节。执行到这里,消费进程拿到的是一把“钥匙”,不是一份“拷贝”。

消费端映射后直接读,用完必须解除映射

消费进程从 reply 里 readAshmem 取出句柄,mapReadWriteAshmem 之后 readDataFromAshmem 直接读取同一块物理内存。

typescript

function consumeAshmemReply(reply: rpc.MessageSequence): void {
  const ashmem: rpc.Ashmem = reply.readAshmem()
  ashmem.mapReadWriteAshmem(ashmem.getAshmemSize(), 0)

  const bytes = ashmem.readDataFromAshmem(64, 0)
  const view = new DataView(bytes)
  const gx = view.getFloat32(0)
  const gy = view.getFloat32(4)

  ashmem.unmapAshmem()
  ashmem.closeAshmem()
}

unmapAshmem 和 closeAshmem 必须成对调用。只 map 不 unmap,映射关系一直挂着,进程句柄表持续增长;close 漏掉更直接,共享内存的引用计数不归零,内核不会回收那块物理页。高频场景下泄漏速度比普通对象快得多。

共享内存不是“写了就能读”,同步得自己搭

Ashmem 提供的是内存共享,不提供并发同步。服务进程写入一半时消费进程来读,读到的是半帧数据。readDataFromAshmem 本身不会等“写完成”。

常见做法是配合信号量或者原子标志位:服务端写之前把标志置为“写入中”,写完置为“可读”;消费端读之前检查标志,读到“写入中”就退避重试。更严谨的方案是环形缓冲区加读写指针,指针的推进用原子操作保证顺序。

typescript

// 服务端写之前
Atomics.store(flagArray, 0, 1) // 写入中
ashmem.writeDataToAshmem(frame, frame.byteLength, 0)
Atomics.store(flagArray, 0, 0) // 可读

// 消费端读之前
if (Atomics.load(flagArray, 0) === 0) {
  const bytes = ashmem.readDataFromAshmem(64, 0)
}

Atomics 操作的对象要和共享内存的生命周期对齐。flagArray 如果是普通 Int32Array,只在单个进程内可见,跨进程同步需要把它也放进共享内存或者用独立的信号量。

共享内存的收益在 100Hz 以上、多消费进程同时读取 的场景下最明显。如果只是每秒几次的设备状态更新,普通 IPC 完全够用。选 Ashmem 的前提是数据量足够大、频率足够高,值得为同步和生命周期管理付出额外的代码复杂度。为了“看起来高级”而上共享内存,调试成本和泄漏风险会盖过性能收益。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐