HarmonyOS NEXT 输入设备管理详解:@ohos.multimodalInput.inputDevice 设备发现与热插拔监听实战
引言
想一想你的 HarmonyOS NEXT 设备——它可能连接了蓝牙键盘、鼠标、触控笔,甚至游戏手柄。你的闹钟 App 想检测用户是否在操作设备来决定是否响铃;你的外设管理工具想列出所有连接的输入设备并显示详情;你的辅助功能应用想感知键盘的插拔来切换输入模式。
这些场景都有一个共同的需求:发现、查询和监听输入设备。HarmonyOS NEXT 的 @ohos.multimodalInput.inputDevice 模块正是为此而生的。
本文将通过"输入设备实验室"Demo,深入讲解输入设备管理的核心 API:设备列表获取、设备详情查询、热插拔事件监听、键盘类型识别、空闲时间检测。值得注意的是,inputDevice 属于 @kit.InputKit,这也是本系列首次离开 @kit.BasicServicesKit 的舒适区,进入输入子系统的领域。
@ohos.multimodalInput.inputDevice 模块概述
@ohos.multimodalInput.inputDevice 是 HarmonyOS NEXT 多模输入子系统中的设备管理模块。它不负责输入事件的传递(那是 @ohos.multimodalInput.inputMonitor 的职责),而是专注于设备元数据查询和生命周期感知——设备有哪些、每个设备的属性是什么、设备何时接入/移除、用户多久没操作了。
导入方式
import inputDevice from '@ohos.multimodalInput.inputDevice';
模块属于 @kit.InputKit,不是 @kit.BasicServicesKit。这是理解鸿蒙 Kit 划分的好例子:窗口、WiFi、电池、温控、电源都属于基础系统服务(BasicServicesKit),而输入设备属于专门的输入 Kit。
API 总览
| API | 返回值 | 类型 | 说明 | API 版本 |
|---|---|---|---|---|
getDeviceList() | Promise<Array<number>> | 异步 | 获取所有设备 ID 列表 | API 9+ |
getDeviceInfo(deviceId) | Promise<InputDeviceData> | 异步 | 获取指定设备详细信息 | API 9+ |
getDeviceInfoSync(deviceId) | InputDeviceData | 同步 | 同步获取设备信息 | API 10+ |
on('change', listener) | void | 订阅 | 监听设备热插拔事件 | API 9+ |
off('change', listener?) | void | 取消 | 取消监听热插拔事件 | API 9+ |
getKeyboardType(deviceId) | Promise<KeyboardType> | 异步 | 获取键盘类型 | API 9+ |
getKeyboardTypeSync(deviceId) | KeyboardType | 同步 | 同步获取键盘类型 | API 10+ |
supportKeys(deviceId, keys) | Promise<Array<boolean>> | 异步 | 检测按键支持 | API 9+ |
supportKeysSync(deviceId, keys) | Array<boolean> | 同步 | 同步检测按键支持 | API 10+ |
getIntervalSinceLastInput() | Promise<number> | 异步 | 距上次输入的时间间隔(ms) | API 14+ |
关键数据类型
SourceType — 输入源类型(联合类型字符串),不是枚举:
type SourceType = 'keyboard' | 'mouse' | 'touchpad' |
'touchscreen' | 'joystick' | 'trackball';
KeyboardType — 键盘类型枚举:
| 枚举值 | 数值 | 含义 |
|---|---|---|
NONE | 0 | 无键盘 |
UNKNOWN | 1 | 未知类型键盘 |
ALPHABETIC_KEYBOARD | 2 | 全键盘(QWERTY 等) |
DIGITAL_KEYBOARD | 3 | 数字小键盘 |
HANDWRITING_PEN | 4 | 手写笔 |
REMOTE_CONTROL | 5 | 遥控器 |
InputDeviceData — 设备详细信息的完整接口:
interface InputDeviceData {
id: number; // 设备唯一 ID(重连可能变化)
name: string; // 设备名称,如 "HUAWEI Mouse"
sources: Array<SourceType>; // 支持的输入源(可多个)
axisRanges: Array<AxisRange>; // 轴信息
bus: number; // 总线类型
product: number; // 产品 ID
vendor: number; // 厂商 ID
version: number; // 版本号
phys: string; // 物理路径
uniq: string; // 唯一标识符
isVirtual?: boolean; // 是否虚拟设备(API 23+)
isLocal?: boolean; // 是否本地设备(API 23+)
}
DeviceListener — 热插拔事件:
interface DeviceListener {
type: ChangedType; // 'add' | 'remove'
deviceId: number; // 发生变化的设备 ID
}
核心 API 详解
1. getDeviceList() — 枚举所有设备
function getDeviceList(): Promise<Array<number>>;
这是探索输入设备的起点——先获取所有设备的 ID 列表,再逐个查询详情。
inputDevice.getDeviceList()
.then((ids: Array<number>) => {
console.log('发现 ' + ids.length + ' 个输入设备');
// ids = [1, 2, 3, ...]
// 每个 ID 对应一个物理或虚拟输入设备
})
.catch((err: Error) => {
console.error('获取设备列表失败: ' + err.message);
});
注意:getDeviceList() 返回的是 Promise,这意味着获取设备列表是一个异步操作——系统需要查询底层输入子系统来枚举所有设备。在设备热插拔时,返回的列表会反映当前最新的设备状态。
旧版 API getDeviceIds()(API 8)已被废弃,始终使用 getDeviceList()。
2. getDeviceInfo / getDeviceInfoSync — 设备详情查询
function getDeviceInfo(deviceId: number): Promise<InputDeviceData>;
function getDeviceInfoSync(deviceId: number): InputDeviceData;
Promise 版本和同步版本提供相同的数据,区别在于调用方式。对于已经获取到的 ID 列表,用同步版本逐个查询更简洁:
// 推荐:先用 getDeviceList 获取 ID,再用 getDeviceInfoSync 逐个查询
inputDevice.getDeviceList()
.then((ids: Array<number>) => {
for (let i = 0; i < ids.length; i++) {
try {
const info: inputDevice.InputDeviceData =
inputDevice.getDeviceInfoSync(ids[i]);
console.log(info.name); // "HUAWEI Mouse"
console.log(info.sources); // ['mouse']
console.log(info.vendor); // 厂商 ID
console.log(info.product); // 产品 ID
console.log(info.phys); // 物理路径
} catch (e) {
console.error('获取设备 ' + ids[i] + ' 信息失败');
}
}
});
InputDeviceData 中几个值得关注的字段:
sources:这是一个数组——一个物理设备可以有多个输入源。例如带触控板的键盘会同时报告['keyboard', 'touchpad']。vendor/product:USB-IF 分配的厂商和产品 ID,可以用来识别具体的外设型号。phys:Linux 内核的设备物理路径,如"usb-xhci-hcd.0.auto-1/input0"。bus:总线类型的数值编码,如 USB=3、Bluetooth=5 等。isVirtual(API 23+):标识是否为虚拟输入设备(如远程桌面注入的输入)。
3. on(‘change’) / off(‘change’) — 热插拔监听
function on(type: 'change', listener: Callback<DeviceListener>): void;
function off(type: 'change', listener?: Callback<DeviceListener>): void;
这是 inputDevice 模块最"活"的 API——它让应用能实时感知外设的接入和移除。当用户插入 USB 键盘、连接蓝牙鼠标、或拔出触控笔时,监听器会立即收到事件。
// 注册监听器
const listener: Callback<inputDevice.DeviceListener> =
(data: inputDevice.DeviceListener) => {
if (data.type === 'add') {
console.log('设备接入: ID=' + data.deviceId);
// 可以调用 getDeviceInfoSync 查询新设备详情
} else {
console.log('设备移除: ID=' + data.deviceId);
}
};
inputDevice.on('change', listener);
// 取消监听(页面销毁时必须调用)
inputDevice.off('change', listener);
重要提示:
-
off()的listener参数是可选的——不传则取消该事件的所有监听器。但如果要精确取消某一个监听器,必须传入on()时使用的同一个函数引用(不是内容相同的匿名函数)。这是 Callback 模式的核心规则。 -
生命周期管理是必须的:在页面的
aboutToDisappear()中调用off(),否则页面销毁后监听器仍在后台运行,既浪费资源又可能导致内存泄漏。 -
热插拔事件中的
deviceId是新设备的 ID,可以立即用getDeviceInfoSync(deviceId)获取详情。
4. getKeyboardType / getKeyboardTypeSync — 键盘类型识别
function getKeyboardType(deviceId: number): Promise<KeyboardType>;
function getKeyboardTypeSync(deviceId: number): KeyboardType;
对于键盘类设备,可以进一步查询其具体类型:
try {
const kt: inputDevice.KeyboardType =
inputDevice.getKeyboardTypeSync(deviceId);
if (kt === inputDevice.KeyboardType.ALPHABETIC_KEYBOARD) {
console.log('全键盘 — 可使用快捷键');
} else if (kt === inputDevice.KeyboardType.DIGITAL_KEYBOARD) {
console.log('数字键盘 — 仅数字输入');
}
} catch (e) {
// 非键盘设备可能抛异常
console.log('非键盘设备');
}
对于非键盘设备(如纯鼠标),getKeyboardTypeSync() 可能抛出异常。因此在实际使用中应该用 try-catch 包裹,或先用 sources 判断设备是否包含 'keyboard' 源。
5. getIntervalSinceLastInput() — 空闲检测
function getIntervalSinceLastInput(): Promise<number>;
这是 API 14 新增的功能——返回自上次用户输入事件(键盘按键、屏幕触摸、鼠标点击等)以来经过的毫秒数。对于需要根据用户活跃度调整行为的场景非常有用:
inputDevice.getIntervalSinceLastInput()
.then((ms: number) => {
const seconds: number = Math.floor(ms / 1000);
if (seconds < 60) {
console.log('用户 ' + seconds + ' 秒前有操作');
} else if (seconds < 3600) {
console.log('用户已空闲 ' + Math.floor(seconds / 60) + ' 分钟');
} else {
console.log('用户已空闲超过 1 小时');
}
});
注意,这个 API 计算的是距上次用户输入事件的时间,不是"用户离开屏幕"的时间。它包括了设备休眠时间——如果设备休眠了 10 分钟后被唤醒,返回的值会超过 10 分钟。
6. supportKeys / supportKeysSync — 按键支持检测
function supportKeys(deviceId: number, keys: Array<KeyCode>): Promise<Array<boolean>>;
function supportKeysSync(deviceId: number, keys: Array<KeyCode>): Array<boolean>;
用于查询某个输入设备是否支持特定的按键码。一次最多查询 5 个按键。这个 API 在适配特殊键盘布局时非常有用——例如确认外接键盘是否有媒体控制键、功能键等。


实战:输入设备实验室
页面设计
Demo 的整体布局如下:
┌─────────────────────────────────┐
│ < 输入设备实验室 @kit.InputKit │ ← 标题栏(#EC4899 粉红)
├─────────────────────────────────┤
│ 已连接设备 │
│ ┌─────────────────────────────┐│
│ │ HUAWEI Mouse ││ ← 可点击展开的卡片
│ │ [鼠标] ▼ ││
│ │ ───────────────────────── ││
│ │ 设备 ID: 1 ││ ← 展开后的详细信息
│ │ 键盘类型: 非键盘设备 ││
│ │ 轴数量: 3 个 ││
│ │ 总线: 0x3 ││
│ └─────────────────────────────┘│
│ ┌─────────────────────────────┐│
│ │ HUAWEI Keyboard ││
│ │ [键盘] ▼ ││
│ └─────────────────────────────┘│
├─────────────────────────────────┤
│ [重新扫描] [热插拔监听] │ ← 操作按钮
├─────────────────────────────────┤
│ 空闲检测 │
│ ┌─────────────────────────────┐│
│ │ 距上次输入事件 42 秒 ││
│ │ [检测空闲时间] ││
│ └─────────────────────────────┘│
├─────────────────────────────────┤
│ 事件日志 │
│ ┌─────────────────────────────┐│
│ │ 14:32:05 发现 3 个输入设备 ││
│ │ 14:32:01 正在扫描输入设备.. ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘
核心实现
设备数据模型
interface DeviceItem {
id: number;
name: string;
sourceLabels: string[]; // 中文标签数组,如 ['键盘', '触控板']
bus: number;
product: number;
vendor: number;
version: number;
phys: string;
keyboardType: string; // 键盘类型中文描述
axisCount: number; // 轴数量
}
注意 sourceLabels 是 string[] 而非 string——因为一个物理设备可以有多个输入源(如带触控板的键盘),用数组存储可以在 UI 中渲染为多个彩色标签。这是从 API 的 Array<SourceType> 到 UI 的 string[] 的映射层。
源类型映射
SourceType 是英文字符串,在 UI 中需要映射为中文标签:
private sourceLabel(s: string): string {
if (s === 'keyboard') { return '键盘'; }
if (s === 'mouse') { return '鼠标'; }
if (s === 'touchpad') { return '触控板'; }
if (s === 'touchscreen') { return '触摸屏'; }
if (s === 'joystick') { return '手柄'; }
if (s === 'trackball') { return '轨迹球'; }
return s;
}
每种设备类型在 UI 中都有不同的颜色标识——键盘用天蓝 #0EA5E9,鼠标用翠绿 #10B981,触控板用靛蓝 #6366F1,触摸屏用橙色 #F97316,手柄用紫罗兰 #8B5CF6,轨迹球用粉红 #EC4899。这种色彩区分让用户一眼就能识别设备类型。
设备扫描流程
private loadDevices(): void {
this.addLog('正在扫描输入设备...');
inputDevice.getDeviceList()
.then((ids: Array<number>) => {
const items: DeviceItem[] = [];
for (let i = 0; i < ids.length; i++) {
try {
// 用同步 API 逐个获取设备信息
const info = inputDevice.getDeviceInfoSync(ids[i]);
// 映射 SourceType → 中文标签
const labels: string[] = [];
for (let j = 0; j < info.sources.length; j++) {
labels.push(this.sourceLabel(info.sources[j]));
}
// 尝试获取键盘类型
let kbType: string = '';
try {
kbType = this.keyboardTypeLabel(
inputDevice.getKeyboardTypeSync(ids[i]));
} catch (e) {
kbType = '非键盘设备';
}
items.push({
id: info.id,
name: info.name,
sourceLabels: labels,
bus: info.bus,
product: info.product,
vendor: info.vendor,
version: info.version,
phys: info.phys,
keyboardType: kbType,
axisCount: info.axisRanges.length
});
} catch (e) {
this.addLog('获取设备 ' + ids[i] + ' 信息失败');
}
}
this.devices = items;
this.loading = false;
this.addLog('发现 ' + ids.length + ' 个输入设备');
});
}
这里的模式值得注意:异步获取列表 + 同步获取详情。getDeviceList() 是 Promise,因为它需要查询系统服务;而拿到 ID 后,getDeviceInfoSync() 可以直接从内核接口读取数据,无需再次异步。这种组合方式比"全部 Promise"更简洁,避免了嵌套 then() 的问题。
热插拔监听
private changeListener: Callback<inputDevice.DeviceListener> | null = null;
private startListen(): void {
this.changeListener = (data: inputDevice.DeviceListener) => {
const action: string = data.type === 'add' ? '插入' : '移除';
this.addLog('设备' + action + ': ID=' + data.deviceId);
this.loadDevices(); // 刷新设备列表
};
inputDevice.on('change', this.changeListener);
this.listening = true;
this.addLog('开始监听设备热插拔');
}
private stopListen(): void {
if (this.changeListener !== null) {
inputDevice.off('change', this.changeListener);
this.changeListener = null;
}
this.listening = false;
}
这里的设计与上一篇温控实验室的 Callback 模式相同:将 listener 引用保存在类属性中,在 aboutToDisappear() 中调用 stopListen() 清理。off() 传入的 listener 必须是 on() 时注册的同一个函数引用。
空闲时间检测
private checkIdle(): void {
this.addLog('正在查询空闲时间...');
inputDevice.getIntervalSinceLastInput()
.then((ms: number) => {
const sec: number = Math.floor(ms / 1000);
if (sec < 60) {
this.idleTime = sec + ' 秒';
} else if (sec < 3600) {
this.idleTime = Math.floor(sec / 60) + ' 分 ' +
(sec % 60) + ' 秒';
} else {
this.idleTime = Math.floor(sec / 3600) + ' 时 ' +
Math.floor((sec % 3600) / 60) + ' 分';
}
this.addLog('距上次输入: ' + this.idleTime);
})
.catch((err: Error) => {
this.idleTime = '获取失败';
this.addLog('查询失败: ' + err.message);
});
}
getIntervalSinceLastInput() 返回的是距离用户最后一次输入事件的时间。这里的"输入事件"是广义的——包括触摸屏幕、点击鼠标、按下键盘等所有用户主动触发的输入。与 @ohos.power 的 isActive()(亮屏)和 isStandby()(待机)不同,空闲检测更精细——用户可能在亮屏状态下闲置(例如阅读长文),也可能在待机状态下通过后台音乐保持活跃。
可展开的设备卡片
Demo 中的设备卡片是可展开的——点击卡片会在折叠和展开之间切换。展开后显示 InputDeviceData 的全部字段:
@Builder
deviceCard(item: DeviceItem) {
Column() {
Row() {
Column() {
Text(item.name)
.fontSize(14).fontColor('#0F172A')
.fontWeight(FontWeight.Medium)
Row() {
ForEach(item.sourceLabels, (s: string) => {
Text(s)
.fontSize(9).fontColor(this.sourceColor(s))
.backgroundColor(this.sourceBgColor(s))
.borderRadius(4)
})
}
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text(this.expandedId === item.id ? '▲' : '▼')
.fontSize(10).fontColor('#94A3B8')
}
.width('100%')
if (this.expandedId === item.id) {
Divider().height(0.5).color('#E2E8F0')
this.detailRow('设备 ID', item.id.toString())
this.detailRow('键盘类型', item.keyboardType)
this.detailRow('轴数量', item.axisCount + ' 个')
this.detailRow('总线', '0x' + item.bus.toString(16))
this.detailRow('产品 ID', '0x' + item.product.toString(16))
this.detailRow('厂商 ID', '0x' + item.vendor.toString(16))
this.detailRow('版本', item.version.toString())
this.detailRow('物理地址', item.phys)
}
}
.width('100%').padding({ top: 8, bottom: 8 })
.onClick(() => { this.toggleExpand(item.id); })
}
展开/折叠的状态管理很简单——用一个 expandedId 状态变量记录当前展开的设备 ID。点击设备时,如果是同一个则折叠(设为 -1),如果是另一个则切换展开目标。
没有权限要求
inputDevice 模块的一个显著特点是:所有查询和监听 API 都不需要权限。getDeviceList()、getDeviceInfo()、on('change')、getKeyboardType()、getIntervalSinceLastInput() 全部无需声明任何权限即可使用。
唯一的例外是 setFunctionKeyEnabled()(API 15+),它需要 ohos.permission.INPUT_KEYBOARD_CONTROLLER 权限——但这个 API 仅限输入法应用使用,普通应用不需要关心。
这使得 inputDevice 成为最容易集成到任何应用中的系统 API——不需要申请权限,不需要用户授权弹窗,开箱即用。
@kit.InputKit 与 Kit 体系
本系列前六篇全部使用 @kit.BasicServicesKit 中的模块(window、wifiManager、batteryInfo、thermal、power、runningLock)。inputDevice 是第一个来自 @kit.InputKit 的模块。
HarmonyOS NEXT 将系统能力划分为不同的 Kit,每个 Kit 包含一组相关的能力模块:
| Kit | 包含的模块(示例) | 领域 |
|---|---|---|
@kit.BasicServicesKit | window, wifiManager, batteryInfo, thermal, power, runningLock, sensor 等 | 基础系统服务 |
@kit.InputKit | inputDevice, inputMonitor, inputConsumer, keyCode, keyEvent 等 | 输入子系统 |
@kit.ArkUI | 所有 UI 组件、router、动画 | 用户界面 |
@kit.TelephonyKit | sim, radio, call, sms 等 | 电话通信 |
理解 Kit 的划分有助于高效查阅文档——当你需要输入相关的能力时,去 @kit.InputKit 而非 @kit.BasicServicesKit 中寻找。这种模块化设计也意味着不同 Kit 有不同的 SystemCapability,应用可以在 module.json5 中精确声明所需的能力。
五种异步模式的回顾
截至目前,本系列已经遇到了五种 API 调用模式:
| 模式 | 代表模块 | 特征 | 适用场景 |
|---|---|---|---|
| 同步常量 | batteryInfo, power | 直接读属性,零开销 | 状态查询 |
| Promise 链 | wifiManager, window | .then().catch() | 需系统服务响应 |
| Callback 订阅 | thermal, inputDevice | on(ev, cb) / off(ev, cb) | 持续事件流 |
| Promise 创建 + 同步操作 | runningLock | create() 返回 Promise,后续同步 | 资源获取后持续操作 |
| Promise + Sync 双轨 | inputDevice | 每个查询 API 同时提供 Promise 和 Sync 版本 | 灵活选择调用方式 |
第五种模式——Promise + Sync 双轨——是 inputDevice 的特色。API 9 引入了 Promise 版本(getDeviceList、getDeviceInfo),API 10 又补充了 Sync 版本(getDeviceInfoSync、getKeyboardTypeSync、supportKeysSync)。这种设计给了开发者最大的灵活性:
- 用
getDeviceList()(Promise)获取动态列表——因为设备数量可能随时变化,Promise 保证拿到最新快照 - 用
getDeviceInfoSync()(Sync)获取静态详情——因为设备信息在短时间内不变,同步调用更简洁 - 用
on('change')(Callback)持续监听——因为这是一条事件流,不是一次性查询
三种异步模式在同一个模块中协同工作,体现了 HarmonyOS NEXT API 设计上的成熟。
实际应用场景
场景 1:外设管理工具
在设备的"设置 → 蓝牙"或"设置 → 外接设备"页面中,通常会列出所有已连接的输入设备:
// 获取设备列表用于设置页面
async function loadDeviceList(): Promise<DeviceItem[]> {
const ids = await inputDevice.getDeviceList();
const items: DeviceItem[] = [];
for (const id of ids) {
const info = inputDevice.getDeviceInfoSync(id);
items.push({ id: info.id, name: info.name,
sources: info.sources });
}
return items;
}
配合 on('change'),当用户插拔设备时列表可以实时更新。
场景 2:辅助功能 — 输入模式自适应
对于辅助功能应用,当检测到物理键盘接入时自动切换输入模式:
inputDevice.on('change', (data: inputDevice.DeviceListener) => {
if (data.type === 'add') {
const info = inputDevice.getDeviceInfoSync(data.deviceId);
if (info.sources.includes('keyboard')) {
// 切换到键盘导航模式
switchToKeyboardMode();
} else if (info.sources.includes('mouse')) {
// 启用鼠标指针增强
enableMousePointerEnhancement();
}
} else if (data.type === 'remove') {
// 检查是否还有键盘连接
checkAndFallbackToTouchMode();
}
});
场景 3:防误触与自动锁屏
利用 getIntervalSinceLastInput() 检测用户空闲时长,结合运行锁实现智能锁屏策略:
async function shouldAutoLock(): Promise<boolean> {
const idleMs = await inputDevice.getIntervalSinceLastInput();
// 如果用户 5 分钟无操作,且界面不涉及导航/播放等场景
return idleMs > 5 * 60 * 1000 && !isActiveTaskRunning();
}
这比简单的定时器更精确——它不是从页面打开开始计时,而是从用户最后一次操作开始计时。
场景 4:游戏手柄适配
游戏应用检测手柄的接入,自动切换控制方案:
inputDevice.on('change', (data: inputDevice.DeviceListener) => {
if (data.type === 'add') {
const info = inputDevice.getDeviceInfoSync(data.deviceId);
if (info.sources.includes('joystick')) {
showGamepadIndicator();
loadGamepadControlScheme();
}
}
});
配合 supportKeys() 可以进一步检测手柄是否支持振动、陀螺仪等高级特性。
注意事项
-
设备 ID 不持久:同一物理设备在重新插拔后 ID 可能变化。不要将 deviceId 持久化存储,应该每次查询时重新获取。
-
不要假设设备存在:在模拟器上,
getDeviceList()返回的结果很有限(通常只有触摸屏)。getDeviceInfoSync()对不存在的 deviceId 会抛异常,始终用 try-catch 包裹。 -
注册的监听器必须在
aboutToDisappear()中移除:这是所有 Callback 模式 API 的通用规则。页面销毁时如果不off(),监听器变成悬空引用,不仅无法触发 UI 更新,还会浪费系统资源。 -
getIntervalSinceLastInput()包含休眠时间:如果设备休眠了 2 小时,返回的不是"用户亮屏后的 5 秒",而是 7200000ms+(2 小时+)。这在判断"用户是否刚操作过"时需要特别注意。 -
Sync 方法虽方便,但要理解适用边界:Sync 方法读取的是调用时刻的快照,不会触发系统服务的实时查询。对于动态变化的设备列表,始终用
getDeviceList()Promise 版本获取最新状态。 -
getKeyboardTypeSync()对非键盘设备可能抛异常:不是返回KeyboardType.NONE,而是直接抛异常。这是因为非键盘设备根本没有"键盘类型"这个概念。在调用前先用sources判断设备类型会更安全。 -
supportKeys()最多 5 个按键:这是单次查询的限制,需要查询更多按键时可以分批调用。
完整代码
import { router } from '@kit.ArkUI';
import inputDevice from '@ohos.multimodalInput.inputDevice';
import { FontSize, Spacing } from '../common/Constants';
interface DeviceItem {
id: number;
name: string;
sourceLabels: string[];
bus: number;
product: number;
vendor: number;
version: number;
phys: string;
keyboardType: string;
axisCount: number;
}
@Entry
@Component
struct InputDeviceLabPage {
@State devices: DeviceItem[] = [];
@State expandedId: number = -1;
@State listening: boolean = false;
@State eventLogs: ChangeEvent[] = [];
@State idleTime: string = '--';
@State loading: boolean = true;
private changeListener: Callback<inputDevice.DeviceListener> | null = null;
aboutToAppear(): void { this.loadDevices(); }
aboutToDisappear(): void { this.stopListen(); }
private loadDevices(): void {
inputDevice.getDeviceList()
.then((ids: Array<number>) => {
const items: DeviceItem[] = [];
for (let i = 0; i < ids.length; i++) {
try {
const info = inputDevice.getDeviceInfoSync(ids[i]);
// 构建中文标签 + 键盘类型 …
items.push({ /* … */ });
} catch (e) { /* … */ }
}
this.devices = items;
this.loading = false;
});
}
// … 完整代码见项目源码
}
总结
本文深入讲解了 HarmonyOS NEXT 中 @ohos.multimodalInput.inputDevice 模块的设计思路和实战应用:
-
设备发现:
getDeviceList()Promise 获取所有设备 ID,getDeviceInfoSync()同步获取详情——Promise + Sync 组合使用。 -
热插拔感知:
on('change')/off('change')Callback 模式实时感知外设接入和移除,是应用响应设备变化的核心机制。 -
类型识别:通过
SourceType判断设备类型(键盘/鼠标/触控板/触摸屏/手柄/轨迹球),通过getKeyboardType()进一步细分键盘类别。 -
空闲检测:
getIntervalSinceLastInput()精细判断用户活跃度,为自动锁屏、省电策略、辅助功能提供数据支持。 -
无权限门槛:所有查询和监听 API 无需权限,降低了集成成本。仅
setFunctionKeyEnabled()需要 INPUT_KEYBOARD_CONTROLLER 权限。 -
首个 @kit.InputKit 模块:本系列首次走出
@kit.BasicServicesKit,进入输入子系统。理解 Kit 划分有助于在正确的范围内查找 API。 -
第五种异步模式——Promise + Sync 双轨:同一模块同时提供 Promise 和 Sync 版本的查询 API,让开发者根据场景灵活选择调用方式。
掌握 @ohos.multimodalInput.inputDevice,你就能构建出感知输入设备变化、自适应不同外设、精细化管理用户交互体验的应用——无论是在外设管理页面、辅助功能设置,还是游戏手柄适配等场景中,这层能力都是不可或缺的底座。
更多推荐

所有评论(0)