理解输入系统和IMS:

目录

输入事件传递流程的组成部分: 

1.输入系统部分:

2.WMS处理部分:

3.View处理部分:

IMS对输入事件的处理:

1.IMS的诞生:

1.1:SystemServer处理部分:

1.2:IMS的构造方法:

2.IMS的启动过程:

3.InputDispatcher的启动过程:

4.InputReader处理事件的过程:

5.输入事件的处理总结:

6.IMS的架构图:

补充--IMS对输入事件的处理:

1.InputReader的加工类型:

2.InputDispatcher的分发过程:

2.1:唤醒InputDispatcherThread:

2.2:InputDispatcher进行分发:

3.事件分发到目标窗口的过程:

4.Motion事件分发过程总结:


输入系统的核心是InputManagerService,而View的事件分发也只能算是输入系统事件传递的一部分。

输入事件传递流程的组成部分: 

输入系统是外界与Android设备交互的基础,仅仅凭借输入系统是无法完成输入系统的事件传递的,因此就需要输入系统和Android系统的其他成员来共同实现输入事件的传递。输入系统的事件传递如图所示:

输入事件的传递流程大致可以分为三个部分,分别是输入系统部分,WMS处理部分和View处理部分。接下来简单介绍一下:

1.输入系统部分:

输入系统部分主要由输入子系统和InputManagerService(简称为:IMS),在Android中还有一个简称为IMS的(IP Multimedia Subsystem),意思是IP多媒体子系统。Android的输入设备有很多种,比如触摸屏,键盘等等。当输入设备可用时,Linux内核会在/dev/input中创建对应的设备节点。用户操作这些输入设备时会产生各种事件。比如:按键事件,触摸事件和鼠标事件。输入事件的产生的原始信息会被Linux内核中的输入子系统采集,原始信息由Kernel space的驱动层一直传递到User space的设备节点。Android提供了getevent和sendevent两个工具帮助开发者从设备节点读取输入事件和写入输入事件。

IMS的工作就是监听/dev/input下的所有设备节点,当一个设备节点有数据时会将数据进行加工处理并且找到合适的窗口,将输入事件派发给它。

2.WMS处理部分:

WMS的职责之一就是输入系统的中转站,WMS作为窗口的管理者,会配合IMS将输入事件交给合适的窗口来处理。

3.View处理部分:

在一般情况下,输入事件最终是由View处理的,应用开发人员可以通过一些回调分发轻松的得到这个事件的封装类并且对其进行处理。

IMS对输入事件的处理:

1.IMS的诞生:

在IMS对输入事件的处理之前,先了解一下IMS的诞生。

1.1:SystemServer处理部分:

与AMS,WMS,PMS一样,IMS也是在SystemServer进程中创建的,SystemServer进程用来创建系统服务。在SystemServer的main方法中调用了SystemServer的run方法,该方法有一个startCoreServices方法用于启动其他服务,而IMS就是属于其他服务。在startCoreServices方法中关于IMS的有:创建了IMS的实例,再把IMS的实例传入WMS的main方法中,然后将WMS和IMS都添加到ServiceManager中进行统一的管理,之后还包括IMS的启动。

1.2:IMS的构造方法:

接着看IMS的构造方法:首先创建了InputManagerHandler,并且传入DisplayThread.get().getLooper方法,这是为了在android.display线程的looper创建InputManagerHandler,这样InputManagerHandler就会运行在android.display线程中,该线程是系统共享的单例前台线程。接着调用了nativeInit方法,很明显是通过JNI调用native方法。该方法在:frameworks/base/services/core/com_android_server_input_InputManagerService.cpp中。它主要做了,创建一个NativeInputManager,再调用reinterpretcast运算符将NativeInputManager指针强制转换并且返回(重新解释比特位)。

NativeInputManager的构造方法中创建了EventHubInputManager,其中EventHub通过Linux内核的INotify与Epoll机制监听设备的节点,通过EventHub的getEvent函数读取设备节点的增删事件和原始的输入事件。而InputManager的构造函数中创建了InputReader和InputDispatcher,其中InputReader会不断的循环读取EventHub中的原始输入事件,将这些原始输入事件进行加工之后交给InputDispatcher处理,而InputDispatcher中保存了WMS的所有窗口的信息(WMS会将窗口信息实时处理并且更新到InputDispatcher中),这样InputDispatcher就可以将输入事件派发给合适的窗口。InputDispatcher和InputReader都是耗时操作,因此在initialize函数中创建了供它们运行的InputDispatcherThread线程和InputReaderThread线程。

frameworks/base/services/java/com/android/input/InputManagerService.java。

2.IMS的启动过程:

IMS在startCoreServices方法中被创建之后,调用IMS的start方法进行启动。在start方法中调用了Watchdog.getInstance().addMonitor方法将IMS添加到Watchdog中进行监控,用于定时检测系统的关键服务是否可能会发生死锁。接着调用了nativeStart方法,这也是通过JNI调用native方法,nativeStart方法对应的JNI层的函数是frameworks/base/services/core/com_android_server_input_InputManagerService.cpp下的nativeStart函数,该函数首先用reinterpret_cast操作符把jlong类型的ptr强制转换为NativeInputManager指针类型,再调用InputManager的start函数。InputManager的start函数运行了InputDispatcherThread线程和InputReaderThread线程。

3.InputDispatcher的启动过程:

我们知道InputManager的构造函数中创建了InputReader和InputDispatcher。在frameworks/native/services/inputflinger/InputDispacher.h中定义了它。首先它定义了一个threadLoop的纯虚函数,而InputDispatcherThread继承了Thread,native的Thread内部有一个循环,当线程运行时,会调用threadLoop函数,如果它返回true并且没有调用requestExit函数,那么就会接着调用threadLoop函数。

threadLoop函数的实现:threadLoop函数中只调用了InputDispatcher的dispatchOnce函数,该函数首先调用haveCommandLocked函数检查InputDispatcher的缓存队列中是否有等待的命令,如果没有就执行dispatchOnceInnerLocked函数,它用来将输入事件分发给合适的窗口。然后调用now函数获取当前的时间,再调用toMillisecondTimeoutDelay得到InputDispatcherThread需要睡眠的时间,最后调用Looper的pollOnce函数将InputDispatcherThread进入睡眠状态,当有输入事件产生之后,InputReader就会将睡眠状态的InputDispatcher唤醒,继续开始分发输入事件。

4.InputReader处理事件的过程:

InputReader是InputReaderThread中启动的,InputReaderThread和InputDispatcher的定义是类似的,也是继承了Thread并且定义了threadLoop纯虚函数。threadLoop函数中只调用了InputReader的loopOnce函数,该函数首先调用了EventHub的getEvents函数来获取设备节点的事件信息并且存放在mEventBuffer中,而事件的信息主要有两种,一种是设备节点的增删事件,另一种是原始的输入事件。接着processEventsLocked函数对mEventBuffer的输入事件信息进行加工处理。

processEventsLocked函数首先遍历了所有的事件,这些事件使用RawEvent对象来表示,将原始的输入事件和设备事件进行分开处理,其中设备事件分为DEVICE_ADDED(设备添加事件),DEVICE_REMOVED(设备删除事件)和FINISHED_DEVICE_SCAN(已完成设备事件),这些事件是在EventHub的getEvent函数中生成的,如果是DEVICE_ADDED事件,则InputReader会新建InputDevice对象,用来存储设备的信息,并且将InputDevice存储在KeyedVector类型的容器mDevices中。如果是DEVICE_REMOVED则调用removeDeviceLocked函数进行处理。如果是FINISHED_DEVICE_SCAN则调用handleConfigurationChangedLocked函数进行处理。原始输入事件则调用processEventsForDeviceLocked函数进行处理。

processEventsForDeviceLocked函数会根据deviceId调用mDevices的indexOfKey函数获取对应的deviceIndex,接着再调用mDevices的valueAt函数传入deviceIndex获取对应的InputDevice,最后调用InputDevice的process函数,该函数首先遍历InputDevice中的所有事件,然后遍历所有的InputMapper,并且通过InputMapper的process函数传入InputDevice的所有事件。至于是哪一个InputMapper处理的,InputReader也不关心。而真正加工原始的输入事件的是InputMapper对象,由于原始的输入事件很多,因此InputMapper也有很多的子类,用不同的子类来加工不同的原始输入事件,比如:键盘输入事件是KeyboardInputMapper处理的,触摸输入事件是TouchInputMapper处理的。

以键盘输入事件为例子:KeyboardInputMapper的process函数首先判断事件的类型,如果是按键类型,则调用KeyboardInputMapper的processKey函数,该函数将加工之后的键盘输入事件封装为NotifyKeyArgs,再将NotifyKeyArgs通过notifyKey函数通知给InputListenerInterface。其中InputDispatcher继承了InputListenerInterface,而InputListenerInterface继承了InputListenerInterface。因此notifyKey函数实际上调用的是InputDispatcher的notifyKey函数,也就是把NotifyKeyArgs交给InputDispatcher处理。InputDispatcher的notifyKey函数,它根据传入的NotifyKeyArgs重新封装了一个KeyEntry对象,它代表一次的按键数据,再调用enqueueInboundEventLocked方法传入这个KeyEntry对象,根据它来判断是否需要将睡眠中的InputDispatcherThread唤醒,如果需要唤醒则调用Looper的wake函数进行唤醒,InputDispatcherThread唤醒之后会重新对输入的事件进行分发。

5.输入事件的处理总结:

输入事件的处理涉及了四个关键的类,分别是IMS、Evenfub、InputDispatcher 和InputReader,它们做了如下工作:

  1. IMS 启动了InputDispatcherThread 和InputReaderThread,它们分别用来运行InputDispatcher和 InputReader。
  2.  InputDispatcher 先于InputReader被创建,InputDispatcher的 dispatchOnceInnerLocked函数用来将事件分发给合适的窗口。InputDispatcher没有输入事件处理时会进入睡眠状态,等待InputReader来通知唤醒。
  3. InputReader 通过 EventHub的 getEvents函数获取事件信息,如果是原始输入事件,就将这些原始输入事件交由不同的InputMapper来处理,最终交由InputDispatcher来进行分发。
  4. 在InputDispatcher的notifyKey函数中会根据按键数据来判断 InputDispatcher是否要被唤醒,InputDispatcher 被唤醒后,会重新调用dispatchOncelnnerLocked函数将输入事件分发给合适的窗口。

6.IMS的架构图:

补充--IMS对输入事件的处理:

frameworks/native/services/inputflinger/InputDispatcher.cpp

frrameworks/native/services/inputflinger/InputReader.cpp

1.InputReader的加工类型:

我们知道InputReader会对原始的输入事件进行加工,而NotifyKeyArgs是InputReader通过加工按键类型的事件得到的。NotifyKeyArgs结构体继承自NotifyArgs结构体,其中NotifyArgs有三个子类,分别是NotifyKeyArgs,NotifyMotionArgs和NotifySwitchArgs。InputReader在对原始的输入事件进行加工之后,最终得到这三种事件的类型,分别是Key事件,Motion事件和Switch事件,将这些事件交给InputDispatcher进行分发。

2.InputDispatcher的分发过程:

不同的事件类型有不同的分发过程,其中Switch事件的处理是没有分发过程的,在InputDispatcher的notifySwitch函数中会将switch事件交给InputDispatcherPolicy来处理。而对于Motion事件来说,它的过程如下所示:

2.1:唤醒InputDispatcherThread:

InPutDispatcher的notifyMotion函数用于唤醒InputDispatcherThread。notifyMotion函数主要做了:检查Motion事件的参数是否有效,其内部会检查触控点的数量pointerCount是否在合理的范围内(小于1或者大于16都是不合理的),以及触控点的ID是否在合理范围内(小于0或者大于31都是不合理的)。接着调用shouldSendMotionToInputFilterLocked函数传入Motion事件进行InputFilter的过滤,其作用是初始化MotionEvent,也就是用NotifyMotionArgs中的事件参数信息赋值给MotionEvent中的参数。然后MotionEvent会通过filterInputEvent函数进行过滤,如果返回值为false,则这次的Motion事件会被忽略,也就是不被分发。接着初始化一个MotionEvent,然后调用enqueueInboundEventLocked函数传入MotionEvent,该函数内部会将MotionEvent添加到InputDispatcher的mInboundQueue队列的末尾,并且返回一个值needWake,代表InputDispatcherThread是否需要被唤醒,如果需要被唤醒就调用wake函数来唤醒InputDispatcherThread。

2.2:InputDispatcher进行分发:

InputDispatcherThread被唤醒之后,会执行InputDispatcherThread的threadLoop函数,该函数只调用了InputDispatcher的dispatchOnce函数

dispatchOnce函数:首先调用haveCommandLocked函数检查InputDispatcher的缓存队列中是否有等待的命令,如果没有就执行dispatchOnceInnerLocked函数,它用来将输入事件分发给合适的窗口。然后调用now函数获取当前的时间,再调用toMillisecondTimeoutDelay得到InputDispatcherThread需要睡眠的时间,最后调用Looper的pollOnce函数将InputDispatcherThread进入睡眠状态,当有输入事件产生之后,InputReader就会将睡眠状态的InputDispatcher唤醒,继续开始分发输入事件。

我们看dispatchOnceInnerLocked函数它主要相关的有:

  1. 首先判断InputDispatcher是否被冻结,如果被冻结了,则不进行分发的操作。其中InputDispatcher有三种状态,分别是正常状态,冻结状态和禁用状态。
  2. 接着没有冻结的话,则进行下面的操作。接着判断isAppSwitchDue的值,该值通过mAppSwitchDueTime<=currentTime来判断,其中前者是APP最近发生窗口切换操作时,该操作事件最迟的分发时间,后者是当前的系统时间。如果为true的话,则说明没有及时响应窗口的切换操作。
  3. 然后,如果mAppSwitchDueTime小于nextWakeupTime(下一次InputDispatcherThread醒来的时间),就会将mAppSwitchDueTime赋值给nextWakeupTime,这样当InputDispatcher处理为分发事件之后,就会第一时间处理窗口切换操作。
  4. 如果没有待分发的事件,就从mInboundQueue中取出一个事件,如果没有待分发的事件,就调用return。如果不为空,则取队列头部的EventEntry赋值给mPendingEvent,mPendingEvent的类型为EventEntry对象指针。
  5. 根据mPendingEvent的type进行区分处理,这里主要截取了对Motion类型的处理。经过过滤会调用dispatchMotionLocked函数为这个事件寻找合适的窗口,如果寻找分发成功的话,则调用releasePendingEventLocked函数,其内部会将mPendingEvent的值设置为null,并且将mPendingEvent指向的对象内存释放。
  6. 将nextWakeupTime的值设置为LONG_LONG_MIN,这是为了使InputDispatcher能够快速处理下一个分发事件。

3.事件分发到目标窗口的过程:

上面的dispatchMotionLocked函数为Motion事件寻找合适的窗口,如果if(*dropReason!=DROP_REASON_NOT_DROPPED)返回为true的话,就说明事件是需要丢弃的,就不会为该事件寻找窗口,而这次的分发任务就没有完成,就会在下一次InputDispatchThread的循环中再次尝试分发。接着判断isPointerEvent的值,根据该值分别对触摸形式和非触摸形式的事件进行处理,比如触摸事件调用的是InputDispatcher的findTouchedWindowTargetsLocked函数。然后将事件处理的结果交给injectionResult处理。后面会判断injectionResult的值,如果该值为INPUT_EVENT_INJECTION_PENDING,则说明找到了窗口并且窗口无响应,这时候输入事件就会被挂起,就会返回false;如果该值不为INPUT_EVENT_INJECTION_SUCCEEDED,则说明没有找到合适的窗口,输入事件没有分发成功,这时候就会返回true。addMonitoringTargetsLocked函数会将分发的目标添加到inputTargets列表中,最终dispatchEventLocked函数将事件分发给inputTargets列表中的目标。

inputTargets列表中存储的是InputTarget结构体,它是InputDispatcher与目标窗口的转换器,其分为两个部分,一部分是枚举中存储的InputDispatcher与目标窗口交互的标记,另一部分是InputDispatcher与目标窗口交互的参数。

4.Motion事件分发过程总结:

  1. Motion 事件在 InputReaderThread线程的 InputReader中进行加工,加工完毕后会判断是否要唤醒InputDispatcherThread,如果需要唤醒,则会在 InputDispaticherThread的线程循环中不断地用InputDispatcher来分发Motion事件。
  2. 将Motion 事件交由InputFilter过滤,如果返回值为false,则这次Motion事件就会被忽略掉。
  3.  InputReader 对Motion事件加工后的数据结构为NotifyMotionArgs,在InputDispatcher的notifyMotion函数中,用NotifyMotionArgs中的事件参数信息构造一个MotionEntry对象,这个MotionEntry对象会被添加到InputDispatcher的mInboundQueue队列的末尾。
  4. 如果mInboundQueue不为空,则取出mInboundQueue队列头部的 EventEntry,并且将其赋值给mPendingEvent。
  5. 根据mPendingEvent的值进行事件丢弃处理。
  6. 调用 InputDispatcher的findTouchedWindowTargetsLocked函数,在mWindowHandles列表中为Motion事件找到目标窗口,并为该窗口生成InputTarget。
  7. 根据 InputTarget 获取一个 Connection,依赖 Connection 将输入事件发送给目标窗口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值