Android Input事件机制
Android系统是由事件驱动的,而Input是最常见的事件之一,用户的点击、滑动、长按等操作,都属于Input事件驱动,其中的核心就是InputReader和InputDispatcher。InputReader和InputDispatcher是跑在system_server进程中的两个Native循环线程,负责读取和分发Input事件。整体处理流程大致如下:
触摸屏会按照屏幕硬件的触控采样率周期,每隔几秒扫描一次,如果有触控时间就会上报到对应设备驱动;系统封装了一个叫EventHub的对象,它利用INotify和epoll机制监听/dev/input目录下的Input设备驱动节点,通过EventHug的getEvents接口就可以监听并获取到Input事件;
InputReader负责从EventHub里面把Input事件读取出来,然后交给InputDispatcher进行事件分发;
InputDispatcher在拿到InputReader获取的事件之后,对事件进行包装后,寻找并分发到目标窗口;
InboundQueue队列("iq")中放着InputDispatcher从InputReader中拿到的Input事件;
OutboundQueue队列("oq")里面放的是即将要指派给各个目标窗口App的事件;
WaitQueue队列("wq")里面记录的是已经派发给App,但是App还在处理没有返回处理成功的事件;
PendingInputEventQueue("aq")中记录的是应用需要处理的Input事件,这里可以看到Input事件已经传递到应用进程;
deliverInputEvent标识App UI Thread被Input事件唤醒;
InputResponse标识Input事件区域,这里可以看到一个Input_Down事件 + 若干个Input_Move + 一个Input_Up事件的处理阶段都被算到了这里;
App响应处理Input事件,内部会在其界面View数中逐层分发和处理。
结合Systrace分析
从上面的系统机制的分析可以看出,整个Input触控时间的分发与处理主要涉及到两个进程:一个是system_server系统进程,另一个是当前焦点窗口所属的Setting应用进程。
system_server进程的处理过程
-
当用户手指在Setting应用界面滑动时,系统system_server进程中的Native线程InputReader会从EventHub中读取,利用Linux的epoll机制监听到的屏幕驱动上报的Input触控事件,然后唤醒另一条Native线程InputDIspatcher负责进行事件的进一步分发处理。
-
InputDispatcher被唤醒后会先将事件放到InboundQueue队列("iq")中,然后找到具体处理此Input事件的应用目标窗口,并将Input事件放入对应的应用目标窗口的OutboundQueue队列("oq")中,等待进一步通过SocketPair双工信道发送Input事件到应用目标窗口中。
-
最后当事件发送给具体的应用目标窗口后,会将事件移动到WaitQueue队列("wq")中,一直等待收到目标应用处理Input事件完成后反馈后再从队列中移除,如果5秒钟没有收到目标应用窗口处理完成此次Input事件的反馈,就会报该应该ANR异常事件。以上整个过程在Android系统AOSP源码中都加有相应的Systrace tag,如下图Systrace所示:
应用进程的处理过程
当Input触控时间通过socket传递到Setting应用进程这边后,会唤醒应用的UI线程在ViewRootImpl#deliverInputEvent的流程中进行Input事件的具体分发与处理。具体的处理流程:
先交给之前在添加应用PhoneWindow窗口时的ViewRootImpl#setView流程中创建的多个不同类型的InputUsage中依次进行处理(比如对输入法处理逻辑的封装ImeInputUsage,某些key类型的Input事件会由它先交给输入法进程处理完成后再交给应用窗口的InputUsage处理),整个处理流程是按照责任链的设计模式进行;
然后会交给负责应用窗口Input事件分发处理的ViewPostImeInputUsage中具体处理,这里面会从View布局树的根节点DecorView开始遍历整个View树上的每一个子View或ViewGroup控件执行事件的分发、拦截、处理的逻辑;
最后触控时间处理完成后会调用finishInputEvent结束应用对触控事件处理逻辑,这里面会通过JNI调用到Native层InputConsumer的sendFinishedSignal函数中通过socket消息通知系统框架中的InputDIspatcher该Input事件处理完成,触发从"wq"队列中及时移除待处理事件以免报ANR异常。
一次滑动过程的触控交互的InputResponse区域中一般会包含一个Input的ACTION_DOWN事件 + 多个ACTION_MOVE事件 + 一个ACTION_UP事件,Setting应用界面中的相关View控件在收到多个ACTION_MOVE触控事件后,经过判断用户手指滑动行为,一般会调用View#invalidate等相关接口触发UI线程的绘制上帧更新画面的操作。
Android Input初始化及InputReader流程
概述
当输入设备可用时,Linux内核会在/dev/input/下创建对应名为event0~n或其他名称的设备节点。而当输入设备不可用时,则会将对应的节点删除。
Android输入系统的工作原理概括来说,就是监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后再所有的窗户中寻找合适的事件接收者,并派发给它。
getevent与sendevent
getevent与sendevent两个工具可以从设备节点中直接读取输入事件或写入输入事件。
Getevent
由于getevent不会对事件数据做任何加工,因此其输出的内容是由内核提供的最原始的事件,输出是十六进制的:
adb shell getevent [-选项] [device_path]。
adb shell getevent –t 查看当前按下按键的值,值0x01表示按下,0x00则表示抬起。
按下返回
[1556162527.777123] /dev/input/event6: 0001 009e 00000001
[1556162527.777123] /dev/input/event6: 0000 0000 00000000
送开返回
[1556162530.504152] /dev/input/event6: 0003 0030 00000000
[1556162530.504152] /dev/input/event6: 0003 0032 00000000
[1556162530.504152] /dev/input/event6: 0000 0002 00000000
[1556162530.504152] /dev/input/event6: 0000 0000 00000000
[1556162530.525527] /dev/input/event6: 0001 009e 00000000
[1556162530.525527] /dev/input/event6: 0000 0000 00000000
事件类型(0001),事件代码(009e)以及事件的值(00000001)。
Sendevent
实现模拟用户输入的功能,sendevent的参数为十进制。
sendevent <节点路径> <类型><代码> <值>
adb shell sendevent /dev/input/event0 1 116 1 #按下电源键
adb shell sendevent /dev/input/event0 1 116 0 #抬起电源键
adb shell input keyevent 4 # 返回按键
Android系统Input事件处理流程
内核将原始事件写入到设备节点中,InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDIspatcher。InputDIspatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件做出响应,更新自己的画面、执行特定的动作。
Linux内核,接收输入设备的中断,并将原始事件的数据写入到设备节点中。
设备节点,作为内核的IMS的桥梁,它将原始事件的数据暴露给用户控件,以便IMS可以从中读取事件。
InputManagerService,一个Android系统服务,它分为Java层和Native层两个部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDIspatcher两个输入系统关键组件的运行容器。
EventHub,直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。
InputReader,是IMS中的关键组件之一。它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvent()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表于配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件,然后交给InputDIspatcher进行派发。
InputReaderPolicy,它为InputReader的事件加工处理提供一些策略配置,例如键盘布局信息等。
InputDispatcher,是IMS中另一个关键组件。它也运行于一个独立的线程中。InputDIspatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。
InputDIspatcherPolicy,它为InputDIspatcher的派发过程提供策略控制。例如截取某些特定的输入事件来作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDIspatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件。
WMS,虽然不是输入系统中的一员,但是它却对InputDIspatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDIspatcher中,使得InputDIspatcher可以正确的将事件派发到指定的窗口。
ViewRootImpl,对于某些窗口,如壁纸窗口、SurfaceView的窗口来说,窗口即是输入事件派发的终点。而对于其他的如Activity、对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件(View)。ViewRootImpl将窗口所收到的输入事件沿着控件树将事件派发给感兴趣的控件。
初始化
InputManagerService初始化
这段代码是Android系统中InputManagerService的启动过程。首先,在SystemServer服务的
startOtherServices()
方法中,创建并启动了InputManagerService实例。然后,在InputManagerService的构造函数中,初始化了Native层和Java层的通信,并将Java层的InputManagerService对象和消息队列的Looper传递给Native层。最后,在nativeInit方法中,创建了一个NativeInputManager对象,这个对象是Java层和Native层之间的桥梁,并将这个对象的指针返回给Java层的InputManagerService,保存在mPtr成员变量中。具体来说,
startOtherServices()
方法中:
创建了InputManagerService实例。
调用了InputManagerService的
setWindowManagerCallbacks()
方法,将WindowManager的回调传递给InputManagerService。调用了InputManagerService的
start()
方法,启动了InputManagerService。在
InputManagerService
的构造函数中:
保存了上下文信息。
创建了一个名为
InputManagerHandler
的Handler,用于处理输入事件。调用了native方法
nativeInit()
,传入了上下文对象、InputManagerService对象和消息队列的Looper。将InputManagerService注册为
InputManagerInternal
服务的本地服务。在
nativeInit()
方法中:
从Java层的消息队列对象中获取了消息队列。
创建了一个
NativeInputManager
对象,这个对象是Java层和Native层之间的桥梁。将
NativeInputManager
对象的指针转换为jlong
类型,并返回给Java层的InputManagerService,保存在mPtr成员变量中。这样,Java层的InputManagerService就可以通过mPtr成员变量与Native层的
NativeInputManager
进行通信了。这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。
* frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
...
traceBeginAndSlog("StartInputManagerService");
inputManager = new InputManagerService(context);
traceEnd();
...
traceBeginAndSlog("StartInputManager");
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
traceEnd();
...
}
* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public InputManagerService(Context context) {
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
...
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
* frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
...
/* 新建了一个NativeInputManager对象,NativeInputManager,
此对象将是Native层组件与Java层IMS进行通信的桥梁 */
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper());
im->incStrong(0);
//返回了NativeInputManager对象的指针给Java层的IMS,IMS将其保存在mPtr成员变量中
return reinterpret_cast<jlong>(im);
}
在nativeInit函数中,将Java层的MessageQueue转换为native层的MessageQueue,然后再取出Looper用于NativeInputManager的初始化。NativeInputManager是Java层与Native层互相通信的桥梁,它实现了InputReaderPolicyInterface与InputDispatcherPolicyInterface两个接口,通过JNI回调Java层的IMS,由它完成决策。
NativeInputManager的初始化
这个过程做了以下事情:
将Java层的InputManagerService转换为native层的InputManagerService存储在mServiceObj中 -创建InputManager。
这段代码是Android系统中InputManager的初始化过程。在InputManager的构造函数中,首先创建了InputDispatcher和InputClassifier对象,这两个对象分别负责分发输入事件和输入事件的分类。然后,创建了InputReader对象,这个对象负责从设备读取输入事件。最后,创建了InputReaderThread和InputDispatcherThread两个线程,分别用于处理输入事件的读取和分发。
具体来说:
InputManager
的构造函数中:
创建了
InputDispatcher
对象,用于分发输入事件。创建了
InputClassifier
对象,用于对输入事件进行分类。调用
createInputReader()
方法创建了InputReader
对象,这个对象负责从设备读取输入事件。调用
initialize()
方法,创建了InputReaderThread
和InputDispatcherThread
两个线程。
createInputReader()
方法中:
创建了
InputReader
对象,这个对象负责从设备读取输入事件。
initialize()
方法中:
创建了
InputReaderThread
对象,这个线程用于处理输入事件的读取。创建了
InputDispatcherThread
对象,这个线程用于处理输入事件的分发。这样,InputManager就可以通过这两个线程来处理输入事件了。
这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。
* frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mClassifier = new InputClassifier(mDispatcher);
mReader = createInputReader(readerPolicy, mClassifier);
initialize();
}
sp<InputReaderInterface> createInputReader(
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) {
return new InputReader(new EventHub(), policy, listener);
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
InputManager的初始化
InputManager的构造函数也比较简洁,它创建了四个对象,分别为IMS的核心参与者InputReader与InputDispatcher,以及它们所在的线程InputReaderThread与InputDispatcherThread。注意InputManager的构造函数的参数readerPolicy与dispatcherPolicy,它们都是NativeInputManager。
InputDispatcher会创建自己线程的Looper,以及设置根据传入的dispatchPolicy设置分发规则。InputReader则会将传入的InputDispatcher封装为监听对象存起来,并创建一个EventHub。
启动
system_server执行InputManagerService.start()函数以启动IMS,在start()方法中,做了以下事情:
调用nativeStart方法,其实就是调用InputManager的start()方法。
将InputManagerService交给WatchDog监控。
注册触控点速度、显示触控的观察者,并注册广播监控它们。
主动调用updateXXX方法更新(初始化)。
InputManager的start()启动InputDispatcherThread和InputReaderThread开始监听。
当两个线程启动后,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由事件处理函数进行事件响应。
这段代码是Android系统中InputManagerService的启动过程。在Java层的
start()
方法中,调用了native方法nativeStart()
,传入了mPtr成员变量,这个变量是Java层和Native层之间的桥梁。在Native层的
InputManager
的start()
方法中,首先启动了InputDispatcherThread
线程,这个线程负责分发输入事件。然后,启动了InputReaderThread
线程,这个线程负责从设备读取输入事件。具体来说:
InputManagerService
的start()
方法中:
调用了native方法
nativeStart()
,传入了mPtr成员变量,这个变量是Java层和Native层之间的桥梁。
InputManager
的start()
方法中:
调用
mDispatcherThread->run()
方法,启动了InputDispatcherThread
线程,这个线程负责分发输入事件。调用
mReaderThread->run()
方法,启动了InputReaderThread
线程,这个线程负责从设备读取输入事件。这样,InputManager就可以通过这两个线程来处理输入事件了。
这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。
* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(mPtr);
...
updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
}
* frameworks/native/services/inputflinger/InputManager.cpp
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
...
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
...
}
InputReaderThread
启动后循环执行mReader->loopOnce(),loopOnce中会调用mEventHub->getEvents读取事件,读取的结果存储在参数mEventBuffer中,返回值表示事件的个数,当EventHub中无事件可以抽取时,此函数的调用将会阻塞直到事件到来或者超时。
读取到了事件就会调用processEventsLocked处理事件,对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则在进行转译、封装与加工后将结果暂存到mQueuedListener中,处理完成后调用getInputDevicesLocked获取输入设备信息。
调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化。
最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDIspatcher。
注意 C++层的Thread类与Java层的Thread类有着一个显著的不同。C++层Thread类内建了线程循环,threadLoop()就是一次循环而已,只要返回值为true,threadLoop()将会不断地被内建的循环调用。这也是InputReader.loopOnce()函数名称的由来。而Java层Thread类的run()函数则是整个线程的全部,一旦其退出,线程也便完结。
这段代码是Android系统中InputReader的循环处理过程。在Native层的
InputReaderThread
的threadLoop()
方法中,调用了InputReader
的loopOnce()
方法,这个方法会循环读取输入事件。在
InputReader
的loopOnce()
方法中,首先从EventHub
中读取输入事件,然后处理这些事件。如果输入设备发生了变化,就通知InputReaderPolicy
。最后,调用mQueuedListener
的flush()
方法,将处理过的输入事件发送给监听者。具体来说:
InputReaderThread
的threadLoop()
方法中:
调用了
mReader
的loopOnce()
方法,这个方法会循环读取输入事件。
InputReader
的loopOnce()
方法中:
从
EventHub
中读取输入事件。处理这些事件。
如果输入设备发生了变化,就通知
InputReaderPolicy
。调用
mQueuedListener
的flush()
方法,将处理过的输入事件发送给监听者。这样,
InputReader
就可以通过这个循环来处理输入事件了。这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。
* frameworks/native/services/inputflinger/InputReaderBase.cpp
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
* frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::loopOnce() {
...
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
if (count) {
processEventsLocked(mEventBuffer, count);
}
...
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
mQueuedListener->flush();
}
EventHub
InputReader在其线程循环中的第一个工作便是从EventHub中读取一批未处理的事件。 EventHub的直译是事件集线器,顾名思义,它将所有的输入事件通过一个接口getEvents()将从多个输入设备节点中读取的事件交给InputReader,是输入系统最底层的一个组件。
这段代码是Android系统中EventHub的初始化过程。在EventHub的构造函数中,首先创建了一个epoll对象和inotify对象,用于监听设备节点的增删事件。然后,将设备节点的路径/dev/input作为监听对象添加到inotify对象中,当此文件夹下的设备节点发生创建与删除事件时,都可以通过inotify对象读取事件的详细信息。接着,将inotify对象和名为wakeFds的匿名管道(用于唤醒InputReader线程)作为epoll的监控对象,并注册到epoll对象中。
具体来说:
EventHub的构造函数中:
创建了一个epoll对象。
创建了一个inotify对象,用于监听设备节点的增删事件。
将设备节点的路径/dev/input作为监听对象添加到inotify对象中。
创建了一个名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。
在epoll对象中注册了inotify对象和wakeFds管道,用于监听设备节点的增删事件和唤醒InputReader线程。
这样,EventHub就可以通过epoll和inotify来监听设备节点的变化,并及时处理这些事件了。
这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。
* frameworks/native/services/inputflinger/EventHub.cpp
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(nullptr), mClosingDevices(nullptr),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
//创建一个epoll对象
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
//创建一个inotify对象。这个inotify对象将被用来监听设备节点的增删事件
mINotifyFd = inotify_init();
//将存储设备节点的路径/dev/input作为监听对象添加到inotify对象中。当此文件夹下的设备节点
//发生创建与删除事件时,都可以通过mINotifyFd读取事件的详细信息
mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
...
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.fd = mINotifyFd;
//将mINotifyFd作为epoll的一个监控对象。当inotify事件到来时,epoll_wait()将
//立刻返回,EventHub便可从mINotifyFd中读取设备节点的增删信息,并作相应处理
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
/*创建了一个名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。
因为InputReader在执行getEvents()时会因无事件而导致其线程阻塞在epoll_wait()的调用里,
然而有时希望能够立刻唤醒InputReader线程使其处理一些请求。
此时只需向wakeFds管道的写入端写入任意数据,此时读取端有数据可读,使得epoll_wait()得以返回,
从而达到唤醒InputReader线程的目的*/
int wakeFds[2];
result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
...
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
...
}
INotify
INotify是一个Linux内核所提供的一个文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的创建、删除、读写等。INotify机制有两个基本对象,分别为inotify对象与watch对象,都使用文件描述符表示。
-
inotify对象对应了一个队列,应用程序可以向inotify对象添加多个监听。当被监听的事件发生时,可以通过read()函数从inotify对象中将事件信息读取出来。Inotify对象可以通过inotify_init创建。
-
watch对象则用来描述文件系统的变化事件的监听。它是一个二元组,包括监听目标和事件掩码两个元素。监听目标是文件系统的一个路径,可以是文件也可以是文件夹。而事件掩码则表示了需要需要监听的事件类型,掩码中的每一位代表一种事件。可以监听的事件种类很多,其中就包括文件的创建(IN_CREATE)与删除(IN_DELETE)。以下代码即可将一个用于监听输入设备节点的创建与删除的watch对象添加到inotify对象中:
int wd = inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE | IN_DELETE);
完成上述watch对象的添加后,当/dev/input/下的设备节点发生创建与删除操作时,都会将相应的事件信息写入到inotifyFd所描述的inotify对象中,此时可以通过read()函数从inotifyFd描述符中将事件信息读取出来。
事件信息使用结构体inotify_event进行描述:
struct inotify_event {
__s32 wd; /* 事件对应的Watch对象的描述符 */
__u32 mask; /* 事件类型,例如文件被删除,此处值为IN_DELETE */
__u32 cookie;
__u32 len; /* name字段的长度 */
char name[0]; /* 可变长的字段,用于存储产生此事件的文件路径*/
};
当有监听事件发生时,可以通过如下方式将一个或多个未读取的事件信息读取出来:
size_t len = read (inotifyFd, events_buf,BUF_LEN);
其中events_buf是inotify_event的数组指针,能够读取的事件数量由取决于数组的长度。成功读取事件信息后,便可根据inotify_event结构体的字段判断事件类型以及产生事件的文件路径了。
INotify机制的使用过程
通过inotify_init()创建一个inotify对象。
通过inotify_add_watch将一个或多个监听添加到inotify对象中。
通过read()函数从inotify对象中读取监听事件。当没有新事件发生时,inotify对象中无任何可读数据。
通过INotify机制避免了轮询文件系统的麻烦,但是还有一个问题,INotify机制并不是通过回调的方式通知事件,而需要使用者主动从INotify对象中进行事件读取,这里借助Linux的Epoll机制。
Epoll
Epoll可以使用一次等待监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符或自定义的数据,使用者可以据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。
epoll_create(int max_fds):创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成。max_fds参数表示了此epoll对象可以监听的描述符的最大数量。
int epoll_create1(int flag);
当flag是0时,表示和epoll_create函数完全一样,不需要size的提示了
当flag = EPOLL_CLOEXEC,创建的epfd会设置FD_CLOEXEC
当flag = EPOLL_NONBLOCK,创建的epfd会设置为非阻塞
FD_CLOEXEC是fd的一个标识说明,用来设置文件close-on-exec状态的。当close-on-exec状态为0时,调用exec时,fd不会被关闭;状态非零时则会被关闭,这样做可以防止fd泄露给执行exec后的进程
epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):用于管理注册事件的函数。这个函数可以增加/删除/修改事件的注册。
op表示了何种操作,包括EPOLL_CTL_ADD/DEL/MOD三种,分别表示增加/删除/修改注册事件。
int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):用于等待事件的到来。当此函数返回时,events数组参数中将会包含产生事件的文件描述符。函数返回值表示获取了多少个事件。
Epoll机制的使用过程
创建epoll对象:
Int epfd = epoll_create(MAX_FDS)
。填充epoll_event结构体,以描述监控事件。
通过epoll_ctl()函数将此描述符与epoll_event结构体注册进epoll对象,重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中。
使用epoll_wait()函数等待事件,会使调用者陷入等待状态,直到其注册的事件之一发生之后才会返回,并且携带了刚刚发生的事件的详细信息,在如getEvents中调用。
EventHub的创建过程
- 创建mEpollFd用于监听是否有数据(有无事件)可读。
- 创建mINotifyFd将它注册到DEVICE_PATH(这里路径就是/dev/input)节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得EventHub能收到来自系统的通知,并获取事件的详细信息。
- 调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中。
- 定义int wakeFd[2]作为事件传输管道的读写两端,并将读端注册到epoll中让mEpollFd监听。
EventHub
是 Android 系统中负责处理输入事件的类,它位于frameworks/native/services/inputflinger
目录下。这个类的主要职责是监听和管理/dev/input
目录下的所有输入设备,并将这些设备的事件转换为 Android 系统可以理解的事件格式。在
getEvents
方法中,它首先检查是否需要扫描设备,如果需要,它会调用scanDevicesLocked
方法来遍历/dev/input
目录下的所有设备,打开这些设备并存储到Device
结构体中,同时将这些设备的文件描述符的可读事件注册到 Epoll 中。当设备的输入事件到来时,Epoll 会在getEvents
函数的调用中产生一条 epoll 事件。然后,它遍历
mClosingDevices
链表,为每一个已卸载的设备生成DEVICE_REMOVED
事件。接着,它通过 Epoll 事件的
data
字段确定此事件表示了mINotifyFd
可读,即 Epoll 事件的处理。如果mPendingINotify
事件待处理,它会调用readNotifyLocked
函数来读取并处理存储在mINotifyFd
中的 INotify 事件。这个过程会一直重复,直到满足某个条件(例如超时或者缓冲区满)。
这个过程的关键点在于
scanDevicesLocked
和readNotifyLocked
方法,它们分别负责扫描设备和处理 INotify 事件。这两个方法的具体实现细节没有在提供的代码片段中展示,但它们应该是用来打开设备、注册 Epoll 事件、处理设备卸载和 INotify 事件的。请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、线程同步、设备状态管理等。
* frameworks/native/services/inputflinger/EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
...
for (;;) {
...
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
//遍历/dev/input下所有可用的输入设备打开并存储到Device结构体
//将设备节点的描述符的可读事件注册到Epoll中,当此设备的输入事件到来时,Epoll会在getEvents()函数的调用中产生一条epoll事件
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
//遍历mClosingDevices链表,为每一个已卸载的设备生成DEVICE_REMOVED事件
while (mClosingDevices) {
...
}
// 通过Epoll事件的data字段确定此事件表示了mINotifyFd可读,及Epoll事件的处理
while (mPendingEventIndex < mPendingEventCount) {
}
//如果INotify事件待处理
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
//调用readNotifyLocked()函数读取并处理存储在mINotifyFd中的INotify事件
readNotifyLocked();
deviceChanged = true;
}
}
}
getEvents()通过Epoll事件的data.u32字段在mDevices列表中查找已加载的设备,并从设备的文件描述符中读取原始输入事件列表。从文件描述符中读取的原始输入事件存储在input_event结构体中,这个结构体的四个字段存储了事件的事件戳、类型、代码与值四个元素。然后逐一将input_event的数据转存到RawEvent中并保存至buffer以返回给调用者。
InputReader
InputReader是在InputReaderThread中启动的,InputReaderThread和InputDispatcherThread的定义是类似的,也是继承了Thread并定义了threadLoop纯虚函数。如果处理的事件为键盘输入事件,则调用时序图如下所示。
原始事件的入口函数是ProcessEventsLocked,根据事件类型区分原始输入事件还是设备增删事件,对于原始输入事件,EventHub会讲属于同意输入设备的原始输入事件放在一起,因此使用processEventsForDeviceLocked同时处理来自同意输入设备的一批事件。
InputReader
是 Android 系统中负责处理输入事件的类,它位于frameworks/native/services/inputflinger
目录下。这个类的主要职责是接收和处理来自EventHub
的原始输入事件。在
processEventsLocked
方法中,它遍历所有的原始事件。对于每一条原始事件,根据事件类型,它会调用processEventsForDeviceLocked
方法来处理。如果事件类型是原始输入事件,那么它会调用InputDevice
的process
方法来处理。如果事件类型是设备增删事件,那么它会处理设备增删事件。在
processEventsForDeviceLocked
方法中,它首先检查设备是否存在,如果设备被忽略,那么它会忽略这条事件。如果设备存在,那么它会调用InputDevice
的process
方法来处理事件。这个过程的关键点在于
InputDevice
的process
方法,它是用来处理输入事件的。这个方法的具体实现细节没有在提供的代码片段中展示,但它是用来处理输入事件的。请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、设备状态管理等。
* frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
//根据事件类型区分原始输入事件还是设备增删事件
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
int32_t deviceId = rawEvent->deviceId;
...
//数据事件的处理,batchSize表示属于此设备的输入事件个数
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
//处理设备增删事件
...
}
count -= batchSize;
rawEvent += batchSize;
}
}
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
//InputReader保存了mDevices字典,以设备ID为键值存储了一系列的InputDevice对象
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
ALOGW("Discarding event for unknown deviceId %d.", deviceId);
return;
}
InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()) {
//ALOGD("Discarding event for ignored deviceId %d.", deviceId);
return;
}
//调用InputDevice的process处理事件
device->process(rawEvents, count);
}
processEventsForDeviceLocked中,InputReader保存了mDevices字典,在processEventsLocked的处理设备增删阶段初始化,然后调用InputDevice::process处理事件。
InputDevice
InputReader的InputDevice存储输入设备的信息,与EventHub一样,InputDevice描述了一个输入设备,并且以设备ID为键保存在mDevices中,InputReader::InputDevice和EventHub::Device结构体类似,也保存了设备的ID、厂商信息、设备所属类别。只是InputDevice多了一个InputMapper列表。
InputMapper
InputMapper是InputReader中实际进行原始输入事件加工的场所,有一系列子类,分别用于加工不同类型的原始输入事件。这样使得InputDevice不需要知道哪一个InputMapper可以处理一个原始输入事件,只须将一个事件逐一交给每一个InputMapper尝试处理,如果InputMapper可以接受这个事件则处理,否则什么都不做。
InputReader
类是 Android 系统中负责管理输入设备和处理输入事件的类,它位于frameworks/native/services/inputflinger
目录下。在
addDeviceLocked
方法中,它首先检查是否已经添加了相同的设备,如果已经添加了,那么它会忽略这条设备添加事件。然后,它获取设备标识符、设备类和设备控制器编号,并调用createDeviceLocked
方法来创建一个新的InputDevice
对象。在
createDeviceLocked
方法中,它创建一个新的InputDevice
对象,并根据设备类添加相应的InputMapper
。例如,如果设备是键盘类设备,它会添加KeyboardInputMapper
;如果设备是触摸屏设备,它会添加MultiTouchInputMapper
或SingleTouchInputMapper
。这个过程的关键点在于
InputDevice
的创建和InputMapper
的添加。InputDevice
是用来管理设备和处理事件的,而InputMapper
是用来处理特定类型的输入事件的。请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、设备状态管理等。
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex >= 0) {//已添加的相同设备则不再添加
ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
return;
}
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
device->configure(when, &mConfig, 0);
device->reset(when);
...
mDevices.add(deviceId, device);
...
}
InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, uint32_t classes) {
InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
controllerNumber, identifier, classes);
...
//添加键盘类设备InputMapper
if (keyboardSource != 0) {
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
}
...
//添加触摸屏设备InputMapper
if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
device->addMapper(new MultiTouchInputMapper(device));
} else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
device->addMapper(new SingleTouchInputMapper(device));
}
...
}
InputDevice::process首先会遍历InputDevice中的所有的事件,真正加工原始输入事件的是InputMapper对象,由于原始输入事件的类型很多,因此在InputMapper有很多子类,用于加工不同的原始输入事件,比如KeyboardInputMapper用于处理键盘输入事件,TouchInputMapper用于处理触摸输入事件。
InputDevice
类是 Android 系统中负责处理输入事件的类,它位于frameworks/native/services/inputflinger
目录下。在
process
方法中,它遍历所有的原始事件。如果mDropUntilNextSync
的值为true
,那么它会忽略当前事件。如果mDropUntilNextSync
的值为false
,那么它会遍历所有的InputMapper
,并调用它们的process
方法来处理事件。这个过程的关键点在于
InputMapper
的process
方法,它是用来处理输入事件的。这个方法的具体实现细节没有在提供的代码片段中展示,但它是用来处理输入事件的。请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、设备状态管理等。
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
...
//mDropUntilNextSync的值默认为false,如果设备的输入事件缓冲区溢出,这个值会置为true。
if (mDropUntilNextSync) {
}else{
for (InputMapper* mapper : mMappers) {
mapper->process(rawEvent);
}
}
...
--count;
}
}
KeyboardInputMapper
是 Android 系统中负责处理键盘输入事件的类,它位于frameworks/native/services/inputflinger
目录下。在
process
方法中,它根据事件类型来处理键盘事件。当事件类型为EV_KEY
时,它会调用processKey
方法来处理。在processKey
方法中,它会根据扫描码和使用码来获取按键码、元状态和策略标志。如果按键被按下,它会根据屏幕的方向对按键的虚拟键值进行旋转转换,并检查是否应该丢弃虚拟键。如果应该丢弃,它会取消触摸并返回。然后,它会生成一个
KeyDown
结构体并将其添加到集合中。如果按键被抬起,它会从集合中移除对应的
KeyDown
对象。最后,它会生成一个
NotifyKeyArgs
对象,并调用getListener
方法的notifyKey
方法来通知按键事件。在
mapKey
方法中,它首先获取设备,然后检查键字符映射。如果键字符映射存在,它会尝试根据扫描码和使用码来映射按键码。这个过程的关键点在于键字符映射和按键码的映射,以及按键事件的处理。
请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、设备状态管理等。
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {//表示为Keyboard事件
int32_t scanCode = rawEvent->code;
int32_t usageCode = mCurrentHidUsage;
mCurrentHidUsage = 0;
//排除对鼠标按键的处理
if (isKeyboardOrGamepadKey(scanCode)) {
processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
}
break;
}
...
}
}
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
int32_t keyCode;
int32_t keyMetaState;
uint32_t policyFlags;
if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
&keyCode, &keyMetaState, &policyFlags)) {
keyCode = AKEYCODE_UNKNOWN;
keyMetaState = mMetaState;
policyFlags = 0;
}
if (down) {
// 当按下时,首先需要根据屏幕的方向对按键的虚拟键值进行旋转转换
if (mParameters.orientationAware) {
keyCode = rotateKeyCode(keyCode, getOrientation());
}
// KeyboardInputMapper维护了一个mKeyDowns集合
ssize_t keyDownIndex = findKeyDown(scanCode);
if (keyDownIndex >= 0) {
//对于重复按下的按键,需要确保后续的处理过程中的虚拟键值与第一次按下时的一致,以免重复过程中屏幕的方向的变化导致虚拟键值的变化,使得后续InputDispatch无法正常识别重复按键的动作
keyCode = mKeyDowns[keyDownIndex].keyCode;
} else {
//生成keydown结构体并添加到集合中
if ((policyFlags & POLICY_FLAG_VIRTUAL)
&& mContext->shouldDropVirtualKey(when,
getDevice(), keyCode, scanCode)) {
return;
}
if (policyFlags & POLICY_FLAG_GESTURE) {
mDevice->cancelTouch(when);
}
KeyDown keyDown;
keyDown.keyCode = keyCode;
keyDown.scanCode = scanCode;
mKeyDowns.push_back(keyDown);
}
mDownTime = when;
} else {
ssize_t keyDownIndex = findKeyDown(scanCode);
//对于抬起的按键,则将对应的keydown对象从集合中移除
if (keyDownIndex >= 0) {
keyCode = mKeyDowns[keyDownIndex].keyCode;
mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex);
} else {
return;
}
}
...
NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);
}
status_t EventHub::mapKey(int32_t deviceId,
int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
status_t status = NAME_NOT_FOUND;
if (device) {
// Check the key character map first.
sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
if (kcm != nullptr) {
if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
*outFlags = 0;
status = NO_ERROR;
}
}
}
EventHub::openDeviceLocked会为此设备加载键盘布局配置文件,键盘布局配置文件的路径位于/system/usr/keylayout下的.lk文件。
processKey函数会将加工后的键盘输入事件封装为NotifyKeyArgs,将NotifyKeyArgs通知给InputListenerInterface。
mArgsQueue的数据类型为Vector<NotifyArgs*>,将该key事件压人该vector中。
InputReader::loopOnce的最后调用mQueuedListener->flush()遍历整个mArgsQueue数组。
InputDispatcher继承了InputDispatcherInterface,而InputDispatcherInterface继承了InputListenerInterface,因此实际上是调用了InputDispatcher的notifyKey函数,将NotifyKeyArgs交给InputDispatcher处理。
QueuedInputListener
类是 Android 系统中负责接收和处理输入事件的类,它位于frameworks/native/services/inputflinger
目录下。在
notifyKey
方法中,它将接收到的按键事件参数添加到队列中。在
flush
方法中,它遍历队列中的所有事件,调用每个事件的notify
方法来处理事件,然后删除这些事件。最后,它清空队列。这个过程的关键点在于事件的接收和处理,以及事件队列的管理。
请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、事件状态管理等。
* frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
mArgsQueue.push_back(new NotifyKeyArgs(*args));
}
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);
delete args;
}
mArgsQueue.clear();
}
Android Input初始化及InputDIspatcher流程
唤醒InputDIspatcher
以Motion事件的分化过来进行举例。
InputDispatcher
类是 Android 系统中负责分发输入事件的类,它位于frameworks/native/services/inputflinger
目录下。在
notifyMotion
方法中,它首先检查 Motion 事件的参数是否有效,然后通过interceptMotionBeforeQueueing
方法来校验 Motion 事件的触点数量和 ID 是否在合理范围内。如果 Motion 事件需要通过输入过滤器,它会创建一个MotionEntry
对象并将其添加到入队列中。如果事件被过滤器消费,它会返回而不进行分发。在
enqueueInboundEventLocked
方法中,它将事件添加到入队列中,并根据事件类型进行优化处理。如果当前应用无响应且用户希望切换到其他应用窗口,它会查找触摸窗口。在
findTouchedWindowAtLocked
方法中,它遍历所有的窗口,检查坐标是否落在窗口之上。如果窗口是可触摸的,它会返回该窗口。这个过程的关键点在于事件的分发、过滤和窗口的查找。
请注意,这是一个简化的解释,实际的实现可能会更复杂,包括错误处理、事件状态管理等。
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
//检查Motion事件的参数是否有效,对于motion事件,主要时校验触控点的数量与ID是否在合理范围
if (!validateMotionEvent(args->action, args->actionButton,
args->pointerCount, args->pointerProperties)) {
return;
}
uint32_t policyFlags = args->policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
android::base::Timer t;
//mPolicy是指NativeInputManager对象,这里policyFlags为引用,会修改policyFlags的值
//如果处理时间大于50ms则,输出警告
mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
}
bool needWake;
{ // acquire lock
mLock.lock();
//Motion事件是否需要交由InputFilter过滤
if (shouldSendMotionToInputFilterLocked(args)) {
mLock.unlock();
//初始化MotionEvent,将NotifyMotionArgs中的参数信息赋值给MotionEvent中的参数
MotionEvent event;
event.initialize(args->deviceId, args->source, args->displayId,
args->action, args->actionButton,
args->flags, args->edgeFlags, args->metaState, args->buttonState,
args->classification, 0, 0, args->xPrecision, args->yPrecision,
args->downTime, args->eventTime,
args->pointerCount, args->pointerProperties, args->pointerCoords);
policyFlags |= POLICY_FLAG_FILTERED;
//开始过滤,如果返回值为false,就会直接return,这次事件不再进行分发,直接忽略
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}
mLock.lock();
}
//创建KeyEntry对象
MotionEntry* newEntry = new MotionEntry(args->sequenceNum, args->eventTime,
args->deviceId, args->source, args->displayId, policyFlags,
args->action, args->actionButton, args->flags,
args->metaState, args->buttonState, args->classification,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
//将KeyEntry放入队列,返回true,则派发线程处于休眠,需要唤醒
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();
}
}
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
//mInboundQueue为空,则表示派发线程处于休眠
bool needWake = mInboundQueue.isEmpty();
//将事件放入到队列尾部
mInboundQueue.enqueueAtTail(entry);
//事件响应速度优化处理
switch (entry->type) {
case EventEntry::TYPE_KEY: {
...
break;
}
case EventEntry::TYPE_MOTION: {
//当前App无响应且用户希望切换到其他应用窗口,则drop该窗口事件,并处理其他窗口事件
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
&& (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
&& mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
&& mInputTargetWaitApplicationToken != nullptr) {
...
//查询可触摸的窗口
sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
...
}
break;
}
}
return needWake;
}
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
int32_t x, int32_t y, bool addOutsideTargets, bool addPortalWindows) {
//从前到后浏览窗口,找到触摸窗口和外部目标。
// mWindowHandles中保存的InputWindowHandler类保存了窗口的InputChannel以及InputWindowInfo结构体
// InputWindowInfo结构体保存了窗口的各种布局信息,包括可见性、位置、尺寸、flag等。
const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
// 递归遍历mWindowHandles中的所有WindowHandle,检查时间坐标是否落在其上
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId == displayId) {
int32_t flags = windowInfo->layoutParamsFlags;
if (windowInfo->visible) {
if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
// 如果窗口是focusable或者flag不为FLAG_NOT_FOCUSABLE,则说明该窗口是”可触摸模式“
bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
//如果窗口是”可触摸模式或者坐标