InputReader与InputDispatcher
- 在
system_server中,有两个非常重要的线程InputReader:负责把从EventHub拿走的原始的驱动数据,把它“翻译”成 Android 认识的事件InputDispatcher:负责把翻译好的事件,精确地投递给当前正在显示的那个App窗口。
InputReader
-
EventHub给出的数据是struct input)event,那是一串非常原始的二进制流,比如:- "驱动号 1, 扫描码 57, 值 1"
- InputReader的工作就是, “哦,驱动号 1 是键盘,扫描码 57 是空格键,值 1 是按下。所以,这是一个 KeyEvent,Action 是 AKEY_EVENT_ACTION_DOWN。”
- "驱动号 1, 扫描码 57, 值 1"
-
核心循环:loopOnce
-
InputReader内部有一个死循环,不断调用
mEventHub->getEvents -
void InputReader::loopOnce() { // 1. 从 EventHub 获取原始事件(如果没有事件,这里会阻塞) size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); if (count) { // 2. 处理事件 processEventsLocked(mEventBuffer, count); } // 3. 将处理完的事件“刷新”,准备通知 Dispatcher mQueuedListener->flush(); }
-
-
InputMapper
- Android支持各种设备,比如,手写板、触控屏、游戏手柄啥的。每种还是设备的数据解析逻辑都不一样,所以InputReader下面有一群Mapper
- KeyboardInputMapper: 处理按键。
- TouchInputMapper: 处理触摸,它最累,要把屏幕的物理坐标(04095)转换成屏幕像素坐标(01080)。
- CursorInputMapper: 处理鼠标轨迹。
- Android支持各种设备,比如,手写板、触控屏、游戏手柄啥的。每种还是设备的数据解析逻辑都不一样,所以InputReader下面有一群Mapper
InputDispatcher
- InputReader把时间翻译好之后,塞给InputDispatcher的时候,InputDispatcher回面临一个巨大的难题:屏幕上有好几个窗口,比如,状态栏、导航栏、悬浮窗等,这个点击事件到底给谁呢
1. 寻找目标
- InputDispatcher 手里面有一份地图,这个是由
WindowManagerService(WMS)实时更新给他的 - 对于按键事件 Key:直接发给当前的 Focus Window(焦点窗口)
- 对于触摸事件 Motion:
- Dispatcher回遍历地图上的窗口
- 根据窗口的Z-Order(层级,谁在上面)和点击区域进行匹配
- 一旦匹配成功,这个窗口就是“目标选手”
2. 派发队列: 一个个来
- InputDispatcher为每一个连接着的App窗口都维护了两个队列
- QutboundQueue(待发送队列):还没发给App的事件
- WaitQueue(等待反馈队列):已经发给App了,但是App还没回传“我处理完了(Finish)”的信号
联动SocketPair
- 当InputDispatcher确定了目标窗口后
- 他找到该窗口对应的InputChannel
- 通过SocketPair的其中一端(fd),把InputMessage写入
- 写入后,Dispatcher 不会撒手不管,它会把这个事件从 OutboundQueue 挪到 WaitQueue,并开始倒计时。
ANR
-
ANR的物理本质
-
当InputDispatcher把事件投递给App并在
WaitQueue挂号时,ANR的倒计时就开始了 -
如果 App 的主线程在干耗时操作(比如在 onClick 里连数据库),它就没法去从 Socket 读消息,更没法回传 Finish 信号
- WaitQueue 里的事件就会越堆越多。
- 当最前面的事件超过了规定的时间(通常是 5 秒),InputDispatcher 就会通过 Binder 告诉 ActivityManagerService:“喂,XX 进程卡死了,赶紧弹个窗问问用户要不要关掉它。”
总结
- 用户手指触摸屏幕
- 内核:在
/dev/input/eventX生成原始二进制数据 - EventHub:通过 epoll 感知到数据,通知 InputReader。
- InputReader: 开启 InputMapper,把原始坐标转成屏幕坐标,打包成 MotionEvent。
- InputDispatcher:查看 WMS 给的窗口地图,发现点的是微信,于是找到微信的 InputChannel
- SocketPair: Dispatcher 往 fd 里写数据;微信进程的 Looper 被唤醒,从另一个 fd 读出数据。
- ViewSystem:微信内部开始 dispatchTouchEvent 分发