Android应用程序输入事件分发和处理机制
最后更新于:2022-04-02 05:02:16
### **概述**
在Android应用程序中,有一类特殊的消息,是专门负责与用户进行交互的,它们就是触摸屏和键盘等输入事件。触摸屏和键盘事件是统一由系统输入管理器InputManager进行分发的。也就是说,InputManager负责从硬件接收输入事件,然后再将接收到的输入事件分发当前激活的窗口处理。此外,InputManager也能接收模拟的输入事件,用来模拟用户触摸和点击等事件。当前激活的窗口所运行在的线程接收到InputManager分发过来的输入事件之后,会将它们封装成输入消息,然后交给当前获得焦点的控件处理。
主要讲Android应用程序输入事件的分发和处理过程,主要涉及到输入管理InputManager、输入事件监控线程InputReader、输入事件分发线程InputDispatcher,以及应用程序主线程消息循环。
* Android输入系统概述
* 输入管理器的启动过程
* 输入通道的注册过程
* 输入事件的分发过程
* 软键盘输入事件的分发过程
#### **Android输入系统概述**
**输入管理器框架**

**输入管理器与应用程序通过输入通道交互**

**输入通道**

> 注:mFd指向的是一个socket
#### **输入管理器的启动过程**
**由System Server创建和启动**

注:inputManager.setWindowManagerCallbacks:设置回调,例如用来截获输入事件
**创建InputManagerService**

**创建NativeInputManager**

**创建InputManager、InputReader和InputDispatcher,以及InputReaderThread、 InputDispatcherThread**

**启动InputManagerService**

**启动NativeInputManager**

**启动InputManager**

**启动InputDispatcher**
**InputDispatcher.dispatchOnce**

> 注:InputDispatcher负责分发IO事件,IO事件分为两种类型。一种普通的输入事件,另一种是输入设备本身的事件,例如,输入设备配置发生改变事件。普通的输入事件由dispatchOnceInnerLocked处理,输入设备事件由runCommandsLockedInterruptible处理。InputDispatcher本身也维护着两个队列,一个是mInboundQueue,保存的是普通的输入事件,另一个是mCommandQueue,保存的是输入设备事件。
**启动InputReader**
* **InputReaderThread.threadLoop**

* **InputReader.loopOnce**

> 注:mNextTimeout:振动设备是按照一定的频率来进行的,这个频率就可以通过设置mNextTimeout来获得
* **EventHub.getEvents**

* **EventHub.scanDevicesLocked**

> 注:虚拟键盘:设备不一定有键盘,但是程序在测试的时候时候需要模拟键盘输入,这时候模拟的键盘输入就看作是从虚拟键盘发出的。EventHub只存一定会存在虚拟键盘。
* **EventHub.scanDirLocked**

> 注:可以用getevent –i命令来查看/dev/input目录下各个文件所对应的输入设备信息
* **EventHub.openDeviceLocked**

> 注:Key Map Configuraton File和Configuration File:保存在/system/usr或者/data/system/devices目录下
#### **输入通道的注册过程**
**Activity窗口的组成**

> 注:InputChannel是在应用程序请求WMS创建一个Activity窗口时创建的,也就是调用ViewRootImpl.setView开始创建的,在这个过程中,也会同时注册InputChannel,包括Server端InputChannel和客户端InputChannel
**创建InputChannel **
* **ViewRootImpl.setView**

> 注:mInputQueueCallback不等于null表示由view自己来接管输入事件,否则的话就由ViewRootImpl来接管
* **Session.addToDisplay**

* **WMS.addWindow**

* **InputChannel.openInputChannelPair**

* **nativeOpenInputChannelPair**

* **InputChannel::openInputChannelPair**

**注册Server端InputChannel**

> 注:win.mInputWindowHandle:窗口句柄,用来描述窗口的大小和位置等信息
* **IMS.registerInputChannel**

* **nativeRegisterInputChannel**

* **NativeInputManager.registerInputChannel**

* **InputDispatcher.registerInputChannel**

* **Looper.addFd**


**更新当前激活窗口**

* **WMS.updateFoucsedWindowLocked**

> 注:computeFocusedWindowLocked:从窗口堆栈从上到下搜索,如果它的宿主App是当前Focused的App,那么它就是Focused窗口。Focused App在窗口切换时已经确定
* **WMS. finishUpdateFocusedWindowAfterAssignLayersLocked**

* **InputMonitor.setInputFocusLw**

> 注:这里传进来的参数updateInputWindows的值等于false,不会马上调用updateInputWindowsLw来更新窗口,但是接下来新的窗口请求WMS进行layout时,就会调用updateInputWindowsLw来更新窗口
* **InputMonitor. updateInputWindowsLw**

>注: universeBackground:类型为TYPE_UNIVERSE_BACKGROUND的窗口
> TYPE_UNIVERSE_BACKGROUND的窗口:Behind the universe of the real windows, in multiuser systems shows on all users’windows
* **InputManagerService.setInputWindows**

* **nativeSetInputWindows**

* **NativeInputManager.setInputWindows**

* **InputDispatcher.setInputWindows**

**注册Client端InputChannel**

* **new WindowInputEventReceiver **

* **new InputEventReceiver **

* **nativeInit**

* **NativeInputEventReceiver.initialize**

> 注:IO事件发生时, NativeInputEventReceriver::handleEvent将被调用
#### **输入事件的分发过程**
**输入事件处理框架**

**InputReader获得输入事件**
* **InputReader获得输入事件--EventHub.getEvents**

* **InputReader获得输入事件 – InputReader.loopOnce **

* **InputReader获得输入事件 – InputReader.processEventsLocked **

* **InputReader获得输入事件 – InputReader.processEventsForDeviceLocked**

* **InputReader获得输入事件—InputDevice.process**

* **InputReader获得输入事件—KeyboardInputMapper.process**

* **InputReader获得输入事件—KeyboardInputMapper.processKey**

* **InputReader获得输入事件—InputDispatcher.notifyKey**

* **InputReader获得输入事件—InputDispatcher. enqueueInboundKeyLocked**

**InputDispatcher分发键盘事件**
* **InputDispatcher分发键盘事件 – InputDispatcher. dispatchOnceInnerLocked**

* **InputDispatcher分发键盘事件 – InputDispatcher. dispatchKeyLocked**

> 注:
> HOME键会被PhoneWindowManager的成员函数interceptKeyBeforeDispatching拦载,切换至Home App,这是通过post一个command到InputDispatcher的Command Queue去执行实现的
> findFocusedWindowTargetsLocked – 会调用checkInjectionPermission来检查当前处理的键盘事件是否是注入的,如果是的话,再检查注入者是否有权限。注入事件的时候也会做权限检查。
> 注入输入事件:
> 1. InputManagerService.injectInputEvent
> 2. InputDispatcher::injectInputEvent
> findFocusedWindowTargetsLocked还会检查上次分发给Target Window的输入事件是否已经有5s内处理完成,没有处理完成的话就会产生一个ANR Command,并且post到InputDispatcher的Command Queue去执行,最终的ANR窗口是通过mPolicy来通知AMS弹出的
* **InputDispatcher分发键盘事件 – InputDispatcher.dispatchMotionLocked**

* **InputDispatcher分发键盘事件-- InputDispatcher.dispatchEventLocked**

* **InputDispatcher分发键盘事件-- InputDispatcher.prepareDispatchCycleLocked**

> 注:标志位的解释参见InputDispatcher.h
* **InputDispatcher分发键盘事件-- InputDispatcher.startDispatchCycleLocked**

* **InputDispatcher分发键盘事件—InputPublisher.publishKeyEvent**

* **InputDispatcher分发键盘事件— InputChannel::sendMessage**

**App获得键盘事件**
* **App获得键盘事件— NativeInputEventReceiver.handleEvent**

>注: 应用程序可以通过InputEventReceiver.nativeConsumeBatchedInputEvents来批量处理InputDispatcher分发过来的输入事件
* **App获得键盘事件—NativeInputEventReceiver.consumeEvents**

>注: InputConsumer.consumer的返回值等于WOULD_BLOCK时表示当前没有发生输入事件,这时候就会开始批量处理刚才缓存起来的输入事件
* **App获得键盘事件—InputComsumer.consume**

> 注:AMOTION_EVENT_ACTION_MOVE和AMOTION_EVENT_ACTION_HOVER_MOVE类型的事件会被缓存起来批量处理
* **App获得键盘事件—InputChannel.receiveMessage**

* **App获得键盘事件—InputEventReceiver.dispatchInputEvent**

* **App获得键盘事件—WindowInputEventReceiver.onInputEvent**

* **App获得键盘事件—ViewRootImpl.enqueueInputEvent**

**App获得键盘事件—ViewRootImpl. scheduleProcessInputEvents**

* **App获得键盘事件—ViewRootImpl. doProcessInputEvents**

* **App获得键盘事件—ViewRootImpl. deliverInputEvent**

* **App获得键盘事件—ViewRootImpl. deliverKeyEvent**

* **App获得键盘事件—InputMethodCallback. finishedEvent**

* **App获得键盘事件—ViewRootImpl.dispatchImeFinishedEvent**

* **App获得键盘事件—ViewRootImpl. handleImeFinishedEvent**

* **App获得键盘事件—ViewRootImpl. deliverKeyEventPostIme**

* **App获得键盘事件—DecorView. dispatchKeyEvent**

>注: getCallback返回的Callback接口指向当前Activity
> PhoneWindow.onKeyDown和PhoneWindow.onKeyUp会对MENU和BACK等实体键进行处理,对MENU键的处理对应于openPanel的实现,对BACK键的处理对应于closePanel的实现
* **App获得键盘事件—Activity.dispatchKeyEvent**

> 注:getWindow获得的是一个PhoneWindow
* **App获得键盘事件—PhoneWindow.superDispatchKeyEvent**

>注: DecorView.dispatchKeyEvent将KeyEvent分发给当前获得焦点的View处理
**在App中,依次获得键盘事件的顺序**
* View(Pre Input Method)
* Input Method
* View(Post Input Method)
* Activity
* Phone Window(处理MENU、BACK等按键)
**HOME按键被PhoneWindowManager拦截,直接切换至Home App**
**TextView、输入法和输入法管理器的关系**

**输入法通过InputConnection.commitText分发过来的字符被封装成一个类型为FLAG_DELIVER_POST_IME的KeyEvent**
**在ViewRootImpl中,类型为FLAG_DELIVER_POST_IME的KeyEvent不用经过输入法处理,而直接通过deliverKeyEventPostIme分发给View Hierarchy处理**
**deliverKeyEventPostIme的处理过程与实体 键经过输入法处理后的过程是一样的**
* View
* Activity
* Phone Window
';