1.1. 模块
按照进程空间,我大概把整个事件输入系统分为三个部分:
kernel 收集事件上报,InputManager 读取事件并分发, InputQueue 接收事件。之间进程通信用的读写设备文件,以及共享内
存等。
1.2. 类关系
本文主要介绍 InputManager 相关,其他模块不做过多讨论。
InputManager 相关的主要类关系图:
WindowManager 往上的还没有去看,先写写 c++ 层的部分。
整个 InputManager 是通过 NativeInputManager 提供给java层的 WindowManagerService 使用的。java 层通过 JNI 构造
NativeInputManager,并向下注册通道,最后等待底层上报的事件通过通道分发上来。
InputManager 主要有两大部件:
InputReader
InputDispatcher
分别负责事件的读取和分发,各自内部有一个线程负责循环。
被处理的事件类型有:
Switch
Keyboard
Cursor
Touch(MultiTouch, SingleTouch)
Joystick
这里我只关注了Keyboard,其他流程都是类似的,更多精彩内容等待大家挖掘
2. 处理流程
整个事件处理流程简单理解就是:
InputReader 线程不断的从底层读取事件,并提交给 InputDispatcher 列队
InputDispatcher 线程把队列里的事件通过通道上报给 WindowManagerService
2.1. 输入事件的读取
InputReaderThread负责输入事件的读取,流程大致如下(dia功能有限,生命线将就了):
1. InputReaderThread 每次循环,会调用 InputReader 的 loopOnce() 方法
2. InputReader::loopOnce() 方法会依次执行:
I. EventHub::getEvents() , 这个方法会通过 epoll_wait 获取 kernel 上报的事件, ( 按键事件) 通过
KeyLayoutMap.mapKey() 映射扫描码和键值
II. processEventsLocked() ⇒ processEventsForDevicesLocked() , 获取到事件后,就开始处理事件
a. InputDevice::process() , 交给设备处理
α. KeyboardInputMapper::process() ⇒ processKey() ,交给对应的mapper处理,这里介绍键盘
输入事件。 这里会根据orientationAware属性判断是否调用rotateKeyCode()旋转一些键值
α. QueuedInputListener::notifyKey(),最后把事件加入到一个队列
III. QueuedInputListener::flush() , 之后, 调用这个函数把之前加入队列的事件刷新( 其实是把事件从
QueuedInputListener报告给InputDispatcher)
a. InputDispatcher 会把InputReader 送过来的事件,先 intercept 给 java 层的 PhoneWindowManager 做
一些预处理(比如 截屏快捷键问题 里的处理),最后加入一个队列,等着 InputDispatcherThread 来
分发
事件的读取部分到此结束,后面事件的分发是由 InputDispatcherThread 进行的。
2.2. 输入事件的分发
InputDispatcherThread 把 InputReader 提交的事件分发给 InputQueue,然后一层层上报。过程大致如下:
期间用到了 epoll,信号量 等同步方法, 和共享内存等通信方式。。。越看越庞大,先止步于此吧。
可能有疑问的地方是, Channel B怎么来的?
其实在每个Activity的ViewRoot把窗口交给WindowManagerService的时候,WMS会打开一对通道。WMS把Channel A注册到
InputManager,ViewRoot端则把 Channel B注册到 InputQueue:
2.2.1. 事件的重复
mKeyRepeatState 维护了最后一个分发的事件的重复状态。
3. 其它
3.1. NavigationBar的处理
4.x之后,如果手机上有虚拟按键(MENU,HOME,SEARCH,BACK等),用户点击的时候,底层驱动直接发送的是
KeyEvent 给 WindowManager,处理流程和前文提到的一样。如果手机没有虚拟按键,则会显示一个NavigationBar,里面放
置几个KeyButtonView模拟成虚拟按键用于导航。
这几个按键的事件处理有点特别。可以说是结合了MotionEvent和KeyEvent进行的处理。这里简单说一下它的实现方式。
1. 由于NavigationBar是显示在屏幕上的,所以它接收的是MotionEvent。
2. 然后交给KeyButtonView处理,在点击事件中,KeyButtonView会根据它自身的属性(在navigation_bar.xml里指定),
如果发现自己有keyCode(systemui:keyCode),在响应事件的时候,就会往 InputManager (的Dispatcher里)注入
一个 KeyEvent。如果没有keyCode,就按普通的点击事件处理,回调各个 onClickListener 等等。。。
3. 再然后 InputManager 分发这个 KeyEvent 之前,让 WindowManager 拦截(interceptKeyBeforeDispatching)一次,
这样就走到跟有虚拟按键一样的流程。
其中,BACK ,HOME 以及 MENU 默认都指定了 一个 keyCode,所以走到第三步。而 RECENTAPP 默认则没有keyCode,
是在onClickListener里做的处理。如果需要把RECENTAPP也模拟成虚拟按键的方式处理,就需要增加一个键值,并在
navigation_bar.xml里指定给对应的KeyButtonView。