系列目录 :第一篇:从硬件到应用的事件旅程 | 第二篇:EventHub --- 原始事件的采集者 | 第三篇:InputReader --- 原始事件到Android事件的转换引擎 | 第四篇:InputDispatcher --- 事件分发与ANR超时机制 | 第五篇:应用侧 --- InputChannel、ViewRootImpl与事件消费
一、InputDispatcher 的位置与职责
InputReader → InputDispatcher → APP (ViewRootImpl)
NotifyArgs ▲ InputMessage (socket)
本篇聚焦
InputDispatcher 是输入系统中最核心也最容易出问题的环节。它的核心职责:
- 接收加工后的事件 :从 InputReader 拿到
NotifyKeyArgs/NotifyMotionArgs - 确定目标窗口:根据焦点窗口或触摸坐标找到目标窗口
- 通过 InputChannel 发送:利用 socket pair 跨进程发送事件
- 超时监控与 ANR:跟踪每个事件的分发时间,超时触发 ANR
源码位置:
frameworks/native/services/inputflinger/InputDispatcher.cpp
frameworks/native/services/inputflinger/InputDispatcher.h
frameworks/native/services/inputflinger/InputTransport.cpp // InputChannel
本文中所有 C++ 代码块如未特别标注,均来自 InputDispatcher.cpp。Java 代码块来自
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java。
二、InputDispatcher 的线程模型
cpp
void InputDispatcher::start() {
mThread = new InputDispatcherThread(*this);
mThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
}
核心循环:
cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
cpp
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{
AutoMutex _l(mLock);
mDispatcherIsAlive = true;
// 1. 派发所有待处理的事件
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// 2. 处理命令队列(如 doDispatchCycleFinishedCommand)
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN; // 强制立即处理
}
}
// 3. 等待新事件或超时(ANR 的超时也在这里触发)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
dispatchOnce 的精妙之处在于用一个 Looper 同时完成三件事:等待 InputReader 的新事件、等待 APP 侧的消费回复、等待 ANR 超时的触发。
三、事件分发决策:找到正确的目标窗口
3.1 按键事件 --- 焦点窗口策略
对于按键事件(NotifyKeyArgs),目标窗口由当前焦点窗口决定:
cpp
int32_t InputDispatcher::findFocusedWindowTargetsLocked(
nsecs_t currentTime, const EventEntry* entry,
Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
// 1. 获取当前焦点窗口
sp<InputWindowHandle> focusedWindowHandle = getFocusedWindowHandleLocked();
// 2. 检查焦点窗口是否有效
if (focusedWindowHandle == NULL) {
// 没有焦点窗口 → 丢弃事件
injectionResult = handleTargetsNotReadyLocked(...);
goto Unresponsive;
}
// 3. 检查目标窗口的 InputChannel 是否准备好
sp<InputChannel> inputChannel =
getInputChannelLocked(focusedWindowHandle->getInfo());
if (inputChannel == NULL) {
// 通道不可用 → 丢弃事件
}
// 4. 添加到目标列表
InputTarget target;
target.inputChannel = inputChannel;
target.flags = ...;
inputTargets.push_back(target);
}
3.2 触摸事件 --- 坐标命中策略
对于触摸事件(NotifyMotionArgs),需要通过触摸坐标找到命中的窗口:
cpp
int32_t InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry* entry,
Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {
// 1. 根据 ACTION_DOWN / MOVE / UP 采取不同策略
if (action == AMOTION_EVENT_ACTION_DOWN) {
// DOWN 事件:需要重新寻找目标窗口
// 遍历所有可见窗口,按 Z 序从上到下检查
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
const InputWindowInfo* windowInfo = windowHandle->getInfo();
// 检查触摸坐标是否落在窗口范围内
if (windowInfo->touchableRegionContainsPoint(x, y)) {
// 找到命中窗口!
if (windowInfo->isTrustedOverlay) {
// 受信任的覆盖窗口 → 穿透忽略,继续查找
continue;
}
// 找到目标窗口,停止搜索
newTouchedWindowHandle = windowHandle;
break;
}
}
} else {
// MOVE/UP 事件:沿用 DOWN 时的窗口(保持一致性)
newTouchedWindowHandle = oldTouchedWindowHandle;
}
// 2. 检查目标窗口是否准备好
sp<InputChannel> inputChannel =
getInputChannelLocked(newTouchedWindowHandle->getInfo());
if (inputChannel == NULL) {
// 通道不可用 → 可能需要 ANR
}
// 3. 构建 InputTarget
InputTarget target;
target.inputChannel = inputChannel;
target.flags |= InputTarget::FLAG_DISPATCH_AS_IS;
// 坐标需要从全局坐标系转换到窗口坐标系
target.xOffset = -windowFrameLeft;
target.yOffset = -windowFrameTop;
}
3.3 窗口信息从哪来?
mWindowHandles 中的窗口信息来自 WindowManagerService(WMS):
java
// WMS 通过 Binder 回调更新 InputDispatcher 持有的窗口信息
mInputManager.setInputWindows(windowHandles);
每次窗口布局变化(新增窗口、窗口移动、窗口销毁等),WMS 都会重新计算所有窗口的层级和区域信息,然后通过 setInputWindows() 同步给 InputDispatcher。
四、InputChannel:跨进程事件传输通道
4.1 socket pair 的创建
cpp
// InputTransport.cpp
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& outServerChannel,
sp<InputChannel>& outClientChannel) {
int sockets[2];
// 创建 Unix domain socket 对
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
return -errno;
}
// 设置缓冲区大小
int bufferSize = SOCKET_BUFFER_SIZE; // 默认 32KB
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
// 创建两个 InputChannel 对象
outServerChannel = new InputChannel(serverSideName, sockets[0]);
outClientChannel = new InputChannel(clientSideName, sockets[1]);
return OK;
}
关键决策 :使用 socketpair 而非 Binder,是经过深思熟虑的。Binder 调用涉及线程池调度,延迟不可控。而 socket 一旦建立连接,两端的 send()/recv() 几乎就是内核态内存拷贝,延迟极低且可预测。对输入事件这种延迟敏感的场景,socket 是更优选择。
4.2 事件的序列化:InputMessage
cpp
struct InputMessage {
Header header; // 消息类型和大小
// 消息体(union,同一时间只用一种)
union Body {
struct Key {
uint32_t seq; // 序列号(用于 ANR 追踪)
nsecs_t eventTime;
int32_t deviceId;
int32_t source;
int32_t action;
int32_t keyCode;
int32_t scanCode;
int32_t metaState;
nsecs_t downTime;
} key;
struct Motion {
uint32_t seq;
nsecs_t eventTime;
int32_t deviceId;
int32_t source;
int32_t action;
int32_t pointerCount;
struct Pointer {
PointerProperties properties;
PointerCoords coords;
} pointers[MAX_POINTERS];
nsecs_t downTime;
} motion;
struct Finished {
uint32_t seq;
bool handled; // APP 是否消费了事件
} finished;
};
};
4.3 事件发送
cpp
// 确定目标后,开始分发
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
while (connection->outboundQueue.size() > 0) {
DispatchEntry* dispatchEntry = connection->outboundQueue[0];
// 1. 发布事件到 InputChannel
status_t status = connection->inputPublisher
.publishKeyEvent(dispatchEntry->seq, ...);
// 或 .publishMotionEvent(...)
// 2. 发送 InputMessage 到 socket
status = connection->inputPublisher.sendDispatchSignal();
// 3. 记录分发开始时间(用于 ANR 检测)
dispatchEntry->deliveryTime = currentTime;
dispatchEntry->timeoutTime = currentTime +
(isKeyEvent ? KEY_WAITING_FOR_EVENTS_TIMEOUT
: POINTER_WAITING_FOR_EVENTS_TIMEOUT);
// 4. 移入等待队列
connection->outboundQueue.removeAt(0);
connection->waitQueue.push_back(dispatchEntry);
}
}
4.4 为什么绕过 Binder?
| 对比维度 | Binder | socket pair |
|---|---|---|
| 延迟 | 不确定(线程池调度) | 极低(内核态直接拷贝) |
| 内存 | 多次拷贝 | 最小拷贝 |
| 并发 | 线程池限制 | 无需线程池 |
| 优先级控制 | 一般 | 配合 PRIORITY_URGENT_DISPLAY 保证及时性 |
五、ANR 机制:输入超时的判定与触发
5.1 超时时长的定义
cpp
// InputDispatcher.h
const nsecs_t KEY_WAITING_FOR_EVENTS_TIMEOUT = 500 * 1000000LL; // 500ms
const nsecs_t KEY_WAITING_FOR_FINISHED_TIMEOUT = 500 * 1000000LL; // 500ms
const nsecs_t POINTER_WAITING_FOR_EVENTS_TIMEOUT = 500 * 1000000LL; // 500ms
默认输入事件超时时间为 500ms 。这意味着从 InputDispatcher 发出事件到收到 APP 的 finished 信号,最多只能等 500ms。
5.2 超时检测:dispatchOnce 的巧妙设计
cpp
void InputDispatcher::dispatchOnce() {
// ... 分发事件 ...
// nextWakeupTime 被设置为最早超时的时间点
// 如果所有事件都正常消费,nextWakeupTime = LONG_LONG_MAX
// 如果有事件在等待,nextWakeupTime = 最早超时的 deadline
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
// pollOnce 的 timeout 参数就是到最早超时的时间
// 如果超时了(没有收到 finished 回复),pollOnce 返回 0
mLooper->pollOnce(timeoutMillis);
// 下一轮 dispatchOnce:
// 检查 waitQueue 中有没有超时的事件 → 触发 ANR
}
5.3 ANR 触发流程
cpp
nsecs_t InputDispatcher::processAnrsLocked() {
nsecs_t nextAnrCheck = LONG_LONG_MAX;
// 遍历所有连接的 waitQueue
for (auto& connection : mConnectionsByFd) {
for (auto& entry : connection->waitQueue) {
nsecs_t currentTime = now();
// 检查事件是否超时
if (currentTime >= entry->timeoutTime) {
// 触发 ANR!
sp<InputApplicationHandle> applicationHandle =
connection->inputWindowHandle->inputApplicationHandle;
// Java 层处理 ANR
// → InputManagerService.notifyANR()
// → AMS 显示 ANR 对话框
...
// 设置下次检查时间
nextAnrCheck = LONG_LONG_MIN;
} else if (entry->timeoutTime < nextAnrCheck) {
nextAnrCheck = entry->timeoutTime;
}
}
}
return nextAnrCheck;
}
5.4 ANR 的条件并非只有"超时"
ANR 触发的实际条件是:窗口有焦点,且收到了输入事件,且 5 秒内没有响应 (这是另一个层面的超时,不同于 500ms 的输入超时)。500ms 的输入超时只是触发 InputDispatching TimedOut 的条件之一,最终 ANR 对话框的显示由 AMS 综合判断。
5.5 事件消费回复(Finished Signal)
APP 消费事件后,通过同一个 socket send() 一个 Finished 消息回来:
cpp
// 当 socket fd 变为可读时,InputDispatcher 读取 Finished 消息
void InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
Connection* connection = ...;
// 1. 从 socket 读取 Finished 消息
InputMessage msg;
connection->inputPublisher.receiveFinishedSignal(&msg);
// 2. 加入命令队列
mCommandQueue.push(
new DispatchEntry::Finished(connection, msg.body.finished.seq,
msg.body.finished.handled));
// 3. 唤醒 dispatchOnce 处理这条命令
mLooper->wake();
}
然后在下一次 dispatchOnce 中处理:
cpp
void InputDispatcher::doDispatchCycleFinishedLockedCommand(
DispatchEntry* dispatchEntry, bool handled, nsecs_t currentTime) {
// 从 waitQueue 中移除这个事件
connection->waitQueue.remove(dispatchEntry);
// 事件成功消费 → 不会触发 ANR
}
六、Connection 与事件队列
每个 APP 窗口在 InputDispatcher 中对应一个 Connection 对象:
cpp
struct Connection {
sp<InputChannel> inputChannel; // socket 的 server 端
sp<InputWindowHandle> inputWindowHandle; // 窗口信息
// 三个事件队列
Queue<DispatchEntry*> outboundQueue; // 等待发送
Queue<DispatchEntry*> waitQueue; // 已发送,等待 APP 回复
Queue<DispatchEntry*> inboundQueue; // 暂未使用
};
三个队列的状态流转:
InputReader 生成事件
│
▼
outboundQueue(待发送)
│ startDispatchCycleLocked
│ publishKeyEvent → sendDispatchSignal
▼
waitQueue(已发送,等待 APP 消费)
│ APP 通过 socket 返回 Finished 消息
│ handleReceiveCallback → doDispatchCycleFinishedLockedCommand
▼
事件完成,从 waitQueue 移除
七、事件注入(Event Injection)
除了处理来自 InputReader 的硬件事件,InputDispatcher 还支持软件事件注入,例如:
- Monkey 测试工具注入随机事件
- Instrumentation 测试(
Instrumentation.sendPointerSync()) - 无障碍服务模拟点击
cpp
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
int32_t injectorPid, int32_t injectorUid,
int32_t syncMode, int32_t timeoutMillis,
uint32_t policyFlags) {
// 1. 权限检查
if (!hasInjectionPermission(injectorPid, injectorUid)) {
return INPUT_EVENT_INJECTION_PERMISSION_DENIED;
}
// 2. 创建 EventEntry(绕过 InputReader)
EventEntry* entry = ...;
// 3. 加入待分发队列(mInboundQueue)
mInboundQueue.enqueueAtTail(entry);
// 4. 唤醒 dispatchOnce
mLooper->wake();
// 5. 如果是同步模式,等待注入完成
if (syncMode == INPUT_EVENT_INJECTION_SYNC) {
// 等待事件被消费
...
}
}
八、总结
| 模块 | 关键机制 |
|---|---|