引言

智能手机上集成了丰富的传感器:加速度计能感知你拿起手机的动作,陀螺仪能追踪你转动设备的角度,光线传感器能根据环境亮度自动调节屏幕背光,磁力计能告诉你"北"在哪个方向。这些传感器共同构成了设备感知物理世界的能力——它们是体感游戏、运动追踪、AR、导航等应用的技术基座。

HarmonyOS 通过 @ohos.sensor 模块为开发者提供了统一的传感器访问接口。这个模块封装了 10 余种常见传感器的数据订阅机制,使用方式极其简洁——调用 sensor.on() 订阅某个传感器,传入回调函数,框架在每次传感器数据更新时自动调用你的回调。不需要手动管理采样线程,不需要处理硬件寄存器,也不需要对原始电信号做滤波——所有脏活累活都被框架封装好了。

本文将通过构建一个"传感器实验室",深入讲解 @ohos.sensor 的核心 API:SensorId 传感器类型枚举、sensor.on() / sensor.off() 的订阅与取消机制、AccelerometerResponse / GyroscopeResponse 等响应数据结构、interval 采样间隔的控制,以及传感器生命周期的正确管理方式。

读完本文你将能够:

  • 使用 sensor.on(sensor.SensorId.ACCELEROMETER, callback, options) 订阅加速度计数据
  • 使用 sensor.on(sensor.SensorId.GYROSCOPE, callback, options) 订阅陀螺仪数据
  • 理解 options.interval 的纳秒单位及典型取值(Game/UI/Normal)
  • 正确使用 sensor.off(sensorId) 取消订阅,避免电量和性能浪费
  • 了解传感器数据结构的差异(加速度含重力 vs 线性加速度)
  • 掌握 8 种常用传感器的用途和数据单位
  • 在组件生命周期中正确管理传感器订阅

@ohos.sensor 模块概述

传感器的分类

@ohos.sensor 支持的所有传感器通过 sensor.SensorId 枚举来标识。常用传感器包括:

SensorId 枚举 传感器名称 数据类型 单位 典型应用
ACCELEROMETER 加速度计 {x, y, z} m/s² 摇一摇、计步、姿态检测
ACCELEROMETER_UNCALIBRATED 未校准加速度计 {x, y, z} + bias m/s² 需要原始数据的高精度场景
LINEAR_ACCELERATION 线性加速度 {x, y, z} m/s² 运动检测(不含重力)
GYROSCOPE 陀螺仪 {x, y, z} rad/s 旋转检测、AR、全景照片
AMBIENT_LIGHT 环境光传感器 {intensity} lux 自动亮度调节
MAGNETIC_FIELD 磁力计 {x, y, z} μT 指南针、地图定向
PROXIMITY 接近光传感器 {distance} cm 通话息屏
BAROMETER 气压计 {pressure} hPa 楼层定位、天气
HALL 霍尔传感器 {status} bool 翻盖皮套检测
PEDOMETER 计步器 {steps} steps 健康应用步数统计

关键区分:ACCELEROMETER(加速度计)测量的是含重力的总加速度——手机平放时 Z 轴约为 +9.8 m/s²。LINEAR_ACCELERATION(线性加速度)则排除了重力分量,反映的是纯粹的运动加速度。对于运动检测(步数、挥动),用线性加速度;对于姿态检测(倾斜角度、屏幕方向),用含重力的加速度计。

采样周期与性能

sensor.on() 的第三个参数 options.interval 控制传感器的采样频率,单位为纳秒。这是一个需要适应的地方——常规开发者习惯以毫秒(ms)为单位,但传感器 API 使用纳秒(ns)来表示采样间隔。换算关系:1ms = 1,000,000ns。

典型取值场景:

场景 采样间隔 纳秒值 适用原因
体感游戏 20ms 20,000,000 需要高频采样以保证实时响应
UI 动画 60ms 60,000,000 匹配 60fps 刷新率(16.7ms/帧),留余量
常规监测 100-200ms 100,000,000-200,000,000 省电优先,数据变化足够平滑
后台记录 1000ms 1,000,000,000 极低功耗,不需要实时响应

采样频率越高,数据越细腻,但功耗也越大。在每个传感器应用的设计阶段就应该明确采样频率——Game 模式(20ms)功耗最高但延迟最低,Normal 模式(200ms)是日常应用的推荐默认值。

核心 API 逐项解析

导入模块

import { sensor } from '@kit.SensorServiceKit';

注意:sensor 模块归属于 @kit.SensorServiceKit,不是 @kit.ArkTS@kit.BasicServicesKit。这是专门为传感器服务设立的套件。

sensor.on(id, callback, options?)

订阅传感器数据。这是使用传感器最核心的方法:

sensor.on(
  sensor.SensorId.ACCELEROMETER,
  (data: sensor.AccelerometerResponse) => {
    this.accelX = data.x;
    this.accelY = data.y;
    this.accelZ = data.z;
    // 数据实时更新,直接在回调中更新 @State 即可驱动 UI 刷新
  },
  { interval: 100000000 } // 100ms = 100,000,000ns
);

参数说明:

  • id: 传感器类型,sensor.SensorId 枚举值。
  • callback: 数据回调函数。传感器每产生一次新数据,回调被调用一次。回调的参数类型取决于传感器类型(AccelerometerResponse / GyroscopeResponse / LightResponse 等)。
  • options(可选): 采样配置对象。interval 字段指定采样间隔(纳秒),有效范围通常为 5,000,000ns(5ms)到 200,000,000ns(200ms)。如果不传 options,系统使用默认采样周期 200ms。

sensor.off(id)

取消传感器数据订阅:

sensor.off(sensor.SensorId.ACCELEROMETER);

off() 的参数只需要传感器 ID——系统会移除该传感器的所有已注册回调。如果有多个回调注册到同一个传感器,off() 会一次性取消全部。

取消订阅的重要性不亚于发起订阅。正在运行的传感器持续消耗电量——加速度计的典型功耗约为 0.2mA,陀螺仪约为 5mA。在用户离开页面后继续采集传感器数据,等于在后台偷偷烧用户的电池。正确的做法是在 aboutToDisappear() 中取消所有订阅:

aboutToDisappear(): void {
  if (this.accelOn) sensor.off(sensor.SensorId.ACCELEROMETER);
  if (this.gyroOn) sensor.off(sensor.SensorId.GYROSCOPE);
}

sensor.once(id, callback)

获取一次传感器数据后自动取消订阅。适用于只需要当前值快照的场景(如"当前环境亮度是多少?"):

sensor.once(
  sensor.SensorId.AMBIENT_LIGHT,
  (data: sensor.LightResponse) => {
    this.brightness = data.intensity;
    // 自动取消订阅,无需手动 off()
  }
);

once() 在以下场景中特别有用:

  • 应用启动时获取一次环境光来决定初始主题。
  • 拍照前获取一次气压来嵌入照片 EXIF。
  • 用户手动点击"刷新当前方向"按钮。

各传感器的响应数据结构

AccelerometerResponse / LinearAccelerometerResponse

{ x: number, y: number, z: number } // 单位: m/s²

GyroscopeResponse

{ x: number, y: number, z: number } // 单位: rad/s

LightResponse

{ intensity: number } // 单位: lux

MagneticFieldResponse

{ x: number, y: number, z: number } // 单位: μT

ProximityResponse

{ distance: number } // 单位: cm

BarometerResponse

{ pressure: number } // 单位: hPa

HallResponse

{ status: number } // 0 = 远离, 1 = 靠近

PedometerResponse

{ steps: number } // 累计步数

各传感器的返回值结构高度一致——多数返回三轴 {x, y, z}。这种设计简化了开发者的心智模型:加速度计、陀螺仪、磁力计的代码结构完全相同,只需改变 SensorId 和响应类型名。
在这里插入图片描述
在这里插入图片描述

Demo 设计:传感器实验室

本文 Demo 实现了一个"传感器实验室",同时展示加速度计和陀螺仪的实时数据:

页面结构

Column(根容器)
├── Header(深色标题栏:"传感器实验室" + @ohos.sensor 标签)
├── Scroll
│   └── Column
│       ├── 状态总览卡片
│       │   ├── 加速度计状态(运行中/已关闭)
│       │   ├── 陀螺仪状态(运行中/已关闭)
│       │   ├── 数据点计数 + 重置按钮
│       │   └── 状态消息
│       ├── 加速度计面板
│       │   ├── X/Y/Z 轴实时数据(数值 + 动态色条)
│       │   ├── 合加速度值
│       │   └── 启动/关闭按钮
│       ├── 陀螺仪面板
│       │   ├── X/Y/Z 轴实时数据(数值 + 动态色条)
│       │   └── 启动/关闭按钮
│       ├── 采样频率选择区
│       │   ├── Game(20ms) / UI(60ms) / Normal(100ms) / Slow(200ms)
│       │   └── 说明文字
│       ├── 常用传感器一览表(8 种传感器 + 用途 + 单位)
│       └── API 参考区
└── 根容器结束

4 个交互点

  1. 启动/关闭加速度计:点击按钮 → sensor.on(sensor.SensorId.ACCELEROMETER, ...) 订阅 → X/Y/Z 三条色条随设备移动实时变化 → 合加速度数值更新 → 再点击关闭 → sensor.off() 取消订阅。
  2. 启动/关闭陀螺仪:点击按钮 → sensor.on(sensor.SensorId.GYROSCOPE, ...) 订阅 → 旋转设备时三轴数值实时变化 → 条的长度和颜色反映旋转强度。
  3. 切换采样频率:点击 Game/UI/Normal/Slow 四个按钮之一 → 修改 sampleRate → 重新订阅传感器 → 肉眼观察数据刷新速度的变化。
  4. 重置峰值:点击重置按钮 → 最大加速度/最大角速度峰值归零 → 数据点计数归零 → 可以观察新一轮运动产生的峰值变化。

核心实现

传感器订阅与取消

Demo 中加速度计的订阅逻辑是传感器 API 的标准用法:

startAccel(): void {
  sensor.on(
    sensor.SensorId.ACCELEROMETER,
    (data: sensor.AccelerometerResponse) => {
      this.accelX = data.x;
      this.accelY = data.y;
      this.accelZ = data.z;
      // 计算合加速度(含重力)
      this.accelMag = Math.sqrt(
        data.x * data.x + data.y * data.y + data.z * data.z
      );
      // 追踪峰值
      if (this.accelMag > this.maxAccel) {
        this.maxAccel = this.accelMag;
      }
      this.dataPoints++;
    },
    { interval: this.sampleRate * 1000000 } // ms → ns
  );
  this.accelOn = true;
}

stopAccel(): void {
  sensor.off(sensor.SensorId.ACCELEROMETER);
  this.accelOn = false;
}

关键细节:

  • 回调运行在哪个线程:传感器回调在事件处理线程中触发,但 ArkUI 框架会自动将 @State 的变更同步到 UI 线程——开发者不需要关心线程切换。
  • 回调中更新 @State:直接在回调中给 @State 变量赋值即可触发 UI 刷新。不需要 runOnUIThread 等手动切换。
  • 峰值追踪:Demo 在回调中维护 maxAccel(最大合加速度),这是传感器数据统计分析的最基础模式——在实际应用中,这个逻辑可以扩展为计步、摇动检测、跌倒检测等。
  • 单位换算sampleRate 是毫秒(方便 UI 显示和用户理解),sensor.on() 需要的是纳秒,所以乘以 1,000,000。

动态色条的视觉设计

Demo 中有一个精心设计的"动态色条"组件——它把抽象的加速度数值转化为可视化的条状图:

@Builder
axisBar(label: string, value: number, maxVal: number, color: string) {
  Row() {
    Text(label).fontSize(12).width(16)

    Row() {
      Row()
        .height(14)
        .width(this.getBarWidth(value, Math.max(maxVal, 1)))
        .borderRadius(7)
        .backgroundColor(color)
        .animation({ duration: 100, curve: Curve.Linear })
      Blank()
    }
    .height(14).layoutWeight(1).borderRadius(7)
    .backgroundColor('#F5F5F5')

    Text(this.formatNumber(value))
      .fontSize(11).fontColor(color).width(56)
  }
}

色条的宽度通过 getBarWidth() 计算——根据当前值和历史最大值的比例映射到 0-120vp 区间。.animation({ duration: 100 }) 是关键属性——它让色条的变化平滑过渡(类似 CSS 的 transition),而不是跳跃式变化。100ms 的动画时长足够短,使变化看起来"实时",又不至于频繁跳变产生闪烁。

采样频率的动态切换

Demo 中四个采样频率按钮的处理方式值得关注:

setRate(rate: number): void {
  this.sampleRate = rate;
  // 修改采样率后,如果传感器正在运行,需要重新订阅
  // 因为 interval 参数只在 on() 调用时传入
  this.statusMsg = '采样率已设为 ' + rate.toString() + 'ms';
}

这是一个重要的 API 行为特性:sensor.on()interval 在订阅时固定。运行期间修改采样频率需要先 off()on() 重新订阅。Demo 中将频率切换与传感器开关分离——用户先手动关闭传感器,再调整频率,再开启——这样避免了"设备正在运行中修改采样频率"的复杂逻辑。

如果产品需要"运行中动态切换频率",实现方式为:

// 动态切换采样率
if (this.accelOn) {
  sensor.off(sensor.SensorId.ACCELEROMETER);
  sensor.on(sensor.SensorId.ACCELEROMETER, callback, { interval: newRate });
}

生命周期管理

传感器订阅的生命周期管理是 Demo 中最容易被忽略但最重要的部分:

aboutToDisappear(): void {
  if (this.accelOn) {
    sensor.off(sensor.SensorId.ACCELEROMETER);
  }
  if (this.gyroOn) {
    sensor.off(sensor.SensorId.GYROSCOPE);
  }
}

如果不在 aboutToDisappear 中取消订阅,即使用户离开了页面,传感器的回调仍然会持续触发——GPS 和陀螺仪是设备上功耗最高的传感器之一,这种泄漏会在用户不知情的情况下快速消耗电量。对于需要后台持续监测的场景(如计步器),应该使用 ContinuousTask(长时任务)机制来合法地在后台运行传感器。

实际应用场景

场景一:摇一摇功能

利用加速度计的合加速度变化来检测摇动动作:

sensor.on(sensor.SensorId.ACCELEROMETER, (data) => {
  let mag = Math.sqrt(data.x * data.x + data.y * data.y + data.z * data.z);
  if (mag > 15) { // 阈值:超过 15m/s² 认为是一次有效的"摇动"
    this.onShake();
  }
}, { interval: 20000000 }); // 20ms 高频采样以捕捉快速摇动

场景二:指南针

利用磁力计和加速度计计算设备方向,实现电子指南针:

sensor.on(sensor.SensorId.MAGNETIC_FIELD, (magData) => {
  // 结合加速度计数据计算方位角
  let azimuth = Math.atan2(magData.y, magData.x) * 180 / Math.PI;
  if (azimuth < 0) azimuth += 360;
  this.compassAngle = azimuth;
}, { interval: 60000000 });

场景三:自动亮度调节

根据环境光传感器自动调整应用内的主题亮度和对比度:

sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data) => {
  if (data.intensity < 10) {
    this.themeMode = 'dark';  // 暗光环境 → 深色主题
  } else if (data.intensity > 500) {
    this.themeMode = 'light'; // 强光环境 → 浅色主题 + 高对比度
  }
}, { interval: 500000000 }); // 500ms 足够了,亮度变化不快

场景四:体感游戏控制

利用陀螺仪的高频数据来控制游戏中的角色或视角:

sensor.on(sensor.SensorId.GYROSCOPE, (data) => {
  // 累计旋转角度(简单积分)
  this.pitch += data.x * 0.02;  // 20ms * rad/s ≈ Δangle
  this.yaw += data.y * 0.02;
  // 将角度映射到游戏中的视角或角色位置
  this.cameraAngle = this.yaw;
}, { interval: 20000000 }); // 20ms 高频,确保体感操作流畅

注意:从角速度(rad/s)推算角度需要积分(累加 角速度 × 时间间隔)。简单的积分会累积误差,对于需要精确角度的场景(如 AR 中的 3D 对象放置),应该使用 sensor.on(sensor.SensorId.ROTATION_VECTOR, ...) 来获取融合后的姿态数据。

注意事项与最佳实践

1. 及时取消订阅

这是最重要的建议。不在 aboutToDisappear() 中取消传感器订阅会导致:

  • 电量持续消耗:陀螺仪功耗约为 5mA,1000mAh 的电池在 200 小时的连续陀螺仪订阅后耗尽。
  • 数据流无用:用户在浏览其他页面时,传感器数据仍然在回调中更新 @State——虽然页面不可见,但数据更新会触发不必要的组件渲染。

2. 选择合适的采样率

采样率越高,延迟越低,但功耗越大。根据场景选择合适的采样率:

  • 游戏/体感:20ms(不高于 50Hz 的游戏逻辑需求)
  • UI 动画/交互:60ms(匹配 60fps,每帧获取一次数据足够)
  • 常规监测:200ms(人眼对 5Hz 以上的刷新率感知不敏感)
  • 后台记录:1000ms(省电极致)

3. 在回调中保持轻量

传感器回调在专用事件线程中触发,频率可能高达 50Hz(每 20ms 一次)。如果回调中执行了复杂计算(如大数据量的滤波算法、文件 I/O),会导致回调队列堆积,传感器数据延迟增大。

需要复杂处理时,将原始数据传给 Worker 线程处理:

sensor.on(sensor.SensorId.ACCELEROMETER, (data) => {
  this.workerInstance.postMessage({ raw: data, timestamp: Date.now() });
  // Worker 线程负责降噪、特征提取、模式匹配等
}, { interval: 20000000 });

4. 理解坐标系

传感器数据使用设备自身的坐标系:

  • X 轴:水平向右(手机横放时指向右侧)
  • Y 轴:垂直向上(手机横放时指向上方)
  • Z 轴:垂直于屏幕指向外(手机平放时指向天花板)

手机平放在桌面上时,加速度计返回 {x: ~0, y: ~0, z: ~9.8}。这意味着 Z 轴的正方向和重力方向相反——因为加速度计测量的是"施加在设备上的力",而平放时桌面给设备的支持力向上(Z+ 方向),重力向下(Z- 方向)。

5. 不同设备传感器精度不同

低端手机和旗舰手机的传感器精度可能有数量级的差异。为关键阈值(如"摇一摇"的加速度阈值)提供校准机制,或者使用相对变化而非绝对数值来判断事件。

总结

本文通过构建一个"传感器实验室",深入讲解了 HarmonyOS @ohos.sensor 模块的核心用法:

  1. sensor.SensorId:传感器类型枚举,ACCELEROMETER、GYROSCOPE、AMBIENT_LIGHT 等 10 余种传感器一应俱全。
  2. sensor.on(id, callback, options):订阅传感器数据。interval 以纳秒为单位(1ms = 1,000,000ns),典型取值为 20ms(Game)、60ms(UI)、200ms(Normal)。
  3. sensor.off(id):取消订阅传感器数据。在 aboutToDisappear() 中取消订阅是防止电量泄漏的关键。
  4. sensor.once(id, callback):获取一次数据后自动取消,适用于快照式场景。
  5. 各传感器的响应数据:加速度计和陀螺仪返回 {x, y, z} 三元组,环境光返回 {intensity},气压计返回 {pressure} 等。

传感器是连接数字世界和物理世界的桥梁。它们赋予应用"感知环境"的能力——你的应用不仅知道用户点击了什么,还知道用户在怎样移动、周围是亮是暗、朝向哪个方向。掌握 @ohos.sensor API,你就能将这种感知能力融入自己的应用,创造出更智能、更自然的交互体验。


Logo

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

更多推荐