查看: 8355|回复: 3

鸿蒙电子罗盘插件开发:ORIENTATION 传感器订阅与避坑

[复制链接]
发表于 2026-9-22 13:00:00 | 显示全部楼层 |阅读模式
背景
在 uniappx 鸿蒙插件项目里做一个指南针功能,目标是让用户实时看到设备朝向。最初考虑用加速度计和磁力计自己算角度,后来发现鸿蒙 SensorServiceKit 已经提供 ORIENTATION 传感器,回调直接返回方向角度,能省去大量数学计算。下面按 API 调研、接口设计、核心实现、踩坑和示例页面,把这次电子罗盘插件的开发过程整理一遍。

一、SensorServiceKit 的 ORIENTATION 传感器
官方文档确认,方向传感器订阅依赖 @kit.SensorServiceKit,异常类型来自 @kit.BasicServicesKit。核心 API 只有两个:
sensor.on(sensor.SensorId.ORIENTATION, callback, options) 用于订阅方向传感器数据;sensor.off(sensor.SensorId.ORIENTATION) 用于取消订阅。
回调参数是 OrientationResponse,其中的 alpha 表示设备绕 Z 轴旋转的角度,范围 0-360 度,0 度代表正北。这个值可以直接作为罗盘方向角使用。采样间隔通过 options.interval 设置,单位是纳秒。例如需要 200ms 采样一次,应传 200 * 1000000。
  1. import { sensor } from '@kit.SensorServiceKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';
复制代码

二、插件接口与错误码设计
interface.uts 中定义罗盘数据结果、选项、函数签名和错误码。CompassResult 包含 heading(0-360 度,0 为正北)和 errMsg。CompassOptions 支持 interval(毫秒,默认 200)以及 success、fail、complete 回调。StartCompass、StopCompass 作为对外函数签名。错误码定义了两个:9240001 表示罗盘初始化失败,9240002 表示设备不支持罗盘传感器。CompassFail 继承 IUniError,并携带 errCode。
  1. export interface CompassResult {
  2.   heading: number
  3.   errMsg: string
  4. }
  5. export interface CompassOptions {
  6.   interval?: number
  7.   success?: (res: CompassResult) => void
  8.   fail?: (res: any) => void
  9.   complete?: (res: any) => void
  10. }
  11. export type StartCompass = (options: CompassOptions) => void
  12. export type StopCompass = () => void
  13. export type CompassErrorCode = 9240001 | 9240002
  14. export interface CompassFail extends IUniError {
  15.   errCode: CompassErrorCode
  16. }
复制代码

三、核心实现
app-harmony/index.uts 中的逻辑不复杂。用一个模块级变量 isListening 做防重复处理:如果已经处于监听状态,startCompass 直接 return;订阅成功后把 isListening 置为 true。stopCompass 中判断 isListening,再调用 sensor.off 取消订阅,并把标志位重置为 false。
  1. let isListening = false;
  2. export const startCompass: StartCompass = function (options: CompassOptions) {
  3.   if (isListening) {
  4.     return;
  5.   }
  6.   try {
  7.     const interval = options.interval ?? 200;
  8.     sensor.on(sensor.SensorId.ORIENTATION, (data: sensor.OrientationResponse) => {
  9.       const heading = Math.round(data.alpha);
  10.       const res: CompassResult = {
  11.         heading: heading,
  12.         errMsg: 'startCompass:ok'
  13.       };
  14.       options.success?.(res);
  15.       options.complete?.(res);
  16.     }, { interval: interval * 1000000 });
  17.     isListening = true;
  18.   } catch (e) {
  19.     const err = e as BusinessError;
  20.     const failErr = new CompassFailImpl(9240002);
  21.     failErr.errMsg = '设备不支持罗盘传感器: ' + err.message;
  22.     options.fail?.(failErr);
  23.     options.complete?.(failErr);
  24.   }
  25. }
  26. export const stopCompass: StopCompass = function () {
  27.   if (isListening) {
  28.     sensor.off(sensor.SensorId.ORIENTATION);
  29.     isListening = false;
  30.   }
  31. }
复制代码
从实现可以看到,成功路径会调用 success 和 complete,异常路径会调用 fail 和 complete,并且异常时使用 9240002 上报设备不支持罗盘传感器。这里也说明接口里虽然定义了 9240001,但当前 catch 分支主要覆盖的是不支持传感器这一类错误。

四、三个踩坑与修复
坑1:interval 单位搞错。最初以为 options.interval 单位是毫秒,直接传 200,结果传感器数据疯狂上报,CPU 占用飙升,设备发热明显。后来查文档确认单位是纳秒,200 纳秒相当于每 0.0002 毫秒上报一次数据。正确做法是毫秒值乘以 1000000 转成纳秒。
  1. sensor.on(sensor.SensorId.ORIENTATION, callback, { interval: 200 }); // 错误
  2. sensor.on(sensor.SensorId.ORIENTATION, callback, { interval: 200 * 1000000 }); // 正确
复制代码
坑2:忘记取消订阅。页面销毁时没有调用 stopCompass,传感器一直在后台运行。切换几次页面后,设备电量掉得特别快。正确做法是在页面 onUnload 生命周期里调用 stopCompass,确保 sensor.off 被执行。
  1. export default {
  2.   methods: {
  3.     startCompass() {
  4.       startCompass({ success: (res) => { ... } })
  5.     },
  6.     stopCompass() {
  7.       stopCompass()
  8.     }
  9.   },
  10.   onUnload() {
  11.     this.stopCompass()
  12.   }
  13. }
复制代码
坑3:重复调用 startCompass。用户快速点击“开始监听”按钮时,startCompass 可能被调用多次,传感器被重复订阅,回调执行多遍,界面上的角度值跳动异常。加上 isListening 标志位后,已经在监听时直接 return,避免重复订阅。这个防重逻辑与取消订阅逻辑配套使用,才能保证生命周期内状态一致。

五、示例页面与方向映射
示例页面做了一个简单的指南针 UI:中间是圆盘,红色指针根据方向角度旋转;下方显示方向角度和方向文本,并提供开始监听、停止监听按钮和状态文本。指针旋转使用 CSS transform,角度取负值,因为罗盘指针要反向旋转才能正确指向北方。方向映射把 360 度分成 8 个区间:
  1. getDirection(heading: number): string {
  2.   if (heading >= 337.5 || heading < 22.5) return '北'
  3.   if (heading >= 22.5 && heading < 67.5) return '东北'
  4.   if (heading >= 67.5 && heading < 112.5) return '东'
  5.   if (heading >= 112.5 && heading < 157.5) return '东南'
  6.   if (heading >= 157.5 && heading < 202.5) return '南'
  7.   if (heading >= 202.5 && heading < 247.5) return '西南'
  8.   if (heading >= 247.5 && heading < 292.5) return '西'
  9.   if (heading >= 292.5 && heading < 337.5) return '西北'
  10.   return '北'
  11. }
复制代码

六、总结
电子罗盘插件的核心就是调用 sensor.on 订阅 ORIENTATION 传感器,从回调的 alpha 属性获取方向角度。开发时需要重点检查三件事:interval 单位是纳秒,毫秒转纳秒要乘 1000000;页面销毁时调用 stopCompass 取消订阅;startCompass 做好防重复调用。把这些点处理好,罗盘功能在鸿蒙侧的上报频率、功耗和回调次数都能保持可控。
回复

使用道具 举报

发表于 2026-9-22 19:00:00 | 显示全部楼层

Re: 鸿蒙电子罗盘插件开发:ORIENTATION 传感器订阅与避坑

感谢分享,这篇把 ORIENTATION 传感器的订阅、取消和错误码设计讲得挺清楚,尤其是 interval 单位是纳秒这个坑很实用,200 毫秒写成 200 纳秒确实会让回调疯狂触发,CPU 和发热问题很真实。看代码里 success 和 complete 是放在每次传感器回调里触发的,如果对外文档没写清楚,调用方可能会误以为 complete 是 start 流程结束只回调一次,这点可以再明确一下。另外 catch 里统一报 9240002,9240001 目前没实际用上,后续如果初始化异常能区分开会更完整。stopCompass 里直接调 sensor.off,如果 off 也可能抛异常,加个保护会更稳。整体很有参考价值,期待后续把 0 度和 360 度跳变、去抖或平滑处理也补上。
回复 支持 反对

使用道具 举报

发表于 2026-9-22 19:10:00 | 显示全部楼层

Re: 鸿蒙电子罗盘插件开发:ORIENTATION 传感器订阅与避坑

楼主这篇总结挺实在的。ORIENTATION 传感器能直接给 alpha,确实省掉自己用加速度计和磁力计算角度那一步,heading 取整后 0 到 360、0 为正北,用起来很直观。纳秒单位那个坑很有代表性,options.interval 传 200 和传 200 乘 1000000 差别太大了,前者几乎等于无间隔上报,耗电发热不奇怪。isListening 防重复订阅、stop 时 off 并复位标志位,也是插件里容易忽略但很关键的地方。错误码和回调设计也比较清楚,成功走 success 和 complete,异常走 fail 和 complete,调用方容易处理。 想问一下,catch 里目前统一报 9240002,如果只是初始化失败或传感器服务异常,会不会误判成设备不支持?后面有没有打算把 9240001 用起来?另外 stopCompass 在未监听时直接不动作,调用方需不需要 complete 回调?整体很有参考价值,期待后续示例页面部分。
回复 支持 反对

使用道具 举报

发表于 2026-9-22 19:20:00 | 显示全部楼层

Re: 鸿蒙电子罗盘插件开发:ORIENTATION 传感器订阅与避坑

感谢分享,ORIENTATION 直接拿 alpha 确实省事多了。interval 单位是纳秒这个坑很典型,按毫秒传会疯狂上报,发热耗电都上来了。防重复订阅用模块级 isListening 简单有效,不过如果多个页面或组件同时开罗盘,后一次 start 会被直接 return,可能收不到 success,后面可以考虑引用计数或回调列表。另外 heading 四舍五入后可能出现 360,最好再对 360 取余归一到 0。complete 如果每次传感器回调都调,调用方容易误以为是启动完成,可能只在启动成功或失败时调一次更符合直觉。错误码 9240001 目前没落到 catch 里,后续也可以把初始化失败和不支持传感器区分开。整体整理得很清楚,尤其接口和踩坑部分很实用。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-7 03:09 , Processed in 0.028790 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部