
9.1 为什么需要批量处理我个人习惯在分析源码前先想清楚设计意图。批量处理的好处其实就三点减少IPC次数跨进程通信是昂贵的攒一批发一次能省不少开销保证事件顺序同一批次的事件在同一个循环里处理不会出现乱序降低抖动避免频繁唤醒InputDispatcher减少调度延迟我在项目中遇到过一个问题某个定制设备上触摸滑动总是卡顿。后来发现就是事件没做批量处理每个MotionEvent都单独发导致InputDispatcher忙不过来。嗯加了批量后问题就解决了。9.2 QueuedInputListener的结构先看代码。QueuedInputListener定义在InputListener.h里其实很简单// frameworks/native/services/inputflinger/include/InputListener.h class QueuedInputListener : public InputListenerInterface { public: explicit QueuedInputListener(const spInputListenerInterface innerListener); virtual void notifyKeyEvent(const NotifyKeyEventArgs* args); virtual void notifyMotionEvent(const NotifyMotionEventArgs* args); // ... 其他通知方法 void flush(); private: spInputListenerInterface mInnerListener; std::vectorNotifyArgs mArgsQueue; };看到没核心就两个成员mInnerListener真正的下游监听器也就是InputDispatchermArgsQueue一个vector用来暂存事件当InputReader处理完一个事件调用notifyKeyEvent或notifyMotionEvent时QueuedInputListener并不会立刻转发而是把事件参数拷贝到队列里。9.3 事件入队的过程我们来看一个具体的例子。假设InputReader处理了一个触摸事件// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::processEventsLocked(...) { // ... 处理原始事件生成NotifyMotionArgs // 注意这里不是直接调用mQueuedListener-notifyMotionEvent // 而是先入队 mQueuedListener-notifyMotionEvent(args); }那notifyMotionEvent内部做了什么// frameworks/native/services/inputflinger/InputListener.cpp void QueuedInputListener::notifyMotionEvent(const NotifyMotionEventArgs* args) { mArgsQueue.push_back(NotifyArgs(*args)); }就一行代码——把参数拷贝到vector里。没有IPC没有锁竞争非常轻量。小提示这里有个细节NotifyArgs是基类NotifyKeyEventArgs、NotifyMotionEventArgs都是它的子类。vector里存的是基类对象所以需要做一次拷贝构造。我建议你去看一下NotifyArgs的拷贝构造函数它内部用了深拷贝确保事件数据完整。9.4 批量刷出的时机事件攒够了什么时候发出去答案是每次InputReader循环结束的时候。看这个关键函数// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::loopOnce() { // 1. 从设备读取原始事件 // 2. 处理事件生成上层事件 // 3. 事件通过QueuedInputListener入队 // 4. 关键步骤刷出队列 mQueuedListener-flush(); }flush()的实现void QueuedInputListener::flush() { // 遍历队列逐个转发给真正的监听器 for (const auto args : mArgsQueue) { switch (args.getType()) { case NotifyArgs::Type::KEY: mInnerListener-notifyKeyEvent( static_castconst NotifyKeyEventArgs*(args)); break; case NotifyArgs::Type::MOTION: mInnerListener-notifyMotionEvent( static_castconst NotifyMotionEventArgs*(args)); break; // ... 其他类型 } } // 清空队列 mArgsQueue.clear(); }这里有个重要的设计点flush()是在InputReader线程里同步调用的。也就是说事件转发给InputDispatcher时InputReader线程会阻塞直到InputDispatcher处理完这批事件。注意我曾经踩过一个坑。在某个低端设备上InputDispatcher处理事件太慢导致InputReader的flush()卡住进而影响了下一个循环的读取。结果就是触摸响应变慢。后来通过调整批量大小和优先级才缓解了这个问题。9.5 批量大小的控制你可能会问一次批量处理多少个事件合适其实Android没有硬编码的批量大小限制。它取决于InputReader一次循环能读到多少原始事件。举个例子如果设备上报率是60Hz一次循环可能只读到1-2个事件如果设备上报率是240Hz一次循环可能读到4-5个事件如果设备突然爆发一批事件比如快速滑动一次循环可能读到10个事件所以批量大小是动态的由硬件上报速率和系统负载共同决定。场景典型批量大小说明普通点击1-2个按下抬起通常分两次循环慢速滑动2-3个手指移动较慢事件间隔大快速滑动5-10个手指快速移动事件密集游戏场景10-20个高刷新率屏幕快速操作9.6 为什么不用环形缓冲区看到vector有些同学可能会问为什么不用环形缓冲区那样性能不是更好吗嗯这个问题我当初也想过。后来看了源码才明白事件数量不确定环形缓冲区需要固定大小但事件数量是动态的拷贝成本不高NotifyArgs只是参数结构体不是完整的事件对象拷贝很快清空方便vector的clear()直接重置size不需要维护读写指针说白了这里用vector是够用且简单的选择。Android源码里很多地方都是这样不追求极致性能而是追求代码清晰和可维护性。9.7 避坑指南最后分享几个我实际工作中遇到的坑不要跳过QueuedInputListener有些定制ROM为了优化直接让InputReader调用InputDispatcher。结果就是事件乱序、丢事件。批量处理不是可有可无的它是保证事件顺序的关键。注意flush的线程安全QueuedInputListener不是线程安全的。InputReader必须在自己的线程里调用flush不能跨线程。我见过有人试图在另一个线程里手动触发flush结果导致vector迭代器失效。批量事件的内存管理如果事件队列积压太多比如InputDispatcher卡住了mArgsQueue会不断增长。这时候要监控内存使用必要时丢弃旧事件。Android 12以后加入了事件丢弃机制就是应对这种情况的。核心总结QueuedInputListener是InputReader和InputDispatcher之间的缓冲区。它把零散的事件攒成一批在每次循环结束时统一转发。这个设计减少了IPC次数保证了事件顺序是Input系统高性能的关键一环。