news 2026/10/8 7:01:28

10、InputDispatcher线程启动:Dispatcher的创建与线程循环,dispatchOnce()函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
10、InputDispatcher线程启动:Dispatcher的创建与线程循环,dispatchOnce()函数

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 小结

好了,我们来总结一下这一章的核心内容:

  1. Dispatcher的创建:在InputManager的构造函数中创建,与Reader同时初始化
  2. 线程启动:通过initialize()启动DispatcherThread,线程循环调用dispatchOnce()
  3. dispatchOnce():一次分发的完整生命周期,包括处理命令、分发事件、休眠等待
  4. dispatchOnceInnerLocked():在锁保护下执行真正的分发逻辑,命令优先于事件
  5. 休眠机制:通过Looper的pollOnce实现,没有事件时休眠,有事件时被唤醒

下一章,我们会深入dispatchEventLocked(),看看事件是怎么从Dispatcher发送到目标窗口的。那里才是真正的"分发"细节,包括ANR超时、窗口查找、事件序列化等。我们到时候见。

一句话总结:InputDispatcher的线程循环就是一个不断调用dispatchOnce()的无限循环。每次循环要么处理命令,要么分发事件,要么休眠等待。简单,但高效。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 7:01:18

工业级电源路径保护:TPS259483与PIC18F87K22协同设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 6:56:50

把 GEO / AI 落地工作台拆成五层:工具能解决什么、不能解决什么、什么时候才需要私有化部署

先说结论&#xff1a;工具只是工作台&#xff0c;不是结果制造机。 选型之前&#xff0c;先把一条完整链路拆开看——哪些层可以用工具提效、哪些层必须由人判断、哪些层一旦涉及内部系统和客户数据就要单独评估权限与边界。 顺序反了&#xff0c;最常见的结局是&#xff1a;工…

作者头像 李华
网站建设 2026/10/8 6:56:36

5款AI写论文哪个好?拆开论文写作的“流水线”看工具分工|云智变AI官网www.yunzhibian.cn|微信公众号搜一搜 云智变ai学术

你搜“5款AI写论文哪个好”&#xff0c;大概率会看到一堆横向测评&#xff0c;把ChatGPT、DeepSeek、Kimi、豆包、Gemini放在同一张表里比谁生成的段落更流畅。 这个比法本身就错了。因为它们根本不在同一条流水线上。 一篇毕业论文的写作&#xff0c;实际上包含四道工序&…

作者头像 李华