前言
在 Compose 中,处理点击只需一个 Modifier.clickable。但在底层,从你的手指触碰屏幕,到 Kotlin 函数被唤醒,经历了一场横跨内核态与用户态的长途跋涉。
今天我们拆解这一复杂的“神经反射弧”。
一、 起点:内核驱动(Kernel Space)
当你触碰屏幕:
- 产生中断:硬件产生一个电信号中断。
- 生成事件:内核驱动将其封装为 input 事件,写入
/dev/input/eventX设备文件。
二、 传递:InputReader 与 InputDispatcher
在系统的 system_server 进程中,有两个英雄在不停忙碌:
- InputReader:运行在独立线程。它不断轮询设备文件,读取原始坐标,并将其标准化。
- InputDispatcher:它是路由中心。它需要查 WMS(WindowManager):
- “现在哪个窗口在最上面?”
- “这个点落在了哪个窗口范围内?” 然后通过 LocalSocket (InputChannel) 将事件跨进程发给 App 进程。
三、 到达:App 进程的 ViewRootImpl
当事件进入 App 进程:
- Looper 唤醒:主线程被 Socket 信号唤醒。
- 分发逻辑:事件流向
ViewRootImpl->DecorView-> 最后到达我们的AndroidComposeView。
四、 Compose 内部的“二次演译”
一旦进入 AndroidComposeView,传统的 onTouchEvent 就结束了。
- Hit Testing:Compose 开始在自己的
LayoutNode树上进行坐标比对。 - PointerInput 链:事件被封装为
PointerInputChange。 - 消费控制:Compose 通过
Pass(初测、主测、终测)三轮分发,让子组件和父组件能精细地控制“拦截”和“消费”。
五、 给十年老兵的架构挑战
- 响应延迟优化:理解了全链路,你就会明白为什么主线程卡顿(由于刚才说的 Choreographer 问题)会导致点击没反应。
- 跨端手势统一:在 iOS 上,底层事件来自
UITouch。KMP 通过compose-ui将这些不同的原生事件抽象为统一的PointerInput,这正是我们在全栈项目中实现“一套代码处理手势”的底层保障。
六、 源码探秘路径
frameworks/native/services/inputflinger/:Input 系统的 C++ 核心。android.view.InputEventReceiver:Java 层的接收端。androidx.compose.ui.input.pointer.PointerInputEvent:Compose 的封装入口。
结语
手感即正义。理解了 Input 链路,你就能解决最棘手的“交互冲突”和“响应迟钝”问题。
下一篇,我们将探讨线程间通信的灵魂 —— Looper、Handler 与 Epoll:Android 异步设计的本质。