音量键被你的应用“消费“了——InputKit 按键事件拦截全解析
文章目录
- "消费"是什么意思?
- 核心代码拆解
- 第一步:定义监听配置
- 第二步:在 aboutToAppear 里注册默认监听
- 第三步:动态添加/取消监听
- 完整生命周期管理示例
- KeyCode 常用键值速查
- 状态机流程图
- 踩坑记录
- 写在最后
做直播、录音、相机类 App 经常有这个需求:用户按音量键不触发系统音量调节,而是触发应用内的快捷操作(比如拍照、暂停)。HarmonyOS 的@kit.InputKit里有个inputConsumer模块专门干这个事。
这篇基于ArkTSInputConsumerdemo 把这块说清楚。
"消费"是什么意思?
硬件按键事件在 HarmonyOS 里有一套分发机制:先经过系统,再到应用。如果应用没有"消费"这个事件,系统就按默认行为处理(比如音量键 → 调音量)。
inputConsumer的作用就是让你的应用抢先拦截某个按键事件,让系统默认行为不再执行。
核心代码拆解
ArkTSInputConsumer的完整实现只有一个页面,逻辑非常清晰:
第一步:定义监听配置
import{inputConsumer,KeyEvent,KeyCode}from'@kit.InputKit';@Entry@Componentstruct TestDemo14{// 配置1:监听音量上键options1:inputConsumer.KeyPressedConfig={key:KeyCode.KEYCODE_VOLUME_UP,// 监听哪个键action:1,// 1 = 按下(keyDown),0 = 抬起(keyUp)isRepeat:false,// false = 优先消费,不上报给系统}// 配置2:监听音量下键options2:inputConsumer.KeyPressedConfig={key:KeyCode.KEYCODE_VOLUME_DOWN,action:1,isRepeat:false,}// 回调函数,要保存引用,否则取消监听时找不到对应的函数privatevolumeUpCallBackFunc:(event:KeyEvent)=>void=()=>{}privatevolumeDownCallBackFunc:(event:KeyEvent)=>void=()=>{}}isRepeat 字段很关键:
false:表示这是一个"消费型"监听,按键事件不会继续传递给系统(音量不变化)true:表示只是"旁听",系统默认行为照常执行
第二步:在 aboutToAppear 里注册默认监听
aboutToAppear():void{try{// 定义回调逻辑this.volumeUpCallBackFunc=(event:KeyEvent)=>{// 在这里实现业务逻辑,比如拍照、快进等this.getUIContext().getPromptAction().showToast({message:'Volume Up key pressed'});}this.volumeDownCallBackFunc=(event:KeyEvent)=>{this.getUIContext().getPromptAction().showToast({message:'Volume Down key pressed'});}// 注册监听inputConsumer.on('keyPressed',this.options1,this.volumeUpCallBackFunc);inputConsumer.on('keyPressed',this.options2,this.volumeDownCallBackFunc);}catch(error){hilog.error(DOMAIN,'InputConsumer',`Subscribe failed, error: %{public}s`,JSON.stringify(error,["code","message"]));}}第三步:动态添加/取消监听
build(){Column(){// 添加音量上键监听Button('Add monitoring for Volume Up key').onClick(()=>{try{// 重复注册同一个回调是安全的,不会重复触发inputConsumer.on('keyPressed',this.options1,this.volumeUpCallBackFunc);this.getUIContext().getPromptAction().showToast({message:'Successfully added monitoring for Volume Up key!'});}catch(error){hilog.error(DOMAIN,'InputConsumer',`Subscribe failed: %{public}s`,JSON.stringify(error,["code","message"]));}})// 取消音量上键监听Button('Remove monitoring for Volume Up key').onClick(()=>{try{// 取消时传入具体的回调函数引用,只取消这一个// 如果不传回调,会取消该 key 的所有监听inputConsumer.off('keyPressed',this.volumeUpCallBackFunc);}catch(error){hilog.error(DOMAIN,'InputConsumer',`Unsubscribe failed: %{public}s`,JSON.stringify(error,["code","message"]));}})// 音量下键的添加/取消同理...// 状态展示Row(){Text(this.text)}.width('100%').justifyContent(FlexAlign.Center)}.width('100%').height('100%')}完整生命周期管理示例
实际项目中,建议在 Ability 级别管理监听的注册和注销,防止内存泄漏:
import{inputConsumer,KeyEvent,KeyCode}from'@kit.InputKit';import{UIAbility,Want,AbilityConstant}from'@kit.AbilityKit';import{hilog}from'@kit.PerformanceAnalysisKit';exportdefaultclassEntryAbilityextendsUIAbility{privatevolumeUpCallback:(event:KeyEvent)=>void=()=>{}privatevolumeDownCallback:(event:KeyEvent)=>void=()=>{}privatevolumeUpConfig:inputConsumer.KeyPressedConfig={key:KeyCode.KEYCODE_VOLUME_UP,action:1,isRepeat:false}privatevolumeDownConfig:inputConsumer.KeyPressedConfig={key:KeyCode.KEYCODE_VOLUME_DOWN,action:1,isRepeat:false}onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{// 定义回调this.volumeUpCallback=(event:KeyEvent)=>{hilog.info(0x0000,'Ability','Volume Up consumed');// 你的业务逻辑};this.volumeDownCallback=(event:KeyEvent)=>{hilog.info(0x0000,'Ability','Volume Down consumed');};// 注册监听try{inputConsumer.on('keyPressed',this.volumeUpConfig,this.volumeUpCallback);inputConsumer.on('keyPressed',this.volumeDownConfig,this.volumeDownCallback);}catch(error){hilog.error(0x0000,'Ability',`Register failed:${JSON.stringify(error)}`);}}onDestroy():void{// Ability 销毁时必须注销,否则内存泄漏try{inputConsumer.off('keyPressed',this.volumeUpCallback);inputConsumer.off('keyPressed',this.volumeDownCallback);}catch(error){hilog.error(0x0000,'Ability',`Unregister failed:${JSON.stringify(error)}`);}}}KeyCode 常用键值速查
import{KeyCode}from'@kit.InputKit';// 媒体键(最常用于拦截)KeyCode.KEYCODE_VOLUME_UP// 音量增加KeyCode.KEYCODE_VOLUME_DOWN// 音量减少KeyCode.KEYCODE_VOLUME_MUTE// 静音// 功能键KeyCode.KEYCODE_BACK// 返回键KeyCode.KEYCODE_HOME// Home 键(系统保留,应用一般拦截不了)KeyCode.KEYCODE_POWER// 电源键(系统保留)KeyCode.KEYCODE_CAMERA// 拍照键(外接设备)// 导航键KeyCode.KEYCODE_DPAD_UP// 上方向键KeyCode.KEYCODE_DPAD_DOWN// 下方向键KeyCode.KEYCODE_DPAD_LEFT// 左方向键KeyCode.KEYCODE_DPAD_RIGHT// 右方向键// 键盘字母(A-Z)KeyCode.KEYCODE_A// A 键// ...KeyCode.KEYCODE_Z// Z 键状态机流程图
踩坑记录
坑1:回调函数引用必须保存
inputConsumer.off取消特定回调时,传入的必须是注册时的同一个函数引用。如果在on时传入匿名函数,之后完全无法通过off取消(因为匿名函数每次都是新对象)。
// 错误:匿名函数,无法单独取消inputConsumer.on('keyPressed',options1,(event:KeyEvent)=>{console.log('pressed');});inputConsumer.off('keyPressed',(event:KeyEvent)=>{});// 无效!// 正确:保存引用this.callback=(event:KeyEvent)=>{console.log('pressed');};inputConsumer.on('keyPressed',options1,this.callback);inputConsumer.off('keyPressed',this.callback);// 有效坑2:重复注册同一回调不会重复触发
on同一个 key + 同一个 callback,底层会去重,不会触发两次。但如果是不同的 callback 函数,即使逻辑一样,会触发多次。
坑3:必须有 ohos.permission.INPUT_MONITORING 权限声明
在module.json5里声明:
"requestPermissions": [ { "name": "ohos.permission.INPUT_MONITORING" } ]没有这个权限,inputConsumer.on会抛出 201 权限错误。
坑4:Home 键和电源键系统保留,拦截不了
部分系统保留按键即使注册了监听,系统也不会把事件派发给应用。音量键是可以拦截的,Home 和电源一般不行。
写在最后
inputConsumer的使用场景其实挺多的:直播 App 用音量键切换美颜,相机 App 用音量键拍照,视频播放 App 用音量键快进。记住三个核心:isRepeat: false优先消费、保存回调引用、页面销毁时注销。
