HarmonyOS 7 Sensor Service Kit + ArkUI:陀螺仪高频订阅中的环形缓冲、UI 批量刷新与退场停订【鸿蒙心迹】
GyroBatchLab 订阅陀螺仪,目标采样间隔 10 ms。第一版把每条 sample 都透传给 ArkTS 并直接更新 @State,结果页面频繁重建,退出后 Native 还在继续报数据。
最后我把链路拆成三层:Sensor 回调只写环形缓冲;ArkTS 每 200 ms 批量取一次;页面退场执行 Unsubscribe 和 Native 资源销毁。
本次固定数据:Session gyro_batch_20261001_19,Sensor GYROSCOPE,Requested Interval 10 ms,Raw Samples 240,UI Batches 12,Ring Buffer 20,Dropped For UI 228,Peak Queue 20,Active Subscribers 0,Unsubscribed true,State STOPPED。

一、10 ms 采样,不等于 10 ms 更新一次 UI
陀螺仪 X/Y/Z 表示三个轴的角速度,单位 rad/s。问题不是 100 Hz 采样本身,而是把 100 Hz 硬件回调直接变成 100 Hz UI 状态变化。页面只看趋势和最新姿态,所以先把硬件采样频率和 UI 刷新频率拆开。
二、Native 订阅只负责把数据接住
Sensor Service Kit 的 C API 提供订阅和取消订阅接口,陀螺仪还需要对应权限。订阅时创建 Id、Attribute、Subscriber,并设置 10 ms 采样间隔。
Sensor_SubscriptionId* id_ =
OH_Sensor_CreateSubscriptionId();
OH_SensorSubscriptionId_SetType(
id_,
SENSOR_TYPE_GYROSCOPE
);
Sensor_SubscriptionAttribute* attr_ =
OH_Sensor_CreateSubscriptionAttribute();
OH_SensorSubscriptionAttribute_SetSamplingInterval(
attr_,
10000000
); // 10 ms
Sensor_Subscriber* subscriber_ =
OH_Sensor_CreateSubscriber();
OH_SensorSubscriber_SetCallback(
subscriber_,
OnSensorEvent
);
Sensor_Result ret =
OH_Sensor_Subscribe(
id_,
attr_,
subscriber_
);
真实设备还要读取 SensorInfo 的最小/最大 reporting interval,不能假设所有设备都适合完全相同的频率。
如果订阅失败,页面也不能继续显示“采集中”,错误状态应该直接传回 ArkTS。
三、高频回调里只做取值和入环形缓冲
Sensor callback 里最怕做 JSON、复杂日志或跨线程 UI 通知。
我只读取三轴数据和 timestamp,然后压进容量 20 的 ring buffer。
struct GyroSample {
float x;
float y;
float z;
int64_t timestamp;
};
static RingBuffer<GyroSample, 20> g_buffer;
static std::atomic<uint32_t> g_rawSamples = 0;
void OnSensorEvent(Sensor_Event* event)
{
float* data = nullptr;
uint32_t length = 0;
int64_t timestamp = 0;
if (OH_SensorEvent_GetData(
event, &data, &length) != SENSOR_SUCCESS ||
length < 3) {
return;
}
OH_SensorEvent_GetTimestamp(
event,
×tamp
);
g_buffer.Push({
data[0],
data[1],
data[2],
timestamp
});
g_rawSamples++;
}
Ring Buffer 上限 20,满后覆盖最旧 sample。Dropped For UI=228 表示业务主动跳过中间帧,不是传感器丢包。
四、ArkTS 每 200 ms 取一次,而不是每条 sample 都跨语言回调
Native Bridge 暴露 drainLatest(),页面每 200 ms 批量取一次并更新最新 XYZ。
private batchTimer: number = -1
private startUiBatch(): void {
this.batchTimer = setInterval(() => {
const batch =
gyroNative.drainLatest(20)
if (batch.length === 0) {
return
}
const last =
batch[batch.length - 1]
this.latestX = last.x
this.latestY = last.y
this.latestZ = last.z
this.uiBatches++
this.lastBatch = '19:59:44'
}, 200)
}
底层仍然可以 100 Hz 采样,页面只需要大约 5 Hz 更新。
画波形时可一次消费整批;只显示姿态数字时,取最后一条即可。
五、为什么不用普通数组一直 append
普通 Array 上限不明确,UI 一卡就会堆数据。固定 20 的环形缓冲更适合“只关心最近一小段数据”的页面,因此 Peak Queue 不应超过 20。完整轨迹应另存文件或数据库。
六、退场时先 Unsubscribe,再销毁订阅对象
Sensor Service Kit 明确要求订阅和取消订阅成对使用。
void StopGyroSampling()
{
if (id_ != nullptr &&
subscriber_ != nullptr) {
OH_Sensor_Unsubscribe(
id_,
subscriber_
);
}
if (subscriber_ != nullptr) {
OH_Sensor_DestroySubscriber(subscriber_);
subscriber_ = nullptr;
}
if (attr_ != nullptr) {
OH_Sensor_DestroySubscriptionAttribute(attr_);
attr_ = nullptr;
}
if (id_ != nullptr) {
OH_Sensor_DestroySubscriptionId(id_);
id_ = nullptr;
}
}
顺序是先 Unsubscribe,再销毁 Subscriber / Attribute / Id。页面 aboutToDisappear() 里还要同步清掉 200 ms UI timer,避免 Native 已停但页面仍在空轮询。
七、调试页要能证明“退场以后真的停了”
工程只保留 GyroBatchPage.ets、GyroBatchState.ets 和 Native 的 GyroSampler.cpp/.h。
HiLog 固定输出:
subscribe GYROSCOPE interval=10ms
rawSamples=240 ring=20
flush uiBatch=12 droppedForUI=228
peakQueue=20
unsubscribe success active=0
State: SAMPLING -> STOPPED

八、最终验收不只是“曲线能动”
运行结束后页面显示:
- Raw Samples:
240 - UI Batches:
12 - Ring Buffer:
20 - Dropped For UI:
228 - Peak Queue:
20 - Active Subscribers:
0 - Unsubscribed:
true - State:
STOPPED
最值得看的是最后两项。
如果页面已经退场,Active Subscribers 还等于 1,前面的批量刷新做得再漂亮,也只是把性能问题藏到后台。

九、正式项目还要考虑权限、硬件和时间戳
正式项目要同时处理权限、传感器存在性和设备能力差异。采样间隔也不是严格定时器,算法应使用 Sensor_Event 自带 timestamp,而不是假设相邻 sample 必然正好差 10 ms。
十、这次真正拆开的,是硬件频率、数据缓存频率和 UI 频率
最终状态链路是:
SUBSCRIBED → SAMPLING → BATCHING → STOPPING → STOPPED
底层高频采样,Native ring buffer 持续接收,ArkUI 按业务频率刷新;再配合成对的 Subscribe / Unsubscribe,页面才能真正做到“进来开始,出去彻底停”。
更多推荐




所有评论(0)