Android Input 事件系统Kitkat

 Android Input 处理过程涉及多个系统组件,这里只关注 input 事件从取出到处理完成的流程。在android 系统中,这也被抽象成了一个“生产者和消费者”的模型。InputManagerService 作为系统的事件处理的中枢,负责从 kernel 获取输入事件,解析到android 的 event 类型,并分发给系统或应用处理。因此可以理解成 InputManagerService 主要担当的是"生产者"的角色。


    下图为 InputManagerService 的对象模型:




 显然,input 处理的大部分工作是在 native 层面实现的。IMS native 主体是一个 NativeInputManager 对象,通过 InputManager 维护了两个重要的线程:
        
          * InputReaderThread     -  按字面意思理解,就是从driver 获取 event 的线程。
          * InputDispatcherThread -  将reader 读到的event 整理后发送给处理 event 的实体。


    图中的几个列举的对象需要解释下:
          * EventHub        是android 事件系统对于输入事件的一个抽象和管理器。为了降低程序设计中的耦合,android 各个组件不会直接从 driver 获取事件去各自处理。所以定义了android 层面的event 数据结构,由 EventHub 根据各个输入设备统一管理。
          * InputReader     是获取 event 操作的主体。
          * InputDispatcher  对 event 处理分发的主体。
          * InputListener : Reader 和 Dispatcher 之间没有使用其他ipc 传送数据。是将 Dispatcher 的指针实例做为 listener 注册到了 Reader 中。这样 Reader 获取到的 event 可以直接通过这个 listener 转到 Dispatcher 中去处理。
          * InputPublisher   是一个生产者的模型,对应的有一个消费者模型 InputConsumer 存在于 view 中用于“消费” IMS 中产生的 “Input”。通过这对生产者和消费者的模型实现了 event 的传递和处理。


    
    InputReader 的流程相对简单些,如下是代码运行流程:
    (这里只列出了我们最常使用的 Keyboard 类型)

  InputReader 的执行流程比较直观,EventHub 从driver 中取出抽象化过的 RawEvent,并转化到面向 android 的各个种类的event 。需要留意的是,因为 event 种类比较多,包含 keyboard, touch(multi-touch), joystick 等。所以实际将 RawEvent 转化到各个种类的 event 的过程是由匹配各个种类的 InputDevice 和 InputMapper 完成的。
    举例来说,从/dev/input/event0 中获取到了 linux keycode 为 4 的 Key,需要转换成 adroid 识别的 ACODE 值。就是通过 InputDevice 和 InputMapper 根据 device id(vendor ID)以及 keylayout 来转换的。


    流程:
        InputReaderThread 继承自 Thread, 其 entry 为 threadloop ,执行 Inputreader 的 loopOnce。
        1> 调用 EventHub 的 getEvents 方法。如果此 loop 为第一次执行,EventHub 首先需要扫描输入设备,依次
           依次执行 scanDevicesLocked, openDeviceLocked, addDeviceLocked 将所有 /dev/input 设备添加进来。并设置 getEvents 的返回值的type 为DEVICE_ADDED。
           InputReader 中根据这个type 调用 addDeviceLocked 方法,在这里 createDeviceLocked 创建 InputDevice 和 InputMaper。
        2> loopOnce 再次执行。这次 EventHub 的 getEvents 可以读到输入设备中的 event 的事件 RawEvent,并传递给已经创建的对应的 InputDevice 和 InputMapper 处理。在 InputMapper 的 processKey 方法中,进入 InputListener 的 notify 转到 InputDispatcher 。


 接下来是 InputDispatcher 的处理流程。不过由于这个过程相对复杂,与 WindowManagerService 和 ViewRootImpl 都有关联,要理清这里面的关系,找到关键的线条。需要找到 event 传递的主体对象。
    前面有提到 event 的处理过程是一个“生产者和消费者”的模型,即通过 InputPublisher 提供产品“event”给 InputConsumer 使用。查看 InputPublisher 和 InputConsumer 的代码,在文件 /frameworks/native/include/input/InputTransport.h 和 /frameworks/native/libs/input/InputTransport.cpp 中。可以看到对于KeyEvent  的“生产” 和 “消费”的抽象接口是 publishKeyEvent 和 consume ,而这两个接口的实现主体的是通过各自私有的 InputChannel send 和 receive message, message 里传递了实际的 keyevent 信息。
    status_t InputPublisher::publishKeyEvent(...)
    {
     ……
     ……
     return mChannel->sendMessage(&msg);
    }


    status_t InputConsumer::consume(...)
    {
     ……
     ……
     status_t result = mChannel->receiveMessage(&mMsg);
     ……
    }


    
    InputChannel 类也在这两个文件中定义和实现。头文件中对于 InputChannel 的注释说明了一切。由一对 InputChannel 通过 domin socket 的方式传送 event 数据。而这一对 InputChannel 则是由 InputChannel::openInputChannelPair 创建的。


    这样,在 InputDispatcher 的流程追踪中只要将 InputChannel 做为切入点就可以抽丝剥茧找出关键的执行线了。


    下图为 Dispatcher 的流程(忽略了一些函数内调用以及组件内 java 和 native 调用的细节 ):


这个过程分阶段来理解:
 1. 准备阶段,将需要接收 event 的 focusWindows 记录,并创建 InputPublisher 和 InputConsumer 对象。    
    1> 当一个 activity 设置 setContentView 的时候,会进入 ViewRootImpl 的 setView 方法。在这个方法里会 new 一个 InputChannel, 并将实例通过 session 传递给 WindowsManagerService(WMS) 的 addWindows 方法。在这里执行了一个魔术的过程,将这个 InputChannel 通过 InputChannel::openInputChannelPair() 创建好了一对已建立socket 连接的 InputChannel, 其中用作 server 的 InputChannel[0] 传给了 IMS , 用作 client 的 InputChannel[1] 回传给了 ViewRootImpl。
    2> IMS 的 registerInputChannel 将 WMS 传来的 InputChannel 记录到 Connection 的集合里,并在这里基于该 InputChannel 创建好了 InputPublisher 对象。同时创建了一个 callback, 用于回调 clent 的 InputChannel 触发的回调。同时,addWindows 继续执行会触发 IMS 的 setInputWindow 方法,使之更新 mFocusWindowHandle 记录当前 focus 的 Window。
    3> 继续 ViewRootImpl 中的 setView 。ViewRootImpl 有个 WindowInputEventReceiver 成员,继承自  InputEventReceiver 。通过 client 的 InputChannel 创建 WindowInputEventReceiver 实例的时候,同时就创建好了其内部的 InputConsumer 对象。


 2. 顺序事件流
    1> 首先是 InputReader 中 InputListener 的 notify。从代码追踪看,他首先执行了 interceptKeyBeforeQueue 的方法,通过 InputMonitor 的回调,进入 WMS,进入 PhoneWindowManager 里的 interceptKeyBeforeQueue 中处理。这个处理结果体现在 polcyFlags 中。
    2> notify 代码继续执行 enqueueInboundEventLocked ,将 event 时间存放到 EventEntry Queue 中。
    3> InputDispatcherThread 的线程周期执行,dispatchOnce 依次往下执行会根据当前的 mFocusedWindowHandle 从 connect 集合里找到对应的 InputPublisher 执行 pushlishEvent。
    4> ViewRootImpl 中的 runnable ConsumeBatchedInputRunnable 使 InputEventReceiver 中的 InputConsumer 去执行 consumer ,将读到的 event 放到队列中。ConsumeBatchedInputRunnable 中的 doProcessInputEvents 则从 队列中取出 event 来处理。ViewRootImpl 对于 event 的处理是按 InputStage 来执行的,每一个stage 都可以将此event 消费掉或者不做处理留给下一个 stage。这个概念在android app 学习中都有涉猎。


 3. 反馈
   1> ViewRootImpl 的stage 中消费了event 后会通过 finishInputEvent 使 InputConsumer 发送完成的消息。之前 IMS 中创建好的 callback 会促使 InputPublisher 读取该消息。如果消息中表示这个 event 没有被处理,则会调用 dispatchUnhandledKey,由定义的 fallback key 去默认处理。fallback event 主要是trackball 的事件,默认行为定义在其kcm文件里。
   2> 进入下一轮event 流程。 
 
 待思考和继续追踪的问题:
    1. 我们常用的 interceptKeyBeforeQueue 和 interceptKeyBeforeDispatching ,在代码中的执行先后?有什么区别?
    2. keylaout 文件中的 wake 和 wake drop 标记会用于policyflags,这些标记的作用是什么?        
    3. 各个 InputStage 的过程,需要走代码中梳理出来。
    4. 实际事件流中还有个InputFilter 也会消费事件,在代码中找到 filter 的执行点和执行主体。
    5. 不考虑 inputfilter 部分,我们平常做按键拦截都将逻辑实现往 beforequeue 和 beforedispatching 里丢是否合理?有没有其他模型可以使用。(参考 GlobalKeyManager)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值