《HarmonyOS技术精讲》二:用户动作与状态感知实战
这两个 API 的定位很多人一开始就搞反了HarmonyOS NEXT 的多模态融合感知服务Multimodal Awareness Kit里有两组看起来很像的 APImotion用户动作和userStatus用户状态。官方文档把它们的应用场景写得比较聚焦但实际项目里很多开发者会把它们混用。一个常见的误判是想监听用户“静止”就用userStatus想监听用户“跑步”就用motion。这其实反了。motion判断的是设备本身的运动状态——手机是不是在移动、是不是静止。它不关心人是谁也不关心运动模式它只输出“动”或“不动”。而userStatus判断的是用户的活动类型——跑步、骑行、开车。它依赖的传感器更多结果也更抽象需要常驻或低频轮询。如果项目只需要知道“用户有没有在走动”用motion就够了功耗低、延迟少。但如果你想知道“用户是不是在跑步”就必须走userStatus。这两个 API 的分工和用法完全不同但可以组合起来用。它俩各自解决了什么问题Motion设备级运动检测能力通过motionDetect事件订阅设备动作状态参数MotionType包括MOTION_TYPE_STILL静止、MOTION_TYPE_MOVEMENT移动。适合场景屏幕休眠后设备晃动唤醒、防误触、运动检测辅助定位。不适合识别用户具体在做什么如跑步、骑行。UserStatus用户活动状态识别能力通过statusChange事件订阅用户状态变化参数UserStatus包括RUNNING、CYCLING、DRIVING等。适合场景运动记录、驾驶模式切换、健康类应用。不适合高频、低延迟的动作检测比如抬手亮屏。对比项MotionUserStatus输入传感器加速度计为主加速度计 陀螺仪 GPS状态粒度动静二态多种活动分类功耗较低较高常驻轮询回调频率较高较低依赖权限无ohos.permission.ACTIVITY_MOTION环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机建议真机测试模拟器不支持部分传感器核心实现两套感知模块的订阅与取消1. Motion设备动作感知这部分代码用于订阅设备是否处于移动状态。重点在于MotionType的取值和on/off的生命周期管理。import{motion,MotionType}fromkit.MultimodalAwarenessKit;letcallback:Callbackboolean(isMoving:boolean){console.info(设备运动状态变更:${isMoving});// isMoving true 表示设备正在移动};// 订阅try{motion.on(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,callback);}catch(err){console.error(motion on error: JSON.stringify(err));}// 取消订阅推荐在页面 onPageHide 或生命周期销毁时调用try{motion.off(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,callback);}catch(err){console.error(motion off error: JSON.stringify(err));}注意事项MotionType还有一个MOTION_TYPE_STILL效果与MOVEMENT相反。不需要同时订阅两种isMoving false就是静止。取消订阅时callback参数一定要和订阅时传入的是同一个函数引用否则取消不生效。motion不涉及权限声明系统默认开启。2. UserStatus用户活动状态感知这部分代码用于监听用户活动状态的切换比如从“静止”变为“跑步”。import{userStatus,UserStatus}fromkit.MultimodalAwarenessKit;import{common}fromkit.AbilityKit;// 获取上下文用于动态请求权限letcontext:common.UIAbilityContextgetContext(this);// 动态申请权限仅首次启动时调用即可context.requestPermissionsFromUser([ohos.permission.ACTIVITY_MOTION]).then((){console.info(权限授权成功);}).catch((err:Error){console.error(权限授权失败: err.message);});letstatusCallback:CallbackUserStatus(newStatus:UserStatus){switch(newStatus){caseUserStatus.RUNNING:console.info(用户状态变为跑步);break;caseUserStatus.CYCLING:console.info(用户状态变为骑行);break;caseUserStatus.DRIVING:console.info(用户状态变为驾驶);break;default:console.info(用户状态变为其他);break;}};// 订阅try{userStatus.on(statusChange,statusCallback);}catch(err){console.error(userStatus on error: JSON.stringify(err));}// 取消订阅try{userStatus.off(statusChange,statusCallback);}catch(err){console.error(userStatus off error: JSON.stringify(err));}关键点务必在module.json5的requestPermissions字段添加ohos.permission.ACTIVITY_MOTION否则动态权限申请会走不通。UserStatus回调不是实时变化系统会有一个死区约5-10秒防止抖动。测试时不要指望毫秒级响应。不要在构造函数或aboutToAppear里直接传入this.statusCallback容易出现回调未绑定的情况。建议在onPageShow中注册。3. 综合场景检测到用户跑步时触发提示这个场景把两套 API 组合起来先通过motion判断设备是否在移动再通过userStatus判断具体活动类型。EntryComponentstruct MainPage{StatelastStatus:UserStatusUserStatus.STILL;aboutToDisappear():void{// 页面销毁时清理motion.off(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,this.motionCallback);userStatus.off(statusChange,this.statusCallback);}// 注意必须用箭头函数或 bind 确保 this 指向privatemotionCallback:Callbackboolean(isMoving:boolean){if(!isMoving){// 设备静止时清理 userStatus节省功耗userStatus.off(statusChange,this.statusCallback);return;}// 设备移动时订阅 userStatustry{// 先取消旧的避免重复订阅userStatus.off(statusChange,this.statusCallback);userStatus.on(statusChange,this.statusCallback);}catch(err){console.error(userStatus on failed: JSON.stringify(err));}};privatestatusCallback:CallbackUserStatus(newStatus:UserStatus){if(newStatusUserStatus.RUNNING){// 触发提示可能是震动、弹窗或语音提示console.info(检测到用户正在跑步);// 实际项目中可以在这里调用 Vibration 或 promptAction 接口}};build(){Column(){Text(用户状态监测中...).fontSize(20).align(Alignment.Center)}.width(100%).height(100%).onPageShow((){// 页面显示时订阅 motiontry{motion.on(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,this.motionCallback);}catch(err){console.error(motion on failed: JSON.stringify(err));}})}}设计思路用motion作为“门控”只有设备移动时才去订阅高功耗的userStatus。静止时关闭节省电量。onPageShow/aboutToDisappear负责生命周期挂载避免页面切后台后回调空转。实际提示可以用vibrator或promptAction.showToast实现这里只给出了日志。踩坑记录高频问题问题 1权限申请成功但userStatus回调从未触发现象动态授权已走通module.json5也添加了权限但回调就是不走。原因HarmonyOS 的ACTIVITY_MOTION权限是“用户运动状态”权限但系统对userStatus的生效有一个延迟窗口。首次授权后需要等待约 30 秒到 1 分钟系统才会开始推送数据。很多开发者以为授权后立即就有回调结果等不到就放弃了。解决方案在授权后延迟一段时间再订阅或者先on一次然后保持页面不销毁等阈值时间后再观察。真机测试最好在运动状态下保持 2 分钟。问题 2motion在折叠屏上表现异常现象将折叠屏折叠后motion的isMoving一直为true无法静止。原因折叠屏的铰链动作会导致加速度计数据抖动系统可能会把折叠动作误判为移动。官方在 API 18 之后对折叠屏设备有特殊处理但如果motion的采样频率设置不当依然容易误报。解决方案推荐在上层加一个二次过滤——连续 5 次isMoving false才认为静止。可以在回调里累积计数而不是单次判断。privatestillCount:number0;privatemotionCallback:Callbackboolean(isMoving:boolean){if(isMoving){stillCount0;// 确实在动return;}stillCount;if(stillCount5){// 确认静止console.info(设备已静止);}};最佳实践不要在build()中注册回调。build()在每次状态变更时都会执行导致on被反复调用不仅浪费性能还会触发重复订阅导致回调重复执行。需要把on操作放在onPageShow或onAppear中。userStatus的取消时机比订阅更重要。很多开发者只记得on忘记off。结果页面已经销毁但系统还在轮询传感器并推送给已销毁的组件造成内存泄漏和空指针异常。建议在aboutToDisappear中统一清理所有订阅。运动模拟器无法测试userStatus。即使你在模拟器里模拟“走路”userStatus也不会生效。它依赖真实的 GPS 和陀螺仪数据模拟器只能输出假数据。测试时必须使用真机并且让手机处于运动状态可以握在手里走路。完整入口文件// pages/Index.etsimport{motion,userStatus,MotionType,UserStatus}fromkit.MultimodalAwarenessKit;import{common}fromkit.AbilityKit;EntryComponentstruct MotionDemo{aboutToDisappear():void{motion.off(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,this.motionCallback);userStatus.off(statusChange,this.statusCallback);}privatemotionCallback:Callbackboolean(isMoving:boolean){if(!isMoving){userStatus.off(statusChange,this.statusCallback);return;}userStatus.off(statusChange,this.statusCallback);userStatus.on(statusChange,this.statusCallback);};privatestatusCallback:CallbackUserStatus(newStatus:UserStatus){if(newStatusUserStatus.RUNNING){console.info(用户正跑步);}};build(){Column(){Text(用户动作与状态监测).fontSize(24).padding(20);}.width(100%).height(100%).onPageShow((){// 动态申请权限letcontext:common.UIAbilityContextgetContext(this);context.requestPermissionsFromUser([ohos.permission.ACTIVITY_MOTION]).then((){try{motion.on(motionDetect,MotionType.MOTION_TYPE_MOVEMENT,this.motionCallback);}catch(err){console.error(motion on failed: JSON.stringify(err));}}).catch((err:Error){console.error(权限授权失败: err.message);});})}}FAQQ为什么真机可以模拟器上userStatus一直不生效A模拟器不提供真实的传感器数据它只模拟加速度计和陀螺仪的基本输出但不做用户活动分类。UserStatus 依赖的多模态融合算法在模拟器中不存在。唯一解法是真机测试。Q为什么页面返回后状态检测依然在运行A因为你没有在aboutToDisappear或onPageHide中调用off()。页面虽然不可见但userStatus的回调仍然存活会一直收到数据。如果回调里引用了State变量还会造成尝试更新已销毁组件的 Warning。Qmotion和userStatus的回调是线程安全的吗A是的这两个回调都在系统线程中执行但你不能在里面直接修改State。官方文档没写这点但实测如果直接更新State会导致 ArkUI 的渲染线程和传感器线程争锁出现 UI 卡顿。建议在回调里使用Observable对象或通过updateState方式延迟更新 UI。如果你也遇到类似问题可以重点检查生命周期和状态同步逻辑。官方文档对这个行为描述得比较简单建议结合实际运行效果一起验证。不同设备上的行为可能存在差异建议真机测试。