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,
        &timestamp
    );

    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,页面才能真正做到“进来开始,出去彻底停”。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐