InputReader与InputDispatcher

1 阅读3分钟

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。”
  • 核心循环: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: 处理鼠标轨迹。

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 分发