这篇博客介绍了Android系统中的Input系统,它是负责处理用户输入操作的核心组件。该系统从各种输入设备获取原始输入事件,并将其转换为Android应用可以理解和消费的KeyEvent或MotionEvent对象。博客详细解释了Input系统的角色和重要性,以及它的主要组成部分,包括InputReader、InputDispatcher等。同时,还介绍了输入事件的来源,包括硬件设备和软件模拟,以及不同类型的输入事件,如按键事件和触摸事件。
Android Input系统简介
- Input系统的角色和重要性
- Input系统是Android系统中负责处理用户输入操作的核心组件,它负责从各种输入设备(如屏幕、键盘、鼠标等)获取原始的输入事件(如按键、触摸、滑动等),并将其转换为Android应用可以理解和消费的KeyEvent或MotionEvent对象。
- Input系统对于提供流畅、灵敏和一致的用户交互体验至关重要,它需要在不同的设备、场景和应用中保证输入事件的正确性、及时性和安全性。
- Input系统的主要组成部分
- Input系统由以下几个主要部分组成:
组件 | 功能 | 位置 |
./native/services/inputflinger/reader/EventHub.cpp | 监听/dev/input目录下的输入设备节点,获取内核事件 | Native层 |
./native/services/inputflinger/reader/InputReader.cpp | 从EventHub读取内核事件,进行预处理和分类,生成InputEvent对象 | Native层 |
./native/services/inputflinger/dispatcher/InputDispatcher.cpp | 从InputReader接收InputEvent对象,进行策略判断和分发,将事件发送给目标窗口或应用 | Native层 |
./base/services/core/java/com/android/server/input/InputManagerService.java | 提供Binder服务接口,管理InputReader和InputDispatcher,处理来自应用层或策略层的请求或回调 | Java层 |
./base/core/java/android/hardware/input/InputManager.java | 提供Java API接口,供应用层或策略层调用InputManagerService | Java层 |
Input事件的来源
硬件设备
- 硬件设备是最常见的输入事件来源,例如触摸屏、物理键盘、鼠标、游戏手柄等。这些设备通过驱动程序将用户操作转换为内核事件,并写入到/dev/input目录下对应的设备节点中。
- EventHub会监听这些设备节点,并通过getevent工具读取内核事件。内核事件包含了事件类型(如EV_KEY, EV_ABS等)、事件代码(如KEY_A, ABS_X等)和事件值(如0, 1等)。
软件模拟
- 软件模拟是另一种输入事件来源,例如虚拟键盘、语音输入、手势识别等。这些软件通过sendevent工具或InputManagerService的injectInputEvent方法向/dev/input目录下的设备节点写入内核事件。
- EventHub也会监听这些设备节点,并通过getevent工具读取内核事件。内核事件的格式和硬件设备产生的一样,只是事件源(source)不同。
截图中的2个 第一部分是由adb 发送了input keyevent 224 , 第二部分是由鼠标点击了左键1次。
Event Type | deviceId | source | action | keyCode | scanCode | buttonState | xCursorPosition | yCursorPosition | policyFlags | age |
KeyEvent | -1 | 0x00000000 | DOWN | 224 | 0 | - | - | - | 0x2b000000 | 5205ms |
KeyEvent | -1 | 0x00000000 | UP | 224 | 0 | - | - | - | 0x2b000000 | 5205ms |
MotionEvent | 4 | 0x00002002 | DOWN | - | - | 0x00000001 | 708.6 | 586.5 | 0x62000001 | 1118ms |
MotionEvent | 4 | 0x00002002 | UNKNOWN | - | - | 0x00000001 | 708.6 | 586.5 | 0x62000001 | 1118ms |
MotionEvent | 4 | 0x00002002 | UNKNOWN | - | - | 0x00000000 | 708.6 | 586.5 | 0x62000000 | 974ms |
MotionEvent | 4 | 0x00002002 | UP | - | - | 0x00000000 | 708.6 | 586.5 | 0x62000000 | 974ms |
详细解释下:
- Event Type: 表示事件的类型,可以是按键事件(KeyEvent)或触摸事件(MotionEvent)。在提供的日志中,既包含了按键事件又包含了触摸事件。
- deviceId: 表示输入设备的ID,每个输入设备都有一个唯一的ID。在日志中,deviceId为-1表示未指定或不适用。
- source: 表示事件的来源或输入设备类型。它是一个位掩码,指示事件来自哪种类型的输入设备。在提供的日志中,source的值采用16进制表示,例如0x00000000和0x00002002。
- action: 表示事件的动作,即事件的状态或操作类型。对于按键事件,动作可以是DOWN(按下)或UP(释放)。对于触摸事件,动作可以是DOWN(按下)、UP(释放)或其他(如UNKNOWN)。
- keyCode: 表示按键事件中按下或释放的按键的唯一标识符。它与具体硬件设备无关,是Android系统定义的一套统一的按键编码。
- scanCode: 表示内核事件中用来表示按键的唯一标识符,它与硬件设备相关,不同的设备可能有不同的scanCode。
- buttonState: 表示触摸事件中的鼠标按钮状态。在提供的日志中,buttonState为0x00000000表示没有按下鼠标按钮,为0x00000001表示按下了鼠标左键。
- xCursorPosition: 表示触摸事件或鼠标事件发生时的X轴坐标位置。
- yCursorPosition: 表示触摸事件或鼠标事件发生时的Y轴坐标位置。
- policyFlags: 表示事件的策略标志或属性,它是一个位掩码,表示事件相关的特征或行为。
- age: 表示事件生成的时间或年龄,单位为毫秒。它表示事件生成时间与当前时间的间隔。
Input事件的类型
- 按键事件(KeyEvent)
- 按键事件是指用户按下或松开一个按键时产生的事件,例如物理键盘上的字母键、数字键、功能键等,或者虚拟键盘上的返回键、菜单键、音量键等。
- 按键事件由KeyEvent类表示,它包含了按键的状态(如ACTION_DOWN, ACTION_UP等)、代码(如KEYCODE_A, KEYCODE_BACK等)、标志(如FLAG_CANCELED, FLAG_LONG_PRESS等)、源(如SOURCE_KEYBOARD, SOURCE_GAMEPAD等)、设备(如Keyboard, Mouse等)等属性。
- 触摸事件(MotionEvent)
- 触摸事件是指用户在触摸屏上进行触摸、滑动、缩放等操作时产生的事件,例如单点触摸、多点触摸、手势识别等。
- 触摸事件由MotionEvent类表示,它包含了触摸的状态(如ACTION_DOWN, ACTION_MOVE等)、位置(如X, Y坐标)、压力(如pressure, size等)、速度(如xVelocity, yVelocity等)、源(如SOURCE_TOUCHSCREEN, SOURCE_STYLUS等)、设备(如Touchscreen, Stylus等)等属性。
- 其他事件(GenericMotionEvent)
- 其他事件是指用户使用非触摸屏的输入设备进行移动或旋转操作时产生的事件,例如鼠标移动、滚轮滚动、操纵杆移动、陀螺仪旋转等。
- 其他事件也由MotionEvent类表示,但是它们的状态是ACTION_GENERIC_MOTION,而且它们的属性可能和触摸事件不同,例如轴值(如AXIS_X, AXIS_Y等)、旋转角度(如AXIS_ORIENTATION, AXIS_TILT等)、源(如SOURCE_MOUSE, SOURCE_JOYSTICK等)、设备(如Mouse, Joystick等)等。
Input事件的属性
- 事件代码(scanCode和keyCode)
- scanCode是内核事件中用来表示按键的唯一标识符,它与硬件设备相关,不同的设备可能有不同的scanCode。
- keyCode是KeyEvent中用来表示按键的唯一标识符,它与硬件设备无关,是Android系统定义的一套统一的按键编码。
- scanCode和keyCode之间有一个映射关系,由KeyLayout文件定义。KeyLayout文件是一种文本文件,用来描述输入设备的按键布局和功能。Android系统会根据输入设备的名称或ID加载对应的KeyLayout文件,并根据文件中的映射规则将scanCode转换为keyCode。
- 事件标志(flags)
- flags是KeyEvent或MotionEvent中用来表示事件特征或行为的位掩码,它可以有多个值同时存在,例如FLAG_CANCELED表示事件被取消,FLAG_LONG_PRESS表示长按事件,FLAG_FROM_SYSTEM表示系统产生的事件等 。
- flags可以影响事件的分发和处理过程,例如FLAG_CANCELED会导致后续的ACTION_UP或ACTION_MOVE被忽略,FLAG_FROM_SYSTEM会导致某些策略层拦截或修改事件等。
- 事件源(source)
- source是KeyEvent或MotionEvent中用来表示输入设备类型或类别的位掩码,它可以有多个值同时存在,例如SOURCE_KEYBOARD表示来自物理键盘的事件,SOURCE_TOUCHSCREEN表示来自触摸屏。
Input事件的流程
- InputReader
- InputReader是Input系统中负责从EventHub读取内核事件,并将其转换为InputEvent对象的组件。它运行在一个单独的线程(InputReaderThread)中,不断地调用EventHub的getEvents方法获取内核事件。
- InputReader会根据内核事件的类型(EV_KEY, EV_ABS等)和设备节点(event0, event1等)进行分类和预处理,例如根据KeyLayout文件将scanCode转换为keyCode,根据TouchScreenCalibration文件将触摸坐标进行校准,根据InputDeviceConfiguration文件将设备信息进行解析等。
- InputReader还会根据内核事件的属性(source, device等)和策略层(InputReaderPolicy)的建议,将内核事件转换为KeyEvent或MotionEvent对象,并赋予相应的状态(action, flags等)和时间戳(eventTime, downTime等)。
- InputReader最后会将生成的InputEvent对象发送给InputClassifier,进行进一步的处理。
- InputDispatcher
- InputDispatcher是Input系统中负责从InputClassifier接收InputEvent对象,并将其分发给目标窗口或应用的组件。它运行在另一个单独的线程(InputDispatcherThread)中,不断地调用InputClassifier的process方法获取InputEvent对象。
- InputDispatcher会根据InputEvent对象的类型(KeyEvent或MotionEvent)、属性(flags, source等)和策略层(InputDispatcherPolicy)的建议,进行策略判断和分发,例如判断是否需要拦截或修改事件,判断是否需要唤醒设备或关闭屏幕,判断是否需要显示或隐藏输入法等。
- InputDispatcher还会根据窗口管理层(WindowManagerService)提供的窗口信息(WindowState, LayoutParams等),找到合适的目标窗口或应用,并通过InputChannel与之通信,将事件发送给目标窗口或应用。
- InputDispatcher最后会等待目标窗口或应用对事件的消费结果(如Consumed, Injected等),并根据结果进行相应的处理,例如回调策略层或窗口管理层,更新事件状态或队列等。
- InputChannel
- InputChannel是一种用于在进程间传递InputEvent对象的通信机制,它是基于Binder机制实现的。每个InputChannel都包含一对文件描述符(fd),分别用于读取和写入InputEvent对象。
- InputChannel可以在不同的进程间传递,例如InputDispatcher可以将一个InputChannel传递给目标窗口或应用所在的进程,然后通过这个InputChannel向目标窗口或应用发送InputEvent对象。
- InputChannel还可以在同一个进程内传递,例如目标窗口或应用可以将一个InputChannel传递给它自己的子线程或子组件,然后通过这个InputChannel向子线程或子组件发送InputEvent对象。
参考:
创建输入法 | Android 开发者 | Android Developers
Android 设备输入事件(input)派发原理总结 - 掘金
还没写完 , 有空继续…
有什么问题 欢迎留言~