注:如下内容基于Android API Version 27(Android 8.1)Linux Kernel 3.18.0android
Android的input事件接收和派发发生在WMS
(WindowManagerService
)所在进程,也就是system_server
进程,input系统在WMS
端有两个关键线程:读取线程和派发线程。读取线程扫描输入设备并从设备中主动读取输入事件,而后将事件交给派发线程。App进程向WMS
添加窗口时WMS
会建立一个事件通道(InputChannel
),通道的一头返回给App进程,一头注册到input派发线程,同时WMS
会将当前的全部窗口的层级和大小等信息传给input派发线程,input派发线程收到事件后查看当前focused
窗口或者能够处理当前事件的窗口,而后经过事件通道直接将事件派发给窗口,App进程将从WMS
返回的事件通道的FD(文件描述符)添加到本身主线程Looper
中,收到输入事件后由主线程直接处理事件。api
input系统在Java层表明是IMS
(InputManagerService
),IMS
随着system_server
进程启动,IMS
启动过程当中会启动两个线程,一个事件读取线程(InputReaderThread
),一个事件派发线程(InputDispatcherThread
)。缓存
WMS
直接使用IMS
,WMS
经过IMS
向事件派发线程传递窗口信息并设置当前Focused
的window
,WMS
同时也负责建立和注册system_server
进程与App进程传递事件的通道:InputChannel
。WMS
向IMS
注册事件发送端InputChannel
,IMS
最终调用到了Native层的InputManager
。并发
应用程序经过Java层的InputManager
访问IMS
,Java层的InputManager
包装了IMS
的binder
代理对象。socket
IMS
持有Native层的NativeInputManager
,NativeInputManager
持有Native的InputManger
。ide
Native层的InputManager
包含了一个InputDispatcher
和一个InputReader
,InputReader
包含了一个EventHub
。函数
InputDispatcher
继承了InputListenerInterface
,实现了notifyKey
和notifyMotion
等方法。建立InputReader
时将InputDispatcher
传给了InputReader
。InputReader
以InputListenerInterface
类型持有InputDispatcher
。工具
InputReader
有一个对应的InputReaderThread
线程。在InputReaderThread
里循环调用EventHub
获取事件。oop
EventHub
负责打开/dev/input
目录下的全部设备,和经过inotify
机制监听/dev/input
下面的设备文件变化,并负责读取全部的FD
。布局
EventHub
使用epoll
系统调用监控inotify
和设备文件FD的可读事件,当某个设备可读时,从epoll_wait
返回,紧接着读取有数据的设备的事件数据并将数据返回给InputReader
,InputReader
将原始的生事件加工成熟事件后交给InputDispather
。
InputDispatcher
对应了一个InputDispatcherThread
线程,在InputDispatcherThread
使用Native层的Looper
等待派发出去的事件的回应,同时若是InputReader
有事件会调用InputDispatcher
将事件添加到一个队列中(mInboundQueue
)并唤醒InputDispatcherThread
,InputDispatcherThread
被唤醒后调用InputDispatcher
开始派发事件队列中的事件。
事件派发过程是先找到当前的window
,而后根据window
找到Connection
,而后将事件加到Connection
的outboundQueue
, 而后从outboundQueue
队头取一个消息,调用Connection
的InputPublisher
发送事件,InputPublisher
最终会调用InputChannel
,InputChannel
用本身保存的FD
调用socketpair
的senMsg
函数将事件发出。
一个window
对应一个InputChannel
对应一个Connection
。
发完事件后,将这个消息记录到Connection
的waitQueue
的队尾。InputDispatcherThread
再次等待在Looper
上,等App窗口消费完事件并发送finish事件后,InputDispatcherThread
就会被唤醒,而后根据发生消息的FD
(一个窗口对应一个FD
)找到Connection
,再根据事件的序列号(seq
)找到事件而后将事件从waitQueue
移除,并继续派发属于这个Connction
的消息。
App窗口在调用WMS
的addWindow
时,WMS
会为App窗口创建一对InputChannel
,InputChannel
基于socketpair
(socketpair
的两端是对等的,没有server和client之分),一端给InputDispatcher
使用,一端返回给App进程用来接收事件,WMS
会将服务端的InputChannel
注册到InputDispatcher
,这样InputDispather
就能够用来给窗口发送事件并接收窗口事件了,接收事件的原理是将InputChannel
的FD
加入到InputDispather
的Looper
中,由于InputDispatherThread
阻塞在Looper.pollOnce
上,当InputDispather
收到窗口发来的finish事件后InputDispatherThread
会被唤醒,而后由InputDistather
处理finish消息。
App进程获取到InputChannel
后将以内部的socketpair
的FD
加入到main looper的FD
监听列表中去,后续若是收到事件,事件的处理会直接发生在主线程,main looper监听到FD
上有数据后回调FD
绑定的回调函数,回调函数将事件读出来封装成对应的Event对象,而后层层传递到ViewRootImpl
。ViewRootImpl
经过一个责任链决定事件的处理顺序和方式,某些事件可能会先派发给输入法窗口进行消费,若是输入法窗口不消费就继续派发给view tree消费,派发给view tree是直接派发的,由于这时已经在主线程了,流程大体是: ViewRootImpl -> DecorView -> Activity -> View(DecorView) -> DecorView的子View
若是App进程没有消费事件,也就是Activity
、View
等都没有处理这个事件,App进程发送给InputDispather
的finish事件会标志这个事件的handled
为false
。 InputDispatcher
收到handled
为false
的事件后会询问IMS
是否备选(fallback
)事件,IMS
最终会通过WMS
到PhoneWindowManager
询问是否有备选事件,若是有就将PhoneWindowManager
返回的备选事件加入到窗口对应的connection
的outboundQueue
的队头,在下一次窗口派发循环(注意InputDispather
的mInboundQueue
队列对应的大循环和connection
的outboundQueue
对应的窗口事件小循环)中将这个事件发给窗口。
备选事件:系统能够为某些事件配置回滚,好比一个按键App没有处理,系统能够派发一个与这个按钮功能相似的一个按键事件尝试让App处理。
每个事件都至少在一次线程循环中被派发。 对于当前正在派发事件的窗口,事件是发送一个收到反馈再发下一个,若是本次发送没有收到反馈,不会发下一个。 若是用户按HOME键或触发了目标为其余窗口的事件,此时InputDispatcher
发现当前窗口正在等待上一个事件的反馈,就会将排在当前窗口上的全部事件都丢弃,而后将HOME事件或属于其余窗口的事件发送给对应的目标。
/dev/input
目录下建立event0~eventN
个设备节点。inotify
监听输入设备的添加和移除。epoll
机制监听输入设备的数据变化。InputReader
。IMS
提供的配置信息,好比键盘布局。IMS
提供的配置信息(包括键盘布局,显示屏信息)对原始事件实施一次转换。ACTION_DOWN
事件) 。IMS
提供的派发前策略过滤和拦截事件(好比HOME键)500ms
,重复的间隔是50ms
。WMS
会将当前的全部窗口和窗口信息传给InputDispatcher
,以供InputDispatcher
寻找派发窗口, 找到当前能够接收事件的窗口(好比key事件寻找focus的窗口,motion事件寻找包含这个事件坐标的窗口) 将事件派发给窗口。InputChannel
支持跨进程传输。socketpair
的FD
,App进程持有一端,WMS
进程持有一端。InputChannel
负责事件最终的读写。InputChannel
,负责将InputChannel
的FD
加入到main looper并负责读写InputChannel
。ViewRootImpl
。ANR
InputDispatcher
根据事件找到目标窗口好要看目标窗口是否可以接受事件,能不可以接受事件是根据目标窗口如今有没有正在派发的事件,若是有,本次不派发,记录一个ANR
起始时间,并将这个待派发的事件挂起,若是后续又有新事件入列致使派发线程被唤醒,再次派发刚才挂起的事件,一样检查当前窗口是否能接受事件并更新ANR
时间,当ANR
时间达到5s
,通知IMS
处理ANR
事件。(若是新加入一个属于其余窗口的事件或者HOME键按下事件,会将当前窗口等待派发的事件都丢弃掉,除了已经加入到窗口本身的派发队列中的事件,这些时间会在其前面等待响应的事件响应后挨个发给窗口)
对于key
事件和motion
事件有所区别,key
事件对于同一个窗口必须是发完一个,下一个事件要从新查找窗口再派发,由于有可能上一个事件会致使焦点窗口改变,好比遥控器,用户点了一个按钮,弹出一个弹窗,而下一个事件应该发给新的弹窗,用户的预期是一个按键处理完了再处理另外一个按键。
而motion
事件,若是当前窗口有正在处理的事件,后续事件只要是在第一个未响应事件发出的0.5s
以内发生都仍是加入到这个窗口本身的派发队列,等前面的事件派发完了接着将队列中其余事件派发掉,若是超过0.5s
则不加入当前窗口派发队列,而是等待下一次派发周期从新查找窗口,并记录ANR
起始时间。这样的作法是考虑到motion
事件通常不考虑焦点,用户当前看到的是哪一个window
,预期时间就应该给哪一个window
,即使正在处理的事件会致使window
切换,只要仍是用户如今看到的window
,0.5s
内的事件都仍是给这个window
。超过0.5s
的事件就依照新查找到的窗口而定。
ANR
是个dialog
,是AMS
弹的,事件的源头是InputDispatcher
,通过InputDispatcher -> NativeInputManager -> InputManagerService -> AMS
事件注入能够协助咱们实现UI自动化测试,Android上可使用如下几种方法进行事件注入。
1,使用Instrumentation
类,调用其好比sendPointerSync
方法。
2,使用adb getevent/putevent
命令行工具
3,经过IMS
的injectInputEvent
方法(App进程能够经过InputManager
访问IMS
),因为injectInputEvent
是api hide
的方法,所以只能经过hack的方式访问。
ANR
时AMS
会发出一个android.intent.action.ANR
广播,经过监听这个广播能够收集App的ANR
事件并上报给服务端。HOME
键在派发前会被IMS
拦截,由于IMS
是InputDispatcher
的派发前polocy
,IMS
会将事件转交给PhoneWindowManager
,由PhoneWindowManager
启动HOME
桌面并消费掉事件。