• android源码-事件分发处理机制(下)-从信号源输入到处理完成的完整源码解读


    前言:

    android原生事件分发这块,内容复杂度感觉还好,所分为两篇来介绍。上篇介绍APP收到事件信号后如何进行分发和处理的,而下篇介绍各种点击信号如何从屏幕一层一层传递到APP层的。

    上篇链接:android源码-事件分发处理机制(上)- java层事件分发流程_失落夏天的博客-CSDN博客

    因为涉及到底层这块逻辑是十分的复杂,所以本文尽量多的用图而不是文字的形式来描述,希望借此让读者能够更容易的记住每个流程。

    一.流程简介

    整个流程链路虽然比View绘制流程要简单,但是整个链路还是很长的,即使看过这篇文章之后全部都理解清楚了,有可能用不了多久,还是会忘记,包括作者本身有可能也会忘。所以我们要先了解其主要的核心流程,了解主流程之后,再去细细研读其每一处的细节。这样就算细节哪怕有一天忘记了,只要记录主流程,后续再次翻阅源码查阅时也会方便的多。

    首先,借用努比亚技术团队的一张流程图作为开始头,这张图为我们讲解了整个事件分发的流程。

    第二张是我自己画的流程图,相对于第一张图,区别主要还是体现在流程上。

    我把整个流程分为五大块。

    1.硬件层和驱动层定时扫描接受输入事件,放到FD文件中等待EventHub读取(这一部分图中未体现)。

    2.InputReader中有一个线程,不断的从EventHub读取输入事件,然后放入到mInboundQueue集合种,并唤醒InputDispatcher中的线程。

    3.InputDispatcher中也存在一个线程,起作用主要为两块:

    首先处理mInboundQueue集合中的事件,分发给APP层,并加入waitQueue集合。

    然后进行超时判断,判断waitQueue集合中是否存在超时的事件。如果存在,则走ANR流程。

    4. APP侧收到事件后,通过InputEventReceiver交由应用层处理,应用层处理完后,发送事件通知系统侧处理完成。

    5.系统侧通过handleReceiveCallback方法收到事件后,进行相应的修改。主要是把事件从waitQueue集合中删除,并加入mOutboundQueue集合。这样ANR读取waitQueue集合就不会存在超时的问题。

    第一部分这块其实并没有什么代码实例,第一部分我们只要这个概念就好了。所以我们直接从第二块开始讲起。

    二.InputReader中读取输入事件

    主流程图如下:

    1.InputReader线程的启动

    首先,SystemServer中,启动各种服务的时候,会启动一个InputManagerService的服务,该服务启动后,会调用start方法,代码如下:

    1. inputManager = new InputManagerService(context);
    2. ...
    3. inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
    4. inputManager.start();

    然后经过一层层调用,最终会调用到InputReader::start方法中,并开启loopOnce无限循环。

    loopOnce的概念就不具体解释了,我们只要知道,这是一个阻塞调用的方法就好了,如果接受到外界通知,就会唤醒执行该方法。执行完之后,又会进入阻塞状态。

     所以,我们可以认为,loopOnce就是那个不断读取外部输入信号的方法。

    在loopOnce方法中,主要做了3件事:

    首先,通过getEvents方法从Eventhub从指定位置读取输入事件,

    然后,通过processEventsLocked方法进行尸检的分发。

    最后,通过mQueuedListener->flush();通知到InputDispather进行下一步处理。

    2.通过EventHub读取事件

    上面有提到,主要通过getEvents方法来实现读取的,传入参数有三个:

    timeoutMillis:超时时间

    mEventBuffer:存放事件的buffer空间

    EVENT_BUFFER_SIZE:读取长度

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    EventHub中有两个集合,mClosingDevices和mOpeningDevices。

    首先遍历mClosingDevices和mOpeningDevices集合,看是否有移出或者新添加的设备,读取其中信息并从对应的集合中移除。

    然后从mPendingEventItems数组中读取epoll_event事件,加入到结构体中:

    1. size_t count = size_t(readSize) / sizeof(struct input_event);
    2. for (size_t i = 0; i < count; i++) {
    3. struct input_event& iev = readBuffer[i];
    4. event->when = processEventTimestamp(iev);
    5. event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
    6. event->deviceId = deviceId;
    7. event->type = iev.type;
    8. event->code = iev.code;
    9. event->value = iev.value;
    10. event += 1;
    11. capacity -= 1;
    12. }

    event和buffer都是结构体RawEvent的对象,该方法中主要对event进行操作,则event和buffer的value差值就是新添加的事件数量,所以,getEvents方法中最后返回了新添加的输入事件数量:

    1. // All done, return the number of events we read.
    2. return event - buffer;

    3.分发事件

    如果上面读取到的输入事件数量>0,则需要进行下一步的分法操作,分发的方法是processEventsLocked,所以我们主要看一下该方法:

    1. if (count) {
    2. processEventsLocked(mEventBuffer, count);
    3. }

    整个流程如下图所示,就不多讲解了:

    4.通知InputDispather

    这个主要是通过观察者模式来实现的,入口方法为:mQueuedListener.flush()。

     该流程中,主要是对输入信号根据类型进行处理,处理完成后加入mInboundQueue集合,并且唤醒InputDispatcher中的线程。

    三.InputDispatcher分发输入事件

    InputDispatcher中的线程被唤醒后,会执行dispatchOnce方法。

    我们把dispatchOnce方法的流程也主要分为四部分:

    1.dispatchOnceInnerLocked方法中,读取mInboundQueue中的输入事件,进行相关的分法;

    2.runCommandsLockedInterruptable方法中,主要执行一些任务。这些任务主要是APP侧完成了事件消费后的通知处理。这个,我们在5.2小节中具体讲,这里就先不展开了。

    3.processAnrsLocked方法中,根据各种条件进行判断决定是否要进入ANR流程;

    4.计算出下一次的触发时间,通过mLooper->pollOnce(timeoutMillis);方法设置最长休眠时间进入等待。

    3.1 处理输入事件

    输入事件处理主要是在dispatchOnceInnerLocked方法中,

    dispatchOnceInnerLocked方法中的整个流程如下图所示:

    1.如果mInboundQueue不为空的,则取出其头部的事件,赋值给mPendingEvent。

    2.dispatchMotionLocked方法中,通过判断输入事件的位置,确定属于哪个window。

    3.dispatchEventLocked中通过window的token找到和客户端通信的connection对象。

    4. enqueueDispatchEntriesLocked方法中,把事件加入到Connection对象中outboundQueue集合里。

    Connection对象中,有两个集合,分别是outboundQueue和waitQueue。outboundQueue集合中存放发送给APP侧的输入事件,waitQueue中存放发送给APP侧但还没收到回应的输入事件。

    5.startDispatchCycleLocked负责事件的具体分发,按照类型不同调用不同的方法进行的分发。这里以点击事件为例,通过publishMotionEvent方法进行传送。传送完成后,把该事件加入到waitQueue队列中进行记录。并且也添加到mAnrTracker中的集合当中。下面5.2小节中会讲到如何取出和使用。

    6.publishMotionEvent负责具体的事件分发,最终通过socket的方式发送到客户端,通过的是FD管道。

    3.2 ANR判断

    ANR的判断逻辑,主要是由processAnrsLocked方法来负责的。

    我们首先来看下官方的注释:

    1. // If we are still waiting for ack on some events,
    2. // we might have to wake up earlier to check if an app is anr'ing.
    3. const nsecs_t nextAnrCheck = processAnrsLocked();

    简单来说,就是需要等待APP侧的回应,所以我们要进行检查防止APP产生ANR了。

    我们首先看一下 processNoFocusedWindowAnrLocked()方法,如下:

    1. nsecs_t InputDispatcher::processAnrsLocked() {
    2. const nsecs_t currentTime = now();
    3. nsecs_t nextAnrCheck = LONG_LONG_MAX;
    4. // Check if we are waiting for a focused window to appear. Raise ANR if waited too long
    5. if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
    6. if (currentTime >= *mNoFocusedWindowTimeoutTime) {
    7. //场景1
    8. processNoFocusedWindowAnrLocked();
    9. mAwaitedFocusedApplication.reset();
    10. mNoFocusedWindowTimeoutTime = std::nullopt;
    11. return LONG_LONG_MIN;
    12. } else {
    13. // Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
    14. nextAnrCheck = *mNoFocusedWindowTimeoutTime;
    15. }
    16. }
    17. // Check if any connection ANRs are due
    18. nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
    19. if (currentTime < nextAnrCheck) { // most likely scenario
    20. return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
    21. }
    22. // If we reached here, we have an unresponsive connection.
    23. sp connection = getConnectionLocked(mAnrTracker.firstToken());
    24. if (connection == nullptr) {
    25. ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
    26. return nextAnrCheck;
    27. }
    28. connection->responsive = false;
    29. // Stop waking up for this unresponsive connection
    30. mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
    31. //场景2
    32. onAnrLocked(connection);
    33. return LONG_LONG_MIN;
    34. }

    processNoFocusedWindowAnrLocked方法最终也会调用到onAnrLocked方法。

    也就是说有两种场景会触发ANR:

    1.场景1:有等待获取焦点应用和没有获取焦点的窗口,并且超过5S。

    2.场景2:有应用获取到了焦点,并且超过5S没有响应。具体的判断条件是这行一行代码:

    nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());

    从AnrTracker中记录读取最前面节点的时间,和nextAnrCheck做对比,取时间更早的一个。如果当前时间>=nextAnrCheck,则说明已经ANR了。具体firstTimeout如何计算的,这个我们放到下面的5.2小节中去介绍。

    大多数的ANR情况,都是应用获取到了焦点并且超过5S没有响应。

    这里具体onAnrLocked之后的逻辑我们就暂时不看了,后面会专门写一个为什么会有ANR的文章进行详细的讲解。

    最后会返回一个时间,如果还没有到ANR触发的时间,则把下次ANR触发的时间进行返回,让线程休眠到下一次的触发时间。

    3.3 进入休眠逻辑

    这块逻辑比较简单,获取到下一次的检查时间,然后进入休眠状态,直到下一次的检查时间到来。

    如果中间传递过来新的输入事件,则由InputReader唤醒进行响应。

    1. nsecs_t currentTime = now();
    2. int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    3. mLooper->pollOnce(timeoutMillis);

    四.APP侧处理输入事件

    上面3.1中讲到,InputDispatcher会把事件分发给APP侧。

    APP侧接收这个信号的是android_view_InputEventReceiver.cpp类,整个流程如下:

    1.InputEventReceiver中注册了FD,在收到输入事件的时候,会调用handleEvent方法唤起流程。

    2.handleEvent中,主要是consumeEvents负责完成整个流程。它主要完成了三件事情:

    首先,他会通过InputConsumer的consume方法接收输入事件;其中有一个集合mConsumeTimes,存放成功接收到的输入事件。

    然后,判断类型,进行不同的转换,比如点击事件,就把输入事件InputEvent转换成了JAVA的对象InputEvent。

    最后,通过反射调用java的方法,通知到java层InputEventReceiver.java中的dispatchInputEvent方法。

    核心代码如下:

    1. status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
    2. bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    3. ...
    4. for (;;) {
    5. InputEvent* inputEvent;
    6. //接受输入事件
    7. status_t status = mInputConsumer.consume(&mInputEventFactory,
    8. consumeBatches, frameTime, &seq, &inputEvent);
    9. ...
    10. switch (inputEvent->getType()) {
    11. ...
    12. //类型转换
    13. case AINPUT_EVENT_TYPE_MOTION: {
    14. ...
    15. inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
    16. break;
    17. }
    18. ...
    19. if (inputEventObj) {
    20. //通知java层方法
    21. env->CallVoidMethod(receiverObj.get(),
    22. gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
    23. ...
    24. }
    25. }
    26. }

    3.java层中,注册的InputEventReceiver其实是ViewRootImpl中的WindowInputEventReceiver,所以会调用到WindowInputEventReceiver中的onInputEvent方法。

    4.接下来,就是我们十分了解的java层事件分发流程了,这里具体就不细讲了。这个上篇的文章里面有详细的描述,链接如下:

    android源码学习-事件分发处理机制_失落夏天的博客-CSDN博客

    5.java层中如果没有主线程的阻塞,则会中会调用finishInputEvent方法进行通知,告之系统侧APP事件已经处理完成。

    6.android_view_InputEventReceiver.cpp中有一个集合mOutboundQueue,存放的是处理完成的事件。所以流程走到NativeInputEventReceiver::finishInputEvent中时,会把已经处理完的事件加入到这个集合当中。

    7.NativeInputEventReceiver::processOutboundEvents中负责消费mOutboundQueue中的事件。通过mInputConsumer.sendFinishedSignal方法通知到系统侧,如果成功,则从mOutboundQueue中删除该事件。

    8.InputConsumer::sendUnchainedFinishedSignal方法中,首先会构建InputMessage对象,其类型为InputMessage::Type::FINISHED,然后通过mChannel通道发送给服务。这一点,和socket通信很像,因为其本身走的就是socket通道。同样,如果发送成功,则从mConsumeTimes中移除该输入事件。

    五.InputDispatcher中处理输入完成消息

    系统侧接受输入事件处理完成信号的方式和APP侧接受的方式类似,也是基于FD的检测,只不过这一次系统从生产者变成了消费者。

    5.1 FD注册回调

    在WindowManagerService创建Window的时候,其实就会创建完成消息的FD注册。

    主要流程如下:

    这样,如果系统侧的通道收到传入信号时,就会调用InputDispatcher中的handleReceiveCallback方法。

    5.2 处理完成消息

    handleReceiveCallback这个方法中如下,我们只看最核心的代码:

    1. int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
    2. std::scoped_lock _l(mLock);
    3. sp<Connection> connection = getConnectionLocked(connectionToken);
    4. if (connection == nullptr) {
    5. for (;;) {
    6. Result<InputPublisher::ConsumerResponse> result =
    7. connection->inputPublisher.receiveConsumerResponse();
    8. if (std::holds_alternative<InputPublisher::Finished>(*result)) {
    9. const InputPublisher::Finished& finish =
    10. std::get<InputPublisher::Finished>(*result);
    11. finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
    12. finish.consumeTime);
    13. } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
    14. ...
    15. }
    16. gotOne = true;
    17. }
    18. if (gotOne) {
    19. runCommandsLockedInterruptable();
    20. if (status == WOULD_BLOCK) {
    21. return 1;
    22. }
    23. }
    24. ...
    25. return 0; // remove the callback
    26. }

    这里的逻辑主要是在一个循环中完成下列操作

    1.通过通道读取完成消息,connection->inputPublisher.receiveConsumerResponse();完成该功能;

    2.处理这个完成消息,finishDispatchCycleLocked完成该功能;

    3.如果没有读到新的消息,则退出该循环。

    所以首先我们看一下如何接受消息的,这里的代码仍然和APP侧类似,通过receiveConsumerResponse方法实现,代码如下:

    1. android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
    2. ...
    3. InputMessage msg;
    4. status_t result = mChannel->receiveMessage(&msg);
    5. if (result) {
    6. return android::base::Error(result);
    7. }
    8. if (msg.header.type == InputMessage::Type::FINISHED) {
    9. return Finished{
    10. .seq = msg.header.seq,
    11. .handled = msg.body.finished.handled,
    12. .consumeTime = msg.body.finished.consumeTime,
    13. };
    14. }
    15. if (msg.header.type == InputMessage::Type::TIMELINE) {
    16. return Timeline{
    17. .inputEventId = msg.body.timeline.eventId,
    18. .graphicsTimeline = msg.body.timeline.graphicsTimeline,
    19. };
    20. }
    21. ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
    22. mChannel->getName().c_str(), ftl::enum_string(msg.header.type).c_str());
    23. return android::base::Error(UNKNOWN_ERROR);
    24. }

    其实就是通过通道来读取,如果读取不到内容,则返回ERROR。

    然后,我们在看一下如何消费完成消息的,finishDispatchCycleLocked方法代码如下:

    1. void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
    2. const sp& connection, uint32_t seq,
    3. bool handled, nsecs_t consumeTime) {
    4. ...
    5. auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {
    6. doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);
    7. };
    8. postCommandLocked(std::move(command));
    9. }

    postCommandLocked中就是把任务加入到mCommandQueue集合中,这时候,就会回到InputDispatcher的线程中去执行了。这里,我们在第三章的开头有介绍过,会在dispatchOnce中的第二步haveCommandsLocked()方法中去执行该任务。

    接下来,我们看一下具体任务的处理方法:doDispatchCycleFinishedCommand,该方法具体执行逻辑如下图所示:

    主要的做了如下的逻辑:

    1.寻找seq对应的节点,如果找不到,则直接返回。

    2.判断整个事件流程的执行时间,如果超过2秒,打印相关日志。

    3.把该事件从waitQueue队列中移除,因为事件处理已经完成。

    4.把该事件从mAnrTimeouts队列中移出,事件处理完成,不需要进行ANR监听了。

    5.释放掉该节点对象。

    六.扩展问题

    问题1:WindowInputEventReceiver中的onInputEvent方法,是运行在主线程还是子线程?

    分析:一般情况下理解,应该是子线程,因为主线程不会一直阻塞等待来自系统侧的消息。但是事实确实很出乎我所料,如下图所示,竟然是主线程的回调。

    其原因就是在于基于Looper的FD监听机制。上面第四章的时候讲到,APP侧android_view_InputEventReceiver.cpp类在初始化的时候,会注册一个基于looper的FD监听。注册了之后如果这个FD收到传递过来的消息,就会自动唤醒主线程。然后在主线程就会通过其逻辑读取这个输入的信号。

    答:运行在主线程

    问题2:ANR超时的5S是在何处定义的?

    答:设置超时时间值的时候,是当前时间+超时时间,所以我们只要获取这个timeout时间即可。

    1. const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
    2. dispatchEntry->timeoutTime = currentTime + timeout.count();

    我们看一下getDispatchingTimeoutLocked方法:

    1. std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
    2. const sp<Connection>& connection) {
    3. if (connection->monitor) {
    4. return mMonitorDispatchingTimeout;
    5. }
    6. const sp<WindowInfoHandle> window =
    7. getWindowHandleLocked(connection->inputChannel->getConnectionToken());
    8. if (window != nullptr) {
    9. return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
    10. }
    11. return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
    12. }

    如代码中所示,会优先使用WindowInfoHandler中的数值,不过这个值最终也是5S。

    如果没有设置,则使用DEFAULT_INPUT_DISPATCHING_TIMEOUT。其定义如下:

    1. InputDispatcher.cpp
    2. const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::milliseconds(
    3. android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
    4. HwTimeoutMultiplier());
    5. UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS=5000

    七.声明

    本身主要参考了努比亚技术团队的文章,由于其原文章描述较为粗略,所以本文算是对其整个原理介绍流程的一个细化。由衷感谢努比亚技术团队的技术分享。

    https://www.jianshu.com/p/386bbb5fa29a

    本文基于AOSP中Android13的最新代码的进行的分析,文章中得出来的结论大多属于作者的个人分析,如有分析错误的地方,欢迎指出。

  • 相关阅读:
    nosql期末
    黑马程序员Java数据结构与java算法笔记(1)
    关于MySQL日期函数你不知道的用法
    win10查看wifi密码
    Android-Jetpack Compose的简单运用
    面试官:IoC 和 DI 有什么区别?
    idea中往git上上传项目步骤
    不容错过的用户标签全面解读。建议收藏!
    python中实现定时任务的几种方案
    做题杂记333
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/126795965