Android Input子系统:Input事件的产生、读取和分发,InputReader、InputDispatcher

CheelokCheelok

开始本文的学习前,先通过前面的图先复习一下吧:

EventHub:

InputManagerService:

在上一篇博文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.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为例,是非常常见的业务逻辑了:

  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.cpp

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();
}

在flush中,循环取出队列里的NotifyXXXArgs(有多种,例如NotifyKeyArgs、NotifyMotionArgs),调用它的notify函数通知mInnerListener,也就是InputReader创建时传入的listener,即InputDispatcher,最终会调用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.cpp

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

  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。里面做的事情大致是:

  1. 如果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队列)。
  2. 如果两者有一不为空且系统已经准备好,如果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。
  3. 如果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事件的读取和分发相关的学习就结束了。

题外话

如果你喜欢看我写的技术文的话,可以关注我的公众号:Cheelok的自留地喔。

weixin.qq.com/r/EyguNmH (二维码自动识别)

「多谢老板们的咖啡~」
1 人赞赏
许万金
文章被以下专栏收录
3 条评论
推荐阅读