10.1 Dispatcher的创建时机
InputDispatcher是在InputManagerService初始化时创建的。具体来说,是在NativeInputManager的构造函数里。
我们来看一下关键的创建流程:
// frameworks/native/services/inputflinger/InputManager.cpp InputManager::InputManager( const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { // 创建Dispatcher mDispatcher = new InputDispatcher(dispatcherPolicy); // 创建Reader mReader = new InputReader(readerPolicy, mDispatcher); // 启动两个线程 initialize(); }这里有个细节,我建议你注意一下:InputReader的构造函数里传入了mDispatcher。这意味着什么?意味着Reader在创建时就已经持有了Dispatcher的引用。Reader加工完事件后,直接调用Dispatcher的方法把事件塞进去。这个设计说白了就是——让生产者知道消费者在哪,省去了中间层的转发开销。
核心要点:InputDispatcher和InputReader是同时创建的,但它们的线程是分别启动的。Dispatcher的线程叫"InputDispatcher",Reader的线程叫"InputReader"。两个线程通过内部管道通信。
10.2 线程启动:从initialize()到threadLoop()
创建完Dispatcher和Reader之后,initialize()方法会启动两个线程。我们重点关注Dispatcher线程的启动:
// frameworks/native/services/inputflinger/InputManager.cpp void InputManager::initialize() { // 启动Dispatcher线程 mDispatcherThread = new InputDispatcherThread(mDispatcher); // 启动Reader线程 mReaderThread = new InputReaderThread(mReader); }这两个线程类都继承自Thread基类。Android的Thread封装了一个threadLoop()虚函数,线程启动后会不断调用这个函数,直到它返回false。
我们来看看InputDispatcherThread的threadLoop:
// frameworks/native/services/inputflinger/InputDispatcher.cpp bool InputDispatcherThread::threadLoop() { mDispatcher->dispatchOnce(); return true; // 返回true表示继续循环 }看到了吗?整个线程循环的核心就是一行代码:dispatchOnce()。每次循环调用一次,然后返回true继续下一次。就这么简单。
我曾经在调试一个触摸卡顿问题时,在这个循环里加过日志。你猜怎么着?正常情况下,一次dispatchOnce()的执行时间在微秒级别。但如果某个App的窗口卡住了,这个时间会飙升到几十毫秒。嗯,这就是为什么系统会触发ANR——因为Dispatcher被阻塞了。
10.3 dispatchOnce():一次分发的完整生命周期
好,现在进入正题。dispatchOnce()是InputDispatcher的核心函数,它完成一次事件分发的所有工作。我们来看它的源码:
// frameworks/native/services/inputflinger/InputDispatcher.cpp void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // 加锁区域 AutoMutex _l(mLock); // 1. 检查是否有命令需要执行 if (!mCommandQueue.isEmpty()) { dispatchOnceInnerLocked(&nextWakeupTime); } // 2. 检查是否有事件需要分发 if (mPendingEvent != nullptr) { dispatchOnceInnerLocked(&nextWakeupTime); } } // 3. 等待下一次唤醒 nsecs_t currentTime = now(); nsecs_t sleepDuration = nextWakeupTime - currentTime; if (sleepDuration > 0) { mLooper->pollOnce(sleepDuration); } }这个函数的结构其实很清晰,我把它拆成三步:
| 步骤 | 做什么 | 说明 |
|---|---|---|
| 1 | 处理命令队列 | 执行之前排队的命令,比如窗口注册、焦点切换等 |
| 2 | 分发待处理事件 | 从队列中取出一个事件,分发给目标窗口 |
| 3 | 休眠等待 | 计算下次需要唤醒的时间,然后休眠 |
我的经验:很多初学者会忽略第一步——命令队列。其实命令队列处理的是"非事件"的工作,比如窗口注册、输入法切换、焦点变化等。这些操作如果不及时处理,会导致事件分发到错误的窗口。我曾经遇到过一个bug,就是命令队列积压导致焦点切换延迟,触摸事件发到了上一个窗口。
10.4 dispatchOnceInnerLocked():真正的分发逻辑
dispatchOnceInnerLocked()是dispatchOnce()的内部实现,它在锁的保护下执行。我们来看看它做了什么:
// frameworks/native/services/inputflinger/InputDispatcher.cpp void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { // 1. 先处理命令 if (!mCommandQueue.isEmpty()) { CommandEntry* command = mCommandQueue.dequeueAtHead(); command->command(); command->release(); return; } // 2. 如果没有待处理事件,尝试从队列取一个 if (mPendingEvent == nullptr) { mPendingEvent = mInboundQueue.dequeueAtHead(); if (mPendingEvent != nullptr) { // 处理ANR超时 resetANRTimeoutsLocked(); } } // 3. 如果取到了事件,开始分发 if (mPendingEvent != nullptr) { dispatchEventLocked(mPendingEvent, nextWakeupTime); mPendingEvent = nullptr; } }这里有个关键点:命令的优先级高于事件。为什么?因为命令通常涉及窗口注册、焦点切换等元操作。如果不先处理这些,事件可能发错地方。
你想想看,如果用户刚点了一个按钮,弹出了一个新窗口,然后紧接着又触摸了屏幕。如果焦点切换的命令还没执行,Dispatcher可能把触摸事件发给了旧窗口。这就会导致用户体验的割裂感。
10.5 休眠与唤醒机制
最后,我们聊聊dispatchOnce()里的休眠逻辑。为什么需要休眠?因为如果没有事件,Dispatcher线程不能空转,否则会浪费CPU。
休眠时间的计算逻辑是这样的:
- 如果有待处理事件:
nextWakeupTime设为0,表示立即唤醒,不休眠 - 如果有ANR超时:
nextWakeupTime设为ANR超时时间,到点唤醒处理ANR - 如果什么都没有:
nextWakeupTime保持LONG_LONG_MAX,表示一直休眠直到被外部唤醒
外部唤醒是通过Looper的wake()方法实现的。当InputReader写入新事件时,会调用mLooper->wake(),把Dispatcher从休眠中唤醒。
注意:这里有一个常见的坑。如果dispatchOnce()在处理事件时发生了死锁或者长时间阻塞,那么整个输入系统就会卡住。因为Dispatcher线程是单线程模型,一次只能处理一个事件。我曾经在项目中遇到过,某个App的窗口响应超时,导致Dispatcher线程在等待窗口回复时被阻塞了500ms,结果所有后续的触摸事件都延迟了。嗯,这就是ANR的根源之一。
10.6 小结
好了,我们来总结一下这一章的核心内容:
- Dispatcher的创建:在InputManager的构造函数中创建,与Reader同时初始化
- 线程启动:通过
initialize()启动DispatcherThread,线程循环调用dispatchOnce() - dispatchOnce():一次分发的完整生命周期,包括处理命令、分发事件、休眠等待
- dispatchOnceInnerLocked():在锁保护下执行真正的分发逻辑,命令优先于事件
- 休眠机制:通过Looper的pollOnce实现,没有事件时休眠,有事件时被唤醒
下一章,我们会深入dispatchEventLocked(),看看事件是怎么从Dispatcher发送到目标窗口的。那里才是真正的"分发"细节,包括ANR超时、窗口查找、事件序列化等。我们到时候见。
一句话总结:InputDispatcher的线程循环就是一个不断调用dispatchOnce()的无限循环。每次循环要么处理命令,要么分发事件,要么休眠等待。简单,但高效。