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方法,代码如下:

inputManager = new InputManagerService(context);
...
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
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事件,加入到结构体中:

                     size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        event->when = processEventTimestamp(iev);
                        event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                        capacity -= 1;
                    }

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

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

3.分发事件

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

if (count) {
            processEventsLocked(mEventBuffer, count);
        }

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

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方法来负责的。

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

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

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

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

nsecs_t InputDispatcher::processAnrsLocked() {
    const nsecs_t currentTime = now();
    nsecs_t nextAnrCheck = LONG_LONG_MAX;
    // Check if we are waiting for a focused window to appear. Raise ANR if waited too long
    if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
        if (currentTime >= *mNoFocusedWindowTimeoutTime) {
            //场景1
            processNoFocusedWindowAnrLocked();
            mAwaitedFocusedApplication.reset();
            mNoFocusedWindowTimeoutTime = std::nullopt;
            return LONG_LONG_MIN;
        } else {
            // Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
            nextAnrCheck = *mNoFocusedWindowTimeoutTime;
        }
    }

    // Check if any connection ANRs are due
    nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
    if (currentTime < nextAnrCheck) { // most likely scenario
        return nextAnrCheck;          // everything is normal. Let's check again at nextAnrCheck
    }

    // If we reached here, we have an unresponsive connection.
    sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
    if (connection == nullptr) {
        ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
        return nextAnrCheck;
    }
    connection->responsive = false;
    // Stop waking up for this unresponsive connection
    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
    //场景2
    onAnrLocked(connection);
    return LONG_LONG_MIN;
}

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唤醒进行响应。

 nsecs_t currentTime = now();
 int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
 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方法。

核心代码如下:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ...
    for (;;) {
        InputEvent* inputEvent;
        //接受输入事件
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        ...
            switch (inputEvent->getType()) {
            ...
            //类型转换
            case AINPUT_EVENT_TYPE_MOTION: {
               ...
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
            ...

            if (inputEventObj) {
               //通知java层方法
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
             ...
        }
    }
}

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这个方法中如下,我们只看最核心的代码:

int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
    std::scoped_lock _l(mLock);
    sp<Connection> connection = getConnectionLocked(connectionToken);
    if (connection == nullptr) {
    
        for (;;) {
            Result<InputPublisher::ConsumerResponse> result =
                    connection->inputPublisher.receiveConsumerResponse();
            
            if (std::holds_alternative<InputPublisher::Finished>(*result)) {
                const InputPublisher::Finished& finish =
                        std::get<InputPublisher::Finished>(*result);
                finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
                                          finish.consumeTime);
            } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
                ...
            }
            gotOne = true;
        }
        if (gotOne) {
            runCommandsLockedInterruptable();
            if (status == WOULD_BLOCK) {
                return 1;
            }
        }
        ...
    return 0; // remove the callback
}

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

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

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

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

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

android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
    ...
    InputMessage msg;
    status_t result = mChannel->receiveMessage(&msg);
    if (result) {
        return android::base::Error(result);
    }
    if (msg.header.type == InputMessage::Type::FINISHED) {
        return Finished{
                .seq = msg.header.seq,
                .handled = msg.body.finished.handled,
                .consumeTime = msg.body.finished.consumeTime,
        };
    }

    if (msg.header.type == InputMessage::Type::TIMELINE) {
        return Timeline{
                .inputEventId = msg.body.timeline.eventId,
                .graphicsTimeline = msg.body.timeline.graphicsTimeline,
        };
    }

    ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
          mChannel->getName().c_str(), ftl::enum_string(msg.header.type).c_str());
    return android::base::Error(UNKNOWN_ERROR);
}

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

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

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                const sp<Connection>& connection, uint32_t seq,
                                                bool handled, nsecs_t consumeTime) {
    ...
    auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {
        doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);
    };
    postCommandLocked(std::move(command));
}

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时间即可。

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

我们看一下getDispatchingTimeoutLocked方法:

std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
        const sp<Connection>& connection) {
    if (connection->monitor) {
        return mMonitorDispatchingTimeout;
    }
    const sp<WindowInfoHandle> window =
            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
    if (window != nullptr) {
        return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
    }
    return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}

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

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

InputDispatcher.cpp
const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::milliseconds(
        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
        HwTimeoutMultiplier());

UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS=5000

七.声明

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

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

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

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt 的事件循环是一个非常重要的机,它负责接收和分发事件,保证 Qt 应用程序的正常运行。下面是简单的 Qt 事件循环源码分析: 1. Qt 的事件循环是通过 `QCoreApplication::exec()` 方法启动的。该方法首先会创建一个 `QEventLoop` 对象,然后进入一个无限循环。 2. 在事件循环中,`QEventLoop` 对象通过调用 `QCoreApplication::processEvents()` 方法来处理当前队列中的事件。该方法会检查是否有待处理事件,如果没有,则线程会进入休眠状态,等待新的事件到来。 3. 当一个事件到来时,Qt 会根据事件的类型和目标对象,将事件分发给正确的接收者进行处理。接收者可以是窗口部件、控件、布局等。 4. 对于每个事件,Qt 会调用接收者的对应方法来处理。例如,对于鼠标点击事件,Qt 会调用接收者的 `mousePressEvent()` 方法来处理。 5. 在事件处理过程中,如果需要进行其他操作(如更新界面、执行定时器等),Qt 会将这些操作添加到事件队列中。 6. 当所有待处理事件都被处理完毕后,Qt 会通过调用 `QCoreApplication::quit()` 方法退出事件循环,程序结束运行。 需要注意的是,Qt 的事件循环并不是单线程的。在多线程环境下,每个线程都可以有自己的事件循环,但每个线程只能有一个事件循环。当一个事件需要跨线程传递时,Qt 会通过事件队列和线程间的信号槽机来实现。 以上是简单的 Qt 事件循环源码分析,如果您对具体的源码细节有更深入的需求,建议参考 Qt 的官方文档和源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值