AOSP源码分析:Android Input事件的产生、读取和分发

来源:互联网 发布:五五开的淘宝店叫什么 编辑:程序博客网 时间:2024/06/07 22:08

大家好,今天为大家推荐来自MIUI的Cheeeelok同学的AOSP源码分析系列文章,本文依然从源码的角度带大家理解Android Input事件的产生、读取和分发。还没有看过作者上一篇文章 Android Input子系统:Input进程的创建,监听线程的启动 的同学,现在补上同样不迟,好了,话不多说,直接进入正文。


在上一篇博文中学习了Android Input系统事件监听模块,我们了解到InputManagerService启动后会启动InputReader开始监听来自EventHub的事件。今天就沿着前文的思路,看看EventHub将事件交给InputReader后会发生什么。

本文的内容可以由下图概括:

Input事件的读取者InputReader

在上一篇博文中已经讲到,对于InputRead,它在监听过程中会做以下事情:

  1. 启动后循环执行mReader->loopOnce()

  2. loopOnce()中会调用mEventHub->getEvents读取事件

  3. 读到了事件就会调用processEventsLocked处理事件

  4. 处理完成后调用getInputDevicesLocked获取输入设备信息

  5. 调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化

  6. 最后调用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函数中,它主要做了以下事情:

  1. 循环获取RawEvent

  2. 如果RawEvent->type小于FIRST_SYNTHETIC_EVENT,说明这是个来自kernel的Input事件,则调用processEventsForDeviceLocked函数处理

  3. 否则此时的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函数,它主要做了以下事情:

  1. 调用EventHub的mapKey函数根据deviceId、scanCode、usageCode得到keyCode、keyMetaState、policyFlags

  2. 对按键事件进行预处理(按下、按起的记录和逻辑判断)

  3. 将按键事件相关的信息封装到NotifyKeyArgs中(deviceId、when、policyFlags、down还是up、keyCode等等……)

  4. 最终调用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.cppInputReader::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.cppQueuedInputListener::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为例,是非常常见的业务逻辑了:

  1. 判断设备是否已添加,若否则向下执行

  2. 根据deviceId从EventHub中获取identifier、classe、controllerNumber,调用createDeviceLocked将这些信息封装到InputDevice中

  3. 初始化InputDevice

  4. 将新创建的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函数,它做了以下事情:

  1. 验证NotifyArgs的有效性

  2. 对policyFlags和flag进行预处理

  3. 如果keycode为AKEYCODE_HOME则根据action设置’sys.domekey.down’属性(这属性貌似是指纹在用?)

  4. 对按键的down和up作一个处理以保证动作的连续性,做的事情大致上是:如果按键不是home或back,先以keycode为键,包含keycode、deviceId的KeyReplacement struct为值将按键信息添加到mReplacedKey里边,同时将metaState设为off,避免后面相同按键的重复判断。之后在up的时候,根据当前的keycode和deviceId取出之前cache的keycode并移除该KeyReplacement,同时恢复metaState,表示一次按键动作的完成。

  5. 将NotifyKeyArgs和keyCode、flag、metaState等封装到KeyEvent中

  6. 将KeyEvent经由InputManagerService交给PhoneWindowManager判断是否要在放入事件队列前拦截

  7. 判断是否要把事件发送到InputFilte中过滤

  8. 将NotifyArgs和flags、keyCode、repeatCount、metaState、policyFlags封装为KeyEntry,并加入事件处理队列inboundQueue

  9. 最后唤醒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()函数,它做了以下事情:

  1. 唤醒InputDispatcher线程继续分发操作

  2. 判断commandQueue是否为空,为空执行dispatchOnceInnerLocked

  3. 否则继续执行commandQueue里的命令

  4. 执行完commandQueue中的命令后休眠timeoutMillis时间

InputDispatcher.cppbool 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函数,它做了以下事情:

  1. 当设备处于非交互状态(即将休眠),为了确保设备的按键链正确,会将当前KeyRepeatState持有的lastKeyEntry释放并置空(resetKeyRepeatLocked函数)

  2. 对App切换进行优化,如果App切换时间小于nextWakeUpTime,就将appSwitchDueTime设为nextWakeUpTime,丢弃其他事件。

  3. 从事件队列中取出事件,调用pokeUserActivityLocked函数让PowerManagerService唤醒设备,避免让设备进入休眠

  4. 重设ANR计时

  5. 如果事件需要丢弃,则设置dropReason

至此准备工作就做完了,最后就把pendingEvent交给对应的dispatchXXXLocked函数分发,例如这里就是交给dispatchKeyLocked函数。

按键事件分发处理函数dispatchKeyLocked

进入到dispatchKeyLocked函数中,它做了以下事情:

  1. 处理按键重复

  2. 标记事件是否为长按事件

  3. 标记事件开始进行分发

  4. 判断是否需要拦截事件,拦截的话进行处理

  5. 调用findFocusedWindowTargetsLocked函数判断发生按键事件的Window并得到对应的inputTargets

  6. 调用addMonitoringTargetsLocked函数监控这些InputTarget的InputChannel

  7. 最后调用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队列中的事件,它做了以下事情:

  1. 取出队头的DispatchEntry

  2. 根据事件类型通过Connection的inputPublisher的publishXXXEvent函数将事件信息封装到InputMessage中,通过InputChannel发送InputMessage到对应窗口。

这里有一点细节,事件不仅仅会发到对应InputTarget中,还会异步通过另一个InputChannel将DispatchEntry发送到InputManagerService一个监控InputTarget中。该监控InputTarget什么都不会做,只是默默监控。第三方可以做一些自己的特殊事件监听(例如组合按键、手势等)。

完成上面的操作后,将DispatchEntry从outboundQueue中取出来放到waitQueue中,当publish出去的事件被处理完成(finished),InputManagerService就会从应用中得到一个回复,此时就会取出waitQueue中的事件。此外,还会计算事件的处理时间以判断是否要抛出ANR,相关知识会在后面解释。

至此,Input事件的产生、读取和分发流程就串联起来了,后面还将继续学习Input事件经InputDispatcher分发后是如何到达应用的。如果大家对AOSP源码分析系列感兴趣,欢迎继续关注!

 苹果手机专属打赏


欢迎关注技术视界

原创粉丝点击