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集合就不会存在超时的问题。
第一部分这块其实并没有什么代码实例,第一部分我们只要这个概念就好了。所以我们直接从第二块开始讲起。
主流程图如下:
首先,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进行下一步处理。
上面有提到,主要通过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;
如果上面读取到的输入事件数量>0,则需要进行下一步的分法操作,分发的方法是processEventsLocked,所以我们主要看一下该方法:
if (count) {
processEventsLocked(mEventBuffer, count);
}
整个流程如下图所示,就不多讲解了:
这个主要是通过观察者模式来实现的,入口方法为:mQueuedListener.flush()。
该流程中,主要是对输入信号根据类型进行处理,处理完成后加入mInboundQueue集合,并且唤醒InputDispatcher中的线程。
InputDispatcher中的线程被唤醒后,会执行dispatchOnce方法。
我们把dispatchOnce方法的流程也主要分为四部分:
1.dispatchOnceInnerLocked方法中,读取mInboundQueue中的输入事件,进行相关的分法;
2.runCommandsLockedInterruptable方法中,主要执行一些任务。这些任务主要是APP侧完成了事件消费后的通知处理。这个,我们在5.2小节中具体讲,这里就先不展开了。
3.processAnrsLocked方法中,根据各种条件进行判断决定是否要进入ANR流程;
4.计算出下一次的触发时间,通过mLooper->pollOnce(timeoutMillis);方法设置最长休眠时间进入等待。
输入事件处理主要是在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管道。
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 = 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触发的时间进行返回,让线程休眠到下一次的触发时间。
这块逻辑比较简单,获取到下一次的检查时间,然后进入休眠状态,直到下一次的检查时间到来。
如果中间传递过来新的输入事件,则由InputReader唤醒进行响应。
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
上面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中移除该输入事件。
系统侧接受输入事件处理完成信号的方式和APP侧接受的方式类似,也是基于FD的检测,只不过这一次系统从生产者变成了消费者。
在WindowManagerService创建Window的时候,其实就会创建完成消息的FD注册。
主要流程如下:
这样,如果系统侧的通道收到传入信号时,就会调用InputDispatcher中的handleReceiveCallback方法。
handleReceiveCallback这个方法中如下,我们只看最核心的代码:
int InputDispatcher::handleReceiveCallback(int events, sp connectionToken) {
std::scoped_lock _l(mLock);
sp connection = getConnectionLocked(connectionToken);
if (connection == nullptr) {
for (;;) {
Result result =
connection->inputPublisher.receiveConsumerResponse();
if (std::holds_alternative(*result)) {
const InputPublisher::Finished& finish =
std::get(*result);
finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
finish.consumeTime);
} else if (std::holds_alternative(*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::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, 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.释放掉该节点对象。
分析:一般情况下理解,应该是子线程,因为主线程不会一直阻塞等待来自系统侧的消息。但是事实确实很出乎我所料,如下图所示,竟然是主线程的回调。
其原因就是在于基于Looper的FD监听机制。上面第四章的时候讲到,APP侧android_view_InputEventReceiver.cpp类在初始化的时候,会注册一个基于looper的FD监听。注册了之后如果这个FD收到传递过来的消息,就会自动唤醒主线程。然后在主线程就会通过其逻辑读取这个输入的信号。
答:运行在主线程
答:设置超时时间值的时候,是当前时间+超时时间,所以我们只要获取这个timeout时间即可。
const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
dispatchEntry->timeoutTime = currentTime + timeout.count();
我们看一下getDispatchingTimeoutLocked方法:
std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
const sp& connection) {
if (connection->monitor) {
return mMonitorDispatchingTimeout;
}
const sp 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的最新代码的进行的分析,文章中得出来的结论大多属于作者的个人分析,如有分析错误的地方,欢迎指出。