理解输入系统和IMS
一.输入事件传递流程的组成部分
需要输入系统和Android系统的其他成员来共同实现输入事件的传递,如下所示:
输入事件流程分为三部分:输入系统部分、WMS处理部分、View处理部分
1.输入系统
输入系统主要分为输入子系统和InputManagerService(IMS)。
输入事件产生的原始信息会被Linux内核中的输入子系统采集,原始信息由Kernel space的驱动层一直传递到User space的设备节点。
Android提供了getevent和sendevent两个工具帮助开发者从设备节点读取输入事件和写入输入事件。
输入系统部分如下图:
IMS所做的工作就是监听/dev/input下的所有设备节点,当设备节点有数据时会将数据进行加工处理并找到合适的窗口,将输入事件派发给它。
2.WMS处理部分
WMS的职责之一就是输入系统的中转站,WMS作为窗口的管理者,会配合IMS将输入事件交由合适的窗口来处理。
3.View处理部分
输入事件一般情况下最终会交由View来处理。
二.IMS的诞生
1.SystemServer处理部分
在其他服务中先创建IMS,然后创建WMS,需要注意的是创建WMS过程中有个参数就是IMS,WMS是输入系统的中转站,其内部包含了IMS的引用。最后WMS和IMS添加到ServiceManager中统一管理。
2.InputManagerService构造方法
InputManagerService的构造方法--------->InputManagerService.cpp层的nativeInit------>NativeInputManager的构造方法------->InputManager的构造方法------>创建InputReader和InputDispatcher
InputManagerService构造方法: 创建android.display线程的Looper的InputManagerHandler,android.display线程是系统共享的单例前台线程,这个线程内部执行了WMS的创建。然后调用nativeInit方法是通过jni调用到c层。
nativeInit:调用NativeInputManager的构造方法。
NativeInputManager构造方法:创建EventHub和InputManager,EventHub通过Linux内核的INotify与Epoll机制监听设备节点,通过EventHub的getEvent函数读取设备节点的增删事件和原始输入事件。
InputReader:会不断循环读取EventHub中的原始输入事件,将这些原始输入事件交由InputDispatcher处理,InputDispatcher保存了WMS的所有窗口信息,InputDispatcher可以将输入事件派发给合适的窗口。InputReader和InputDispatcher都是耗时操作,因此在initialize函数中创建InputReaderThread和InputDispatcherThread线程供他们使用。
3.IMS的启动过程
SystemServer的startOtherServices方法------->inputManager的start方法------->InputManagerService.cpp的nativeStart方法------>启动InputReaderThread和InputDispatcherThread线程
inputManager的start方法:将自身添加到Watchdog中进行监控。动态注册对应nativeStart方法。
4.InputDispatcher的启动过程
InputManager.cpp的构造方法------->创建InputDispatcher和InputReader-------->threadLoop------>dispatchOnce
创建InputDispatcher:InputDispatcherThread继承Thread。native的Thread内部有一个循环,当线程运行时,会调用threadLoop函数。
dispatchOnce:用来检查InputDispatcher的缓存队列中是否有等待处理的命令,如果没有就执行dispatchOnceInnerLocked函数它用来将输入事件分发给合适的窗口。最后调用Looper的pollOnce函数使InputDispatcherThread进入睡眠状态。当有输入事件产生时,InputReader就会将睡眠状态的InputDispatcher唤醒,InputDispatcher会重新开始分发事件。
5.InputReader处理事件的过程
InputReaderThread的threadLoop-------->InputReader的loopOnce------>processEventsLocked------>processEventsForDeviceLocked------>KeyboardInputMapper的process------>processKey------>InputDispatcher的notifyKey
InputReaderThread的threadLoop:InputReader是在InputReaderThread中启动的,InputReaderThread和InputDispatcherThread的定义也是类似的,也继承了Thread并定义了threadLoop纯虚函数。
loopOnce:调用getEvent函数来获取设备节点的事件信息。事件信息主要有两种:一种是设备节点的增删事件(设备事件),另一种是原始输入事件,processEventsLocked函数用于对原始输入信息进行加工处理,加工处理后的输入事件交由InputDispatcher来处理。
processEventsLocked:遍历所有事件,将原始事件和设备事件分开处理,同一个设备的输入事件交由processEventsForDeviceLocked函数来处理。
KeyboardInputMapper的process:真正加工原始输入事件的是InputMapper对象,InputMapper有很多子类用于加工不同的原始输入事件,比如:KeyboardInputMapper用于处理键盘输入事件,TouchInputMapper用于处理触摸事件。
processKey:将事件封装成NotifyKeyArgs通知给InputDispatcher,交由InputDispatcher处理。
notifyKey:是否需要唤醒InputDispatcherThread线程,然后对事件输入进行分发。
6.输入事件的处理总结
分别涉及四个关键的类:IMS、EventHub、InputDispatcher和InputReader
1.IMS启动了InputDispatcherThread和InputReaderThread,他们分别用来运行InputDispatcher和InputReader.
2.InputDispatcher先于InputReader创建,InputDispatcher的dispatchOnceInnerLocked函数用来将事件分发给合适的窗口。InputDispatcher没有输入事件处理时会进入休眠状态,等待InputReader来唤醒。
3.InputReader通过EventHub的getEvent函数来获取事件信息,如果是原始输入事件,就将这些原始输入事件交由不同的InputMapper来处理,最终交由InputDispatcher来处理。
4.InputDispatcher的notifyKey函数中会根据按键数据来判断InputDispatcher是否要被唤醒,InputDispatcher被唤醒后,会重新调用dispatchOnceInnerLocked函数将输入事件分发给合适窗口。
7.InputReader的加工类型
InputDispatcher的notifyKey方法用于唤醒InputDispatcherThread,它的参数是NotifyKeyArgs是InputReader对按键类型事件加工后得到的。
NotifyKeyArgs结构体继承自NotifyArgs结构,NotifyArgs有三个子类,分别是NotifyKeyArgs、NotifyMotionArgs、NotifySwitchArgs。
InputReader对原始输入事件进行加工后,最终得出三种事件类型,分别是key事件,motion事件,swtich事件。将这些事件交由InputDispatcher进行分发。
8.InputDispatcher的分发过程
以下是对Motion事件分发的过程。
InputDispatcher的notifyMotion唤醒InputDispatcherThread------->执行InputDispatcherThread的threadLoop------>执行InputDispatcher的dispatchOnce------->dispatchOnceInnerLocked
dispatchOnce:设置休眠时间,设置是否休眠,调用dispatchOnceInnerLocked进行时间分发
dispatchOnceInnerLocked:如果InputDispatcher是冻结状态则不进行分发,进行窗口切换操作,当InputDispatcher处理分发完事件后,第一时间来处理窗口切换操作,取出事件,如果没有待分发事件,则再队列中取出一个事件,事件丢弃,根据事件丢弃原因进行区分处理,后续处理,进行内存释放,为InputDispatcher能够快速处理下一个事件进行准备。
9.事件分发到目标窗口的过程
InputDispatcher的dispatchOnceInnerLocked函数------->dispatchMotionLocked------->dispatchEventLocked------->prepareDispatcherCycleLocked------->inputTarget的inputChannel和窗口进行进程间通讯,发送给目标窗口
dispatchOnceInnerLocked:事件丢弃和调用dispatchMotionLocked函数为Motion事件寻找合适窗口。
dispatchMotionLocked:如果事件需要丢弃则直接返回true,不会为该事件寻找窗口,这次分发任务就没有完成,会在下一次InputDispatcherThread的循环中再次尝试分发。对触摸事件findTouchedWindowTargetsLocked处理和非触摸事件分别处理。将事件分发到inputTargets列表中。inputTargets里面存储的是结构体,最终将事件分发给inputTargets列表中的目标。
findTouchedWindowTargetsLocked:先获取MotionEntry中坐标点,以便后面筛选窗口使用,获取mWindowHandles的InputWindowHandle数量,InputWindowHandle中保存了InputWindowInfo,InputWindowInfo中又包含了WindowManager.LayoutParams定义的窗口标志,除了窗口标志,InputWindowInfo中还包含InputChannel和窗口的各种属性,InputWindowInfo描述了可以接收输入事件的窗口属性。这样看来,InputWindowHandle和WMS中的WindowState很相似。通俗的说,WindowState用来代表WMS中的窗口,而InputWindowHandle用来代表系统中的窗口。
遍历mWindowHandles列表中的窗口,找到触摸过的窗口和窗口之外的外部目标。如果窗口是focusable或者flag不为FLAG_NOT_FOCUSABLE,则说明窗口是可触摸的,如果窗口是可触摸的或者坐标点在窗口之上,则会将windowHandle赋值给newTouchedWindowHandle。最后会将windowHandle赋值给newTouchedWindowHandle,并添加到TempTouchState中。
本文地址:https://blog.csdn.net/gongjdde/article/details/110357620
推荐阅读
-
window系统mysql无法输入和无法显示中文的解决方法
-
linux系统中InputStream输入流的方法之reset()和mark()命令的注意事项
-
Linux系统设置RAID 10,确保高性能和容错的磁盘输入/输出的图文教程
-
巧用U盘进入设密码系统免于输入用户名和登录密码
-
Ubuntu系统安装sublime和设置中文输入教程
-
《深入理解计算机系统》阅读笔记--信息的表示和处理(上)
-
Linux输入子系统框架原理解析
-
详解linux系统输入输出管理和vim的常用功能
-
Eclipse中输入系统变量和运行参数
-
linux系统中InputStream输入流的方法之reset()和mark()命令的注意事项