引言
现代设备早已不是"一块屏幕"那么简单。一台 HarmonyOS 设备可能同时连接着物理键盘、蓝牙鼠标、触控笔、游戏手柄,甚至通过分布式能力使用其他设备的输入外设。对于应用开发者来说,感知这些输入设备的存在、类型和状态变化,能够打造更智能的交互体验——比如检测到键盘连接时显示快捷键提示,检测到手写笔时开启精细绘图模式,或者在用户长时间未操作后进入省电状态。
HarmonyOS NEXT 通过@ohos.multimodalInput.inputDevice模块提供了完整的输入设备管理能力。本文将构建一个输入设备实验室Demo,展示设备枚举、信息查询、键盘类型检测、空闲计时器和热插拔事件监听的完整用法。
读完本文,你将掌握:
- 设备枚举:
getDeviceList()获取所有已连接输入设备 - 设备信息:
getDeviceInfoSync()读取设备的名称、类型、厂商、总线等详细属性 - 键盘类型检测:
getKeyboardTypeSync()识别字母键盘、数字键盘、手写笔等 - 空闲计时:
getIntervalSinceLastInput()获取距离上次用户操作的毫秒数 - 热插拔监听:
on('change')实时感知设备的插入与拔出
环境与权限
@ohos.multimodalInput.inputDevice属于InputKit工具包,自 API 8 开始提供。不需要申请任何权限即可使用。
importinputDevicefrom'@ohos.multimodalInput.inputDevice';这是系统级 API 中少数零权限门槛的模块之一,直接导入即用。
一、核心数据类型
InputDeviceData — 设备信息
interfaceInputDeviceData{id:number;// 设备唯一ID(重新插拔后可能变化)name:string;// 设备名称,如 "HID Keyboard"sources:Array<SourceType>;// 输入源列表(一个设备可能有多个输入源)axisRanges:Array<AxisRange>;// 轴信息(鼠标移动范围、触控区域等)bus:number;// 总线类型product:number;// 产品IDvendor:number;// 厂商IDversion:number;// 版本号}注意sources是一个数组——一个物理设备可以同时支持多种输入方式。例如,带触控板的键盘会同时有'keyboard'和'touchpad'两个 SourceType。
SourceType — 输入源类型
typeSourceType='keyboard'|'mouse'|'touchpad'|'touchscreen'|'joystick'|'trackball';这是一个字符串联合类型,不是枚举,因此直接使用字符串字面量进行比较。
KeyboardType — 键盘类型枚举
enumKeyboardType{NONE=0,// 无键盘UNKNOWN=1,// 未知类型ALPHABETIC_KEYBOARD=2,// 全字母键盘(QWERTY等)DIGITAL_KEYBOARD=3,// 数字键盘(小键盘)HANDWRITING_PEN=4,// 手写笔REMOTE_CONTROL=5// 遥控器}DeviceListener — 热插拔事件数据
typeDeviceListener={type:'add'|'remove';// 事件类型:插入或拔出deviceId:number;// 受影响的设备ID};二、设备枚举:发现所有已连接输入设备
获取设备列表采用异步 Promise 模式:
importinputDevicefrom'@ohos.multimodalInput.inputDevice';// 获取所有设备ID列表inputDevice.getDeviceList().then((deviceIds:Array<number>)=>{console.log('发现 '+deviceIds.length+' 个输入设备');// 遍历每个设备,获取详细信息for(leti=0;i<deviceIds.length;i++){letdeviceId=deviceIds[i];letinfo:inputDevice.InputDeviceData=inputDevice.getDeviceInfoSync(deviceId);console.log('设备名称: '+info.name);console.log('设备ID: '+info.id);// 解析输入源类型for(lets=0;s<info.sources.length;s++){letsource=info.sources[s];if(source==='keyboard'){console.log(' - 键盘输入');}elseif(source==='mouse'){console.log(' - 鼠标输入');}elseif(source==='touchscreen'){console.log(' - 触摸屏输入');}}}}).catch((err:Error)=>{console.error('获取设备列表失败: '+err.message);});getDeviceList 返回的是什么?
返回一个Array<number>,仅包含设备 ID。要获取设备的详细信息,需要用这些 ID 逐个调用getDeviceInfo()。
为什么用 getDeviceInfoSync 而不是 Promise 版本?
getDeviceList()返回 ID 后,需要立即获取每个设备的详细信息。如果对列表中的每个设备都发起异步请求,代码结构会比较复杂(需要Promise.all包装)。而getDeviceInfoSync()可以直接在同步循环中调用,代码更清晰。
三、设备热插拔:实时感知插拔事件
这是 inputDevice 模块最实用的功能之一。通过on('change')注册监听,当用户插入或拔出键盘、鼠标、手柄等外设时,应用会立即收到通知:
// 定义监听回调letlistener:Callback<inputDevice.DeviceListener>=(data)=>{if(data.type==='add'){console.log('设备插入: deviceId='+data.deviceId);// 获取新设备信息letinfo=inputDevice.getDeviceInfoSync(data.deviceId);console.log(' 新设备名称: '+info.name);// 可以在此更新UI,如显示"键盘已连接"提示}else{console.log('设备移除: deviceId='+data.deviceId);}};// 注册监听inputDevice.on('change',listener);// 不再需要时注销inputDevice.off('change',listener);重要:
off()必须传入与on()注册时相同的回调函数引用。如果传入匿名函数或不同的引用,注销将无效,导致内存泄漏。
在 Demo 中,监听注册后所有事件都会记录在日志列表中:
startDeviceListener():void{if(this.isListening)return;this.changeListener=(data:inputDevice.DeviceListener)=>{lettypeStr=data.type==='add'?'设备插入':'设备移除';letcolor=data.type==='add'?'#10B981':'#EF4444';this.addLog(typeStr,'deviceId='+data.deviceId.toString(),color);this.refreshDevices();// 自动更新设备列表};inputDevice.on('change',this.changeListener);this.isListening=true;}四、键盘类型检测
getKeyboardTypeSync()可以区分不同类型的键盘设备。这对于优化输入体验很有价值——例如在检测到全字母键盘后显示快捷键,或在数字键盘下调整 UI 布局:
try{letkt:inputDevice.KeyboardType=inputDevice.getKeyboardTypeSync(deviceId);if(kt===inputDevice.KeyboardType.ALPHABETIC_KEYBOARD){console.log('全键盘 — 可显示快捷键');}elseif(kt===inputDevice.KeyboardType.DIGITAL_KEYBOARD){console.log('数字键盘 — 仅数字输入');}elseif(kt===inputDevice.KeyboardType.HANDWRITING_PEN){console.log('手写笔 — 开启手写模式');}elseif(kt===inputDevice.KeyboardType.NONE){console.log('非键盘设备');}}catch(e){// 某些设备不支持键盘类型查询(如鼠标、触控板),会抛异常console.log('该设备无键盘类型');}五、空闲计时器
getIntervalSinceLastInput()返回自用户最后一次操作(触摸屏幕、按键、移动鼠标等)以来经过的毫秒数。这是一个很有用的 API:
- 省电模式:空闲超过 60 秒降低帧率或暂停动画
- 自动保存:用户停止输入 5 秒后触发
- 安全锁屏:空闲超过 5 分钟触发认证
- 活跃度统计:记录用户操作频率
inputDevice.getIntervalSinceLastInput().then((ms:number)=>{if(ms<1000){console.log('用户刚刚操作过 ('+ms+'ms)');}elseif(ms<60000){console.log('距上次输入: '+(ms/1000).toFixed(1)+' 秒');}else{console.log('空闲: '+(ms/60000).toFixed(1)+' 分钟');}}).catch((err:Error)=>{console.error('获取空闲时间失败');});六、实战 Demo:输入设备实验室
页面结构
Demo 页面InputDevicePage.ets整合了上述全部功能:
输入设备实验室 ├── 状态栏 — 实时操作反馈 ├── 设备概况 │ ├── 已连接设备数 + 距上次输入时长 │ ├── 刷新设备列表 按钮 │ ├── 刷新空闲时间 按钮 │ └── 热插拔监听 / 停止监听 按钮 ├── 设备详情卡片列表 │ ├── 设备名称 + ID │ ├── 输入源类型(多标签) │ ├── 来源数 / 总线 / 键盘类型 │ └── Product / Vendor / Version ├── 热插拔事件日志 — 最近15条事件 └── 核心 API 参考 — 10 个关键 API4 个交互点
刷新设备列表— 调用
getDeviceList()+getDeviceInfoSync()枚举所有已连接输入设备,展示名称、类型、厂商信息刷新空闲时间— 调用
getIntervalSinceLastInput()显示距离上次用户操作的精确时长热插拔监听— 通过
on('change')注册设备插拔事件,所有变更实时反映在设备列表和事件日志中键盘类型识别— 对每个设备调用
getKeyboardTypeSync(),区分全键盘、数字键盘、手写笔、遥控器等
核心代码位置
完整代码在dev/entry/src/main/ets/pages/InputDevicePage.ets(约 320 行),路由已注册为pages/InputDevicePage。
七、API 总结表
| API | 返回值 | 说明 |
|---|---|---|
getDeviceList() | Promise<Array<number>> | 获取所有已连接设备ID |
getDeviceInfoSync(id) | InputDeviceData | 同步获取设备详细信息 |
getKeyboardTypeSync(id) | KeyboardType | 获取设备键盘类型 |
getIntervalSinceLastInput() | Promise<number> | 获取距上次输入的毫秒数 |
on('change', cb) | void | 注册热插拔事件监听 |
off('change', cb) | void | 注销热插拔事件监听 |
八、与连接管理模块的对比
上一篇文章介绍的@ohos.net.connection与本文的inputDevice在 API 设计上有不少相似之处:
| 维度 | net.connection | inputDevice |
|---|---|---|
| 枚举 | getAllNetsSync() | getDeviceList()异步 |
| 详情 | getNetCapabilitiesSync() | getDeviceInfoSync() |
| 事件 | NetConnection.on('netAvailable')等 6 种事件 | inputDevice.on('change')两种事件 |
| 注册 | netConnection.register(callback) | 无需注册,直接on() |
| 注销 | netConnection.unregister(callback) | inputDevice.off('change', callback) |
| 权限 | ohos.permission.GET_NETWORK_INFO | 无需权限 |
inputDevice的 API 更加轻量——没有register()/unregister()生命周期管理,直接通过on()/off()注册和注销事件监听,保持了极简的使用体验。
九、实用场景
1. 外接键盘时显示快捷键提示
letdeviceIds=awaitinputDevice.getDeviceList();for(leti=0;i<deviceIds.length;i++){try{letkt=inputDevice.getKeyboardTypeSync(deviceIds[i]);if(kt===inputDevice.KeyboardType.ALPHABETIC_KEYBOARD){// 显示 Ctrl+S 保存、Ctrl+Z 撤销等快捷键提示this.showHotkeyBar();break;}}catch(e){/* 非键盘设备跳过 */}}2. 游戏场景检测手柄连接
// 监听设备插拔inputDevice.on('change',(data)=>{if(data.type==='add'){letinfo=inputDevice.getDeviceInfoSync(data.deviceId);if(info.sources.indexOf('joystick')>=0){this.onGamepadConnected(info.name);// 切换到手柄操作模式}}else{if(this.activeGamepadId===data.deviceId){this.onGamepadDisconnected();// 切换到触屏模式}}});3. 空闲检测触发自动锁屏
setInterval(()=>{inputDevice.getIntervalSinceLastInput().then((ms)=>{if(ms>5*60*1000){// 5分钟无操作this.lockScreen();// 触发安全锁屏}});},30000);// 每30秒检查一次十、总结
@ohos.multimodalInput.inputDevice为 HarmonyOS NEXT 应用提供了完整的输入外设感知能力:
- 设备枚举—
getDeviceList()+getDeviceInfoSync()发现所有已连接输入设备 - 6种输入源类型— 键盘、鼠标、触控板、触摸屏、摇杆、轨迹球
- 5种键盘类型— 无键盘、未知、全字母键盘、数字键盘、手写笔、遥控器
- 热插拔监听—
on('change')实时感知设备插入与拔出 - 空闲计时器—
getIntervalSinceLastInput()精确到毫秒的用户空闲检测 - 零权限门槛— 无需申请任何系统权限
这个模块虽然小巧,但为键盘快捷键适配、手柄游戏支持、手写笔优化、省电模式等场景提供了关键的底层能力。结合上一篇文章的net.connection网络管理,你已经掌握了构建设备感知型应用的两种核心基础设施。