AOSP源码分析:Android Input事件的产生读取和分发
Posted 技术视界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AOSP源码分析:Android Input事件的产生读取和分发相关的知识,希望对你有一定的参考价值。
大家好,今天为大家推荐来自MIUI的Cheeeelok同学的AOSP源码分析系列文章,本文依然从源码的角度带大家理解android Input事件的产生、读取和分发。还没有看过作者上一篇文章 的同学,现在补上同样不迟,好了,话不多说,直接进入正文。
在上一篇博文中学习了Android Input系统事件监听模块,我们了解到InputManagerService启动后会启动InputReader开始监听来自EventHub的事件。今天就沿着前文的思路,看看EventHub将事件交给InputReader后会发生什么。
本文的内容可以由下图概括:
Input事件的读取者InputReader
在上一篇博文中已经讲到,对于InputRead,它在监听过程中会做以下事情:
启动后循环执行mReader->loopOnce()
loopOnce()中会调用mEventHub->getEvents读取事件
读到了事件就会调用processEventsLocked处理事件
处理完成后调用getInputDevicesLocked获取输入设备信息
调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化
最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDispatcher
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
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();
}
接下来我们就学习它具体的处理流程吧。
processEventsLocked对Input事件进行处理、归类
在processEventsLocked函数中,它主要做了以下事情:
循环获取RawEvent
如果RawEvent->type小于FIRST_SYNTHETIC_EVENT,说明这是个来自kernel的Input事件,则调用processEventsForDeviceLocked函数处理
否则此时的RawEvent->type就代表着Input设备的增、删、扫描事件,则调用对应的设备处理函数进行处理
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) {
……
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
Input事件处理函数processEventsForDeviceLocked
在processEventsForDeviceLocked函数中,如果设备已注册,且来自它的事件不需要忽略,则调用device->process让该device处理Input事件。
void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count) {
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;
}
device->process(rawEvents, count);
}
Input事件归类处理InputMapper->process
在InputDevice中,存储着许多InputMapper,每种InputMapper对应一类Device,例如:Touch、Keyboard、Vibrator等等……而调用InputDevice的process函数,就是将Input事件传递给每一个InputMapper,匹配的InputMapper就会对Input事件进行处理,不匹配的则会忽略。
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
…… for (size_t i = 0; i < numMappers; i++) {
InputMapper* mapper = mMappers[i];
mapper->process(rawEvent);
}
……
}
这里为了方便学习,我们以按键类事件为例继续探索,即KeyboardInputMapper。进入到它的process函数,可以看到,当RawEvent->type为EV_KEY时,说明是按键类Input事件,则调用processKey对它进行处理。
进入到processKey函数,它主要做了以下事情:
调用EventHub的mapKey函数根据deviceId、scanCode、usageCode得到keyCode、keyMetaState、policyFlags
对按键事件进行预处理(按下、按起的记录和逻辑判断)
将按键事件相关的信息封装到NotifyKeyArgs中(deviceId、when、policyFlags、down还是up、keyCode等等……)
最终调用getListener()->notifyKey,这里getListener得到的就是InputDispatcher
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
&keyCode, &keyMetaState, &policyFlags)) {
keyCode = AKEYCODE_UNKNOWN;
keyMetaState = mMetaState;
policyFlags = 0;
}
if (down) {
……
} else {
……
}
…… NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);
}
而这里的getListener得到的其实就是封装后的InputDispatcher,即QueuedInputListener:
InputReader.cpp
InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputListenerInterface>& listener) :
mContext(this), mEventHub(eventHub), mPolicy(policy),
mGlobalMetaState(0), mGeneration(1),
mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
mQueuedListener = new QueuedInputListener(listener);
……
}
它的定义:
InputListener.cpp
QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
mInnerListener(innerListener) {
}
所以调用getListener()->notifyKey(&args)调用的是QueuedInputListener的notifyKey函数,它里边有许多notifyXXX函数,做的事情都是将NotifyXXXArgs放入它的mArgsQueue队列中存储,等待处理。
调用getInputDevicesLocked获取输入设备信息
记得在processEventsLocked函数中,当rawEvent->type大于等于EventHubInterface::FIRST_SYNTHETIC_EVENT时会执行什么吗?接下来就让我们一起学习Input设备的增删:
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) {
……
} else {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
……
}
}
事实上这三个函数做的事情都很简单,我就不贴代码了,以addDeviceLocked为例,是非常常见的业务逻辑了:
判断设备是否已添加,若否则向下执行
根据deviceId从EventHub中获取identifier、classe、controllerNumber,调用createDeviceLocked将这些信息封装到InputDevice中
初始化InputDevice
将新创建的InputDevice添加到mDevice中存储
了解了这些后,看getInputDevicesLocked函数就会觉得非常简单了,它就是把mDevices中不需要被忽略的InputDevice取出来放入参数outInputDevices,也就是loopOnce中的inputDevices。
InputDevice添加完成后,调用InputManagerService的notifyInputDevicesChanged函数通知系统输入设备信息需要更新。
处理设备的增删,预处理、归类事件,将事件放入事件队列,通知系统更新设备信息后,当然就是要通知InputDispatcher取出事件队列中的事件进行处理了。
调用mQueuedListener->flush()通知InputDispatcher处理事件
调用mQueuedListener->flush()其实就是循环取出NotifyArgs列表中的NotifyArg并调用它的notify函数通知mInnerListener处理它。根据我们前面的分析可知,这里的mArgsQueue存储的就是前面存储的等待处理的NotifyXXXArgs。
InputListener.cppvoid 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();
}
在flush中,循环取出队列里的NotifyXXXArgs(有多种,例如NotifyKeyArgs、NotifyMotionArgs),调用它的notify函数通知mInnerListener,也就是InputReader创建时传入的listener。最终会调用mInnerListener的notifyXXX函数。
Input事件的分发者InputDispatcher
调用InputDispatcher的notifyKey处理按键事件
进入到InputDispatcher的notifyKey函数,它做了以下事情:
验证NotifyArgs的有效性
对policyFlags和flag进行预处理
如果keycode为AKEYCODE_HOME则根据action设置’sys.domekey.down’属性(这属性貌似是指纹在用?)
对按键的down和up作一个处理以保证动作的连续性,做的事情大致上是:如果按键不是home或back,先以keycode为键,包含keycode、deviceId的KeyReplacement struct为值将按键信息添加到mReplacedKey里边,同时将metaState设为off,避免后面相同按键的重复判断。之后在up的时候,根据当前的keycode和deviceId取出之前cache的keycode并移除该KeyReplacement,同时恢复metaState,表示一次按键动作的完成。
将NotifyKeyArgs和keyCode、flag、metaState等封装到KeyEvent中
将KeyEvent经由InputManagerService交给PhoneWindowManager判断是否要在放入事件队列前拦截
判断是否要把事件发送到InputFilte中过滤
将NotifyArgs和flags、keyCode、repeatCount、metaState、policyFlags封装为KeyEntry,并加入事件处理队列inboundQueue
最后唤醒InputDispatcher线程的Looper,让它循环读取inboundQueue中的事件进行分发
InputDispatcher.cppvoid InputDispatcher::notifyKey(const NotifyKeyArgs* args) { if (!validateKeyEvent(args->action)) { return;
}
……
KeyEvent event;
event.initialize(args->deviceId, args->source, args->action,
flags, keyCode, args->scanCode, metaState, 0,
args->downTime, args->eventTime);
bool needWake;
{ // acquire lock
mLock.lock();
……
int32_t repeatCount = 0;
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();
}
}
InputDispatcher的监听线程InputDispatcherThread
来到InputDispatcherThread的threadLoop函数,可以看到里面就是循环调用InputDispatcher的dispatchOnce()函数,它做了以下事情:
唤醒InputDispatcher线程继续分发操作
判断commandQueue是否为空,为空执行dispatchOnceInnerLocked
否则继续执行commandQueue里的命令
执行完commandQueue中的命令后休眠timeoutMillis时间
InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast(); // Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
} // Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
来到dispatchOnceInnerLocked函数,它做了以下事情:
当设备处于非交互状态(即将休眠),为了确保设备的按键链正确,会将当前KeyRepeatState持有的lastKeyEntry释放并置空(resetKeyRepeatLocked函数)
对App切换进行优化,如果App切换时间小于nextWakeUpTime,就将appSwitchDueTime设为nextWakeUpTime,丢弃其他事件。
从事件队列中取出事件,调用pokeUserActivityLocked函数让PowerManagerService唤醒设备,避免让设备进入休眠
重设ANR计时
如果事件需要丢弃,则设置dropReason
至此准备工作就做完了,最后就把pendingEvent交给对应的dispatchXXXLocked函数分发,例如这里就是交给dispatchKeyLocked函数。
按键事件分发处理函数dispatchKeyLocked
进入到dispatchKeyLocked函数中,它做了以下事情:
处理按键重复
标记事件是否为长按事件
标记事件开始进行分发
判断是否需要拦截事件,拦截的话进行处理
调用findFocusedWindowTargetsLocked函数判断发生按键事件的Window并得到对应的inputTargets
调用addMonitoringTargetsLocked函数监控这些InputTarget的InputChannel
最后调用dispatchEventLocked分发按键事件
InputDispatcher与ANR的关联
findFocusedWindowTargetsLocked函数中有一点细节是需要关注的,就是里边的handleTargetsNotReadyLocked函数,它在focusedWindowHandle为空且focusedApplicationHandle不为空的时候,或checkWindowReadyForMoreInputLocked返回值为false(表示目前Window还没有准备好接收更多Input事件)时被调用。在这个方法里面涉及到onANRLocked的调用,它会触发ANR。里面做的事情大致是:
如果applicationHandle和windowHandle都为空,且inputTargetWaitCause(Input事件等待的原因)不是INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY(系统还没准备好),那么更新inputTargetWaitCause为INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,记录mInputTargetWaitStartTime(等待起始时间)为currentTime,超时时间设为无限大,mInputTargetWaitTimeoutExpired设为false,清空mInputTargetWaitApplicationHandle(等待窗口的ApplicationHandle队列)。
如果两者有一不为空且系统已经准备好,如果windowHandle不为空,timeout(超时时间)为windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT)的返回值;如果applicationHandle不为空,timeout(超时时间)为applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT)。然后记录mInputTargetWaitStartTime(等待起始时间)为currentTime,mInputTargetWaitTimeoutTime超时时间设为起始时间加timeout。然后如果windowHandle不为空则将mInputTargetWaitApplicationHandle设为windowHandle中保存的inputApplicationHandle;否则如果mInputTargetWaitApplicationHandle为空且inputApplicationHandle不为空,则将mInputTargetWaitApplicationHandle设为inputApplicationHandle。
如果currentTime大于mInputTargetWaitTimeoutTime,说明事件的等待超时了,就会执行onARNLocked函数,在里边进行ANR相关的处理。
分发处理InputTarget的dispatchEventLocked函数
在dispatchEventLocked函数中,再次调用pokeUserActivityLocked避免设备进入休眠状态,然后取出InputTargets里面的InputTarget,先调用getConnectionIndexLocked函数获得InputTarget对应的Connection,再调用prepareDispatchCycleLocked函数向Window分发按键事件,最后调用到enqueueDispatchEntriesLocked。
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
#if DEBUG_DISPATCH_CYCLE ALOGD("dispatchEventToCurrentInputTargets");
#endif
ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
pokeUserActivityLocked(eventEntry);
for (size_t i = 0; i < inputTargets.size(); i++) {
const InputTarget& inputTarget = inputTargets.itemAt(i);
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
} else {
#if DEBUG_FOCUS
ALOGD("Dropping event delivery to target with channel '%s' because it " "is no longer registered with the input dispatcher.", inputTarget.inputChannel->getName().string());
#endif
}
}
}
在这里有一点要注意的是,为什么是判断多个InputTarget,因为对于KeyEvent来说,一个InputTarget可能就够了,但对于TouchEvent来说,就会出现同时触控多个InputTarget的情况。
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
bool wasEmpty = connection->outboundQueue.isEmpty(); // Enqueue dispatch entries for the requested modes.
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_IS);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER); // If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
startDispatchCycleLocked(currentTime, connection);
}
}
在enqueueDispatchEntriesLocked函数中,首先调用enqueueDispatchEntryLocked将事件的flag与给出的InputTarget的flag匹配,匹配成功的事件(只能匹配一个flag)再次封装,成为DispatchEntry,根据事件的type对DispatchEntry的resolvedAction赋值,添加到该窗口(InputTarget关联着某个窗口)的outboundQueue队列的队尾,最后留个log记录Connection和Window当前的信息。
将事件分发到对应窗口的outboundQueue队列中后,调用startDispatchCycleLocked循环处理outboundQueue队列中的事件,它做了以下事情:
取出队头的DispatchEntry
根据事件类型通过Connection的inputPublisher的publishXXXEvent函数将事件信息封装到InputMessage中,通过InputChannel发送InputMessage到对应窗口。
这里有一点细节,事件不仅仅会发到对应InputTarget中,还会异步通过另一个InputChannel将DispatchEntry发送到InputManagerService一个监控InputTarget中。该监控InputTarget什么都不会做,只是默默监控。第三方可以做一些自己的特殊事件监听(例如组合按键、手势等)。
完成上面的操作后,将DispatchEntry从outboundQueue中取出来放到waitQueue中,当publish出去的事件被处理完成(finished),InputManagerService就会从应用中得到一个回复,此时就会取出waitQueue中的事件。此外,还会计算事件的处理时间以判断是否要抛出ANR,相关知识会在后面解释。
至此,Input事件的产生、读取和分发流程就串联起来了,后面还将继续学习Input事件经InputDispatcher分发后是如何到达应用的。如果大家对AOSP源码分析系列感兴趣,欢迎继续关注!
苹果手机专属打赏
欢迎关注技术视界
以上是关于AOSP源码分析:Android Input事件的产生读取和分发的主要内容,如果未能解决你的问题,请参考以下文章
Android系统Input专题源码分析视频课程/千里马Framework/InputDispatcher/InputReader/模拟触摸事件
Android系统Input专题源码分析视频课程/千里马Framework/InputDispatcher/InputReader/模拟触摸事件
Android系统Input专题源码分析视频课程/千里马Framework/InputDispatcher/InputReader/模拟触摸事件
docker中编译android aosp源码,出现Build sandboxing disabled due to nsjail error