尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
17、触摸事件分发流程:MotionEvent的封装,从InputDispatcher到ViewRootImpl
17.1 事件封装的起点InputDispatcher好我们直接进入正题。上一章我们聊到InputDispatcher从队列里取出事件准备发给目标窗口。但这里有个关键动作——事件封装。说白了就是把原始的输入数据包装成一个Java层能识别的MotionEvent对象。我个人习惯把InputDispatcher看作一个“分拣员”。它手里拿着的原始数据是C层的NotifyArgs结构体。里面包含了事件类型、坐标、时间戳、设备ID等等。但这些信息Java层不认识。所以必须做一次“翻译”。这个过程发生在InputDispatcher::dispatchOnceInnerLocked()里。它会调用InputDispatcher::prepareDispatchCycleLocked()然后走到InputChannel::sendMessage()。嗯这里要注意发送的不是MotionEvent本身而是一个InputMessage。核心要点InputDispatcher发送的是序列化后的InputMessage而不是直接传MotionEvent。MotionEvent的创建发生在接收端——也就是ViewRootImpl所在的进程。17.2 跨进程的“快递”InputMessage的结构你想想看InputDispatcher运行在system_server进程而我们的应用进程是独立的。跨进程传递对象必须序列化。InputMessage就是那个“快递包裹”。它的结构大概是这样struct InputMessage { enum class Type : uint32_t { KEY, MOTION, FINISHED, FOCUS, CAPTURE, DRAG, TIMELINE, }; Header header; union { KeyEntry key; MotionEntry motion; FinishedEntry finished; FocusEntry focus; CaptureEntry capture; DragEntry drag; TimelineEntry timeline; } body; };对于触摸事件我们关注的是MotionEntry。它包含了事件类型ACTION_DOWN、ACTION_MOVE等事件发生的时间戳指针数量及每个指针的坐标、压力、大小元键状态shift、ctrl等设备ID、来源source我记得有一次排查一个多点触控的bug发现某个设备上报的指针ID顺序乱了。最后定位到是驱动层的问题但当时我盯着InputMessage里的pointerIds字段看了半天。这个字段在封装时如果处理不当就会出现“指针漂移”的现象。17.3 接收端的重生ViewRootImpl中的MotionEvent创建好包裹送到了。应用进程的InputEventReceiver通过InputChannel读取到InputMessage。然后调用InputEventReceiver::consumeBatchedInputEvents()最终走到NativeInputEventReceiver::handleEvent()。这里有个关键函数NativeInputEventReceiver::consumeEvents()。它会调用InputEvent::create()根据InputMessage的类型创建对应的Java层事件对象。对于触摸事件创建的是MotionEvent。这个过程我建议你重点关注// 伪代码展示核心逻辑 jobject MotionEvent::create(JNIEnv* env, const InputMessage msg) { // 1. 从InputMessage中提取原始数据 nsecs_t eventTime msg.body.motion.eventTime; int32_t action msg.body.motion.action; int32_t pointerCount msg.body.motion.pointerCount; // 2. 构造PointerProperties数组 PointerProperties pointerProperties[pointerCount]; for (int i 0; i pointerCount; i) { pointerProperties[i].id msg.body.motion.pointerIds[i]; pointerProperties[i].toolType msg.body.motion.toolTypes[i]; } // 3. 构造PointerCoords数组 PointerCoords pointerCoords[pointerCount]; for (int i 0; i pointerCount; i) { pointerCoords[i].x msg.body.motion.pointers[i].x; pointerCoords[i].y msg.body.motion.pointers[i].y; pointerCoords[i].pressure msg.body.motion.pointers[i].pressure; pointerCoords[i].size msg.body.motion.pointers[i].size; } // 4. 调用Java层的MotionEvent.obtain()静态方法 return env-CallStaticObjectMethod( gMotionEventClassInfo.clazz, gMotionEventClassInfo.obtain, eventTime, action, pointerCount, pointerProperties, pointerCoords, msg.body.motion.metaState, msg.body.motion.buttonState, msg.body.motion.xPrecision, msg.body.motion.yPrecision, msg.body.motion.deviceId, msg.body.motion.edgeFlags, msg.body.motion.source, msg.body.motion.flags ); }避坑指南我曾经遇到过一个性能问题每次触摸事件都卡顿。后来发现是MotionEvent.obtain()频繁创建对象导致GC。实际上Android系统内部维护了一个MotionEvent的对象池obtain()会优先从池中复用。但如果你的代码里手动调用了MotionEvent.obtain()却没有及时recycle()就会导致池子耗尽频繁触发GC。切记谁obtain谁recycle。17.4 从Native到Java的桥梁InputEventReceiverMotionEvent创建完成后怎么交给ViewRootImpl呢这里有个中间人——InputEventReceiver。在Java层ViewRootImpl内部维护了一个WindowInputEventReceiver它继承自InputEventReceiver。当Native层创建好MotionEvent后会回调Java层的onInputEvent()方法。流程是这样的Native层调用InputEventReceiver.dispatchInputEvent()JNI方法Java层的dispatchInputEvent()被调用参数就是刚刚创建的MotionEvent该方法内部调用onInputEvent()由WindowInputEventReceiver实现WindowInputEventReceiver将事件加入队列等待处理嗯这里要注意一个细节。事件并不是直接交给ViewRootImpl的。WindowInputEventReceiver内部有一个InputStage链。事件会经过多个处理阶段阶段名称作用ViewPreImeInputStage在输入法之前处理比如处理一些系统按键ImeInputStage交给输入法处理ViewPostImeInputStage输入法处理完后交给View层次SyntheticInputStage处理一些合成事件比如长按、双击等对于触摸事件最终会走到ViewPostImeInputStage的processPointerEvent()方法。这里会调用ViewRootImpl::enqueueInputEvent()把事件正式交给ViewRootImpl的事件队列。17.5 ViewRootImpl的最终处理事件入队与分发到了ViewRootImpl这里事件已经是一个完整的MotionEvent对象了。但ViewRootImpl不会立刻处理它。它会先把事件加入一个mPendingInputEvents队列然后通过scheduleConsumeBatchedInput()发起一个异步任务。为什么这么设计说白了是为了批量处理。如果用户快速滑动屏幕会产生大量MotionEvent。如果每个事件都立即处理UI线程会忙不过来。所以系统会把多个事件攒起来在下一个Vsync信号到来时一次性处理。最终在doConsumeBatchedInput()方法中ViewRootImpl会从队列中取出事件调用deliverInputEvent()。这个方法会找到当前拥有焦点的View或者根据坐标找到目标View然后调用View.dispatchTouchEvent()。至此事件从InputDispatcher到ViewRootImpl的旅程就结束了。接下来就是View层次内部的分发逻辑——那是下一章的内容。注意事项MotionEvent在跨进程传递时坐标是相对于屏幕的绝对坐标。但到了ViewRootImpl后需要转换成相对于窗口的坐标。这个转换发生在ViewRootImpl::deliverInputEvent()中通过mAttachInfo.mWindowLeft和mAttachInfo.mWindowTop进行偏移。如果你在开发中遇到触摸坐标不准的问题先检查一下窗口位置是否正确。17.6 小结回顾一下整个流程InputDispatcher从队列取出事件封装成InputMessageInputMessage通过InputChannel跨进程传递到应用进程Native层的InputEventReceiver解析InputMessage创建MotionEvent对象MotionEvent通过JNI回调到Java层的InputEventReceiver经过InputStage链最终到达ViewRootImpl的事件队列ViewRootImpl在Vsync回调中批量处理事件开始View层次的分发我个人觉得理解这个流程的关键在于“跨进程”和“异步”这两个概念。很多开发者以为触摸事件是实时响应的其实中间隔了好几层缓冲。这也是为什么有时候触摸会感觉“延迟”——嗯那是系统在等你下一个Vsync信号呢。
RELATED

相关推荐

90DaysOfDevOps Ansible 实战:Tags、Variables、Inventory 与 MySQL 数据库服务器自动化配置

90DaysOfDevOps Ansible 实战:Tags、Variables、Inventory 与 MySQL 数据库服务器自动化配置

文档/教程 【免费下载链接】90DaysOfDevOps This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Pri…

📅 2026/10/8 7:25:19
Agent Safehouse:AI编程助手的安全沙箱,3条命令防住提示注入

Agent Safehouse:AI编程助手的安全沙箱,3条命令防住提示注入

Agent Safehouse:AI编程助手的安全沙箱,3条命令防住提示注入 【免费下载链接】agent-safehouse Sandbox your local AI agents so they can read/write only what they need 项目地址: https://gitcode.com/gh_mirrors/ag/agent-safehouse Agent …

📅 2026/10/8 7:20:18
内存越用越大,甚至 OOM?TDengine 的内存到底花在哪儿

内存越用越大,甚至 OOM?TDengine 的内存到底花在哪儿

"服务器内存一直涨,过几天就被 OOM Killer 干掉"是不少用户在压测或生产上线初期会遇到的问题。这篇文章按 TDengine 实际的内存消费结构,把常见的几块内存"去了哪儿"讲清楚,并给出排查和调优方向。相关配置项名称和默认值均对照源码核实过。 每个 vgroup …

📅 2026/10/8 7:20:18
MORE NEWS

更多资讯

📰

Helm Charts 中 MongoDB 自定义初始化脚本指南:基于 stable/mongodb Chart 的 docker-entrypoint-initdb.d 机制

【免费下载链接】charts ⚠️(OBSOLETE) Curated applications for Kubernetes 项目地址: https://gitcode.com/gh_mirrors/chart/charts 点击查看 免费下载 导读 本文围绕 Kubernetes 官方 Charts 仓库中 stable/mongodb Chart 的初始化脚本机制展开,…

📰

MMDetection3D ScanNet 数据集使用指南:数据导出、训练管线与评测全流程

人工智能计算机视觉深度学习自动驾驶 【免费下载链接】mmdetection3d OpenMMLabs next-generation platform for general 3D object detection. 项目地址: https://gitcode.com/gh_mirrors/mm/mmdetection3d 点击查看 免费下载 ScanNet 是 MMDetection3D 中面向室内…

📰

react-day-picker 的 PreviousMonthButtonProps 类型别名解析:上一月导航按钮的 Props 契约与自定义实践

UI组件前端 【免费下载链接】react-day-picker DayPicker is a customizable date picker component for React. Add date pickers, calendars, and date inputs to your web applications. 项目地址: https://gitcode.com/gh_mirrors/re/react-day-picker 点击查看…

📰

OpenShell外壳定制指南:从开始菜单接管到工作流优化

1. 从"OpenShell"这个名字说起:它到底指什么第一次看到"OpenShell"这个词,很多人会下意识地把它和"开源终端""命令行外壳"联系起来。这个直觉不算错,但只对了一半。在真实的工程语境里,O…

📰

TSN超帧技术解析:从时间切片到工业以太网确定性传输

做运动控制的同行应该都有过这种体验:一条产线上二三十个伺服轴,控制周期压到1ms,PLC跟驱动器之间靠普通以太网交换机通信。平时跑着没事,但只要旁边的视频监控、MES报表、远程桌面一跑大流量,伺服就开始偶发抖动&…

📰

996引擎-WPS公式:自定义公式 format 模板字符串(支持颜色 四则混合运算)

996引擎-WPS公式:自定义公式 format 模板字符串【支持颜色 & 四则混合运算】 自定义公式 format 基础版 1. 支持语法对照表 2. 补充规则说明 3. 最终验证用例(全覆盖) 自定义公式 format 对应的显示预览颜色版本 自定义公式 format 支持颜色,支持自定义占位符 v202609x…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬