根据日常的经验我们大概知道,如果 app 没有及时消费 MotionEvent,超过 5s 就会弹出 ANR 对话框;那么 ANR 的逻辑肯定是在事件分发过程中产生的,我们从事件的源头找起,看看 input 事件是怎么产生的

左边是 input 分发的泳道图
事件分发是从线程 InputReaderThread 开始的,它的主要工作是:
/dev/input 获取 input event
epoll 监听多个 fdEventHub.getEvents() 从 fd 读取 input_event 并转换为 RawEvent(看来输入设备的驱动都需要构造 input_event 数据结构给系统)mInboundQueue(等待 InputDispatcher 分发)
InputDevice 将 RawEvent 交由各种 InputMapper 处理,例如:KeyboardInputMapper 将 RawEvent 包装为 NotifyKeyArgs,TouchInputMapper 将 RawEvent 包装为 NotifyMotionArgs/**
* @param fd 需要监听的文件描述符
* @param ident 表示为当前发生事件的标识符,必须 >= 0,或者为 POLL_CALLBACK(-2) 如果指定了 callback
* @param events 表示为要监听的文件类型,默认是 EVENT_INPUT
* @param callback 当有事件发生时,会回调该 callback 函数
* @param data
*
* 主要做了两件事:
* 1,把输入参数构造成 Request,添加到 mRequests
* 2,将 fd 添加到 epoll
*
*/
int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data);
int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);
这里需要补充下 Native Looper 不同于 Java Looper 的地方:提供了监听文件描述符的机制
// pollAll() -> pollOnce() -> pollInner()
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis)
// ...
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd.get()) {
// ...
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
// ...
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
// ...
}
}
}
// ...
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd, response.request.seq);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = POLL_CALLBACK;
}
}
它有两种使用方式:
Looper.wake() 也会触发 callback 执行// pollAll() -> pollOnce()
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != nullptr) *outFd = fd;
if (outEvents != nullptr) *outEvents = events;
if (outData != nullptr) *outData = data;
return ident;
}
}
// ...
}
pollOnce() 会返回一个 ident,调用者会判断该 ident 是否等于自己需要处理的事件 ident,如果是的话,则开始处理事件
在分析事件分发的逻辑之前,我们先看看 ui 线程是怎么接收 input 事件的,从下图可以看到在 native 层打开了一对 socket,server socket fd 给 InputDispatcher 线程,client socket fd 给 ui 线程(ViewRootImpl.mInputChannel),也就是说它们之间通过 socket 双向通讯

InputMessage 传递给 java 层处理InputStage 责任链的处理,最终到达我们最熟悉的 View.dispatchTouchEvent
一个 input event 的生命流程大概是这样的:
InputReader 放入 InputDispatcher.mInboundQueue 等待分发InputDispatcher 将其移入 window 对应的 Connection→outboundQueue 等待发送Connection.waitQueue 等待 ui 线程的确认应答Connection.waitQueue产生 ANR 的逻辑就在 dispatchKeyLocked(分发一个 input event 的过程)

1,findFocusedWindowTargetsLocked 找到 input event 的分发 window 对象,然后 checkWindowReadyForMoreInputLocked 检查 widnow 是否可以接收 input event,这里截取一段检查逻辑
2,最后走到 AppErrors.handleShowAnrUi 里就是弹出 ANR dialog 的地方
// outboundQueue 不为空说明此 window 仍有 input event 未发送,waitQueue 不为空说明有 input event 在消费中(未收到消费完成的响应)
// 也就是说 input event 必须是按顺序分发和消费的,不能乱序
if (eventEntry->type == EventEntry::TYPE_KEY) {
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return StringPrintf("Waiting to send key event because the %s window has not "
"finished processing all of the input events that were previously "
"delivered to it. Outbound queue length: %d. Wait queue length: "
"%d.",
targetType, connection->outboundQueue.count(),
connection->waitQueue.count());
}
} else {
if (!connection->waitQueue.isEmpty() &&
currentTime >= connection->waitQueue.head->deliveryTime + STREAM_AHEAD_EVENT_TIMEOUT) {
return StringPrintf("Waiting to send non-key event because the %s window has not "
"finished processing certain input events that were delivered to "
"it over "
"%0.1fms ago. Wait queue length: %d. Wait queue head age: "
"%0.1fms.",
targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
connection->waitQueue.count(),
(currentTime - connection->waitQueue.head->deliveryTime) *
0.000001f);
}
}
总结
InputReader 负责监听 input fd,InputDispatcher 负责分发给 ui,ui 消费并反馈给 InputDispatchermInboundQueue 加锁使用即可;InputDispatcher 和 ui 在不同的进程,通过 socket 通讯mInboundQueue(待分发)、outboundQueue(待发送) 和 waitQueue(待响应) 之间流转