10.1 Dispatcher的创建时机InputDispatcher是在InputManagerService初始化时创建的。具体来说是在NativeInputManager的构造函数里。我们来看一下关键的创建流程// frameworks/native/services/inputflinger/InputManager.cpp InputManager::InputManager( const spInputReaderPolicyInterface readerPolicy, const spInputDispatcherPolicyInterface dispatcherPolicy) { // 创建Dispatcher mDispatcher new InputDispatcher(dispatcherPolicy); // 创建Reader mReader new InputReader(readerPolicy, mDispatcher); // 启动两个线程 initialize(); }这里有个细节我建议你注意一下InputReader的构造函数里传入了mDispatcher。这意味着什么意味着Reader在创建时就已经持有了Dispatcher的引用。Reader加工完事件后直接调用Dispatcher的方法把事件塞进去。这个设计说白了就是——让生产者知道消费者在哪省去了中间层的转发开销。核心要点InputDispatcher和InputReader是同时创建的但它们的线程是分别启动的。Dispatcher的线程叫InputDispatcherReader的线程叫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()的无限循环。每次循环要么处理命令要么分发事件要么休眠等待。简单但高效。
阅读完成 · 觉得有帮助?