鸿蒙新特性:@ohos.sensor 设备传感器实战 — 构建传感器数据实验室
引言
智能手机上集成了丰富的传感器:加速度计能感知你拿起手机的动作,陀螺仪能追踪你转动设备的角度,光线传感器能根据环境亮度自动调节屏幕背光,磁力计能告诉你"北"在哪个方向。这些传感器共同构成了设备感知物理世界的能力——它们是体感游戏、运动追踪、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 个交互点
- 启动/关闭加速度计:点击按钮 →
sensor.on(sensor.SensorId.ACCELEROMETER, ...)订阅 → X/Y/Z 三条色条随设备移动实时变化 → 合加速度数值更新 → 再点击关闭 →sensor.off()取消订阅。 - 启动/关闭陀螺仪:点击按钮 →
sensor.on(sensor.SensorId.GYROSCOPE, ...)订阅 → 旋转设备时三轴数值实时变化 → 条的长度和颜色反映旋转强度。 - 切换采样频率:点击 Game/UI/Normal/Slow 四个按钮之一 → 修改
sampleRate→ 重新订阅传感器 → 肉眼观察数据刷新速度的变化。 - 重置峰值:点击重置按钮 → 最大加速度/最大角速度峰值归零 → 数据点计数归零 → 可以观察新一轮运动产生的峰值变化。
核心实现
传感器订阅与取消
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 模块的核心用法:
- sensor.SensorId:传感器类型枚举,ACCELEROMETER、GYROSCOPE、AMBIENT_LIGHT 等 10 余种传感器一应俱全。
- sensor.on(id, callback, options):订阅传感器数据。
interval以纳秒为单位(1ms = 1,000,000ns),典型取值为 20ms(Game)、60ms(UI)、200ms(Normal)。 - sensor.off(id):取消订阅传感器数据。在
aboutToDisappear()中取消订阅是防止电量泄漏的关键。 - sensor.once(id, callback):获取一次数据后自动取消,适用于快照式场景。
- 各传感器的响应数据:加速度计和陀螺仪返回
{x, y, z}三元组,环境光返回{intensity},气压计返回{pressure}等。
传感器是连接数字世界和物理世界的桥梁。它们赋予应用"感知环境"的能力——你的应用不仅知道用户点击了什么,还知道用户在怎样移动、周围是亮是暗、朝向哪个方向。掌握 @ohos.sensor API,你就能将这种感知能力融入自己的应用,创造出更智能、更自然的交互体验。
更多推荐



所有评论(0)