Android 7系统输入(四):InputDispatcher — 事件分发与ANR超时机制

系列目录 :第一篇:从硬件到应用的事件旅程 | 第二篇:EventHub --- 原始事件的采集者 | 第三篇:InputReader --- 原始事件到Android事件的转换引擎 | 第四篇:InputDispatcher --- 事件分发与ANR超时机制 | 第五篇:应用侧 --- InputChannel、ViewRootImpl与事件消费


一、InputDispatcher 的位置与职责

复制代码
InputReader  →  InputDispatcher  →  APP (ViewRootImpl)
 NotifyArgs         ▲               InputMessage (socket)
                本篇聚焦

InputDispatcher 是输入系统中最核心也最容易出问题的环节。它的核心职责:

  1. 接收加工后的事件 :从 InputReader 拿到 NotifyKeyArgs / NotifyMotionArgs
  2. 确定目标窗口:根据焦点窗口或触摸坐标找到目标窗口
  3. 通过 InputChannel 发送:利用 socket pair 跨进程发送事件
  4. 超时监控与 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) {
        // 等待事件被消费
        ...
    }
}

八、总结

模块 关键机制