【转】【Android】事件处理系统

linux输入子系统

  Android是linux内核的,因此它的事件处理系统也在linux的基础上完成的。java

  Linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/路径下建立咱们硬件输入设备的节点,通常状况下在咱们的手机中这些节点是以eventX来命名的,如event0,event1等等,可是若是是虚拟机的话,咱们能够看到一个mice,这个mice表明鼠标设备,这是因为PC须要使用鼠标来模拟触屏。linux

  因为这些设备节点是硬件相关的,因此每款设备都是不尽相同的。android

  看到了这些输入的设备节点,咱们可能比较困惑这些eventX到底表明什么含义呢,也就是说究竟是什么样的设备建立了这个节点呢?架构

  咱们能够从/proc/bus/input/devices中读出eventX相关的硬件设备,具体的就很少说了,咱们知道android读取事件信息就是从/dev/input/目录下的设备节点中读取出来的,算是android事件处理的起源。app

Android事件传递

  Android事件传递的流程,按键,触屏等事件是经由WindowManagerService获取,并经过共享内存和管道的方式传递给ViewRoot,ViewRoot再dispatch给Application的View。当有事件从硬件设备输入时,system_server端在检测到事件发生时,经过管道(pipe)通知ViewRoot事件发生,此时ViewRoot再去的内存中读取这个事件信息。ide

    至于android在事件处理上为何使用共享内存而不是直接使用Binder机制,猜想应该是google为了保证事件响应的实时性,所以在选择进程间传递事件的方式中,选择了高的共享内存的方式,因为共享内存在数据管理过程当中基本不涉及到内存的数据拷贝,只是在进程读写时涉及到2次数据拷贝,这个是不可避免的数据拷贝,所以这种方式可以很好的保证系统对事件的响应,可是仅仅是共享内存是不够的,由于共享内存的通讯方式并不可以通知对方有数据更新,所以android在事件处理过程当中加入了另外一种进程间通讯方式管道(pipe),管道的效率不如共享内存高,会不会影响事件处理的实时性?不要紧,每次system_serve通知ViewRoot只是向其传递一个字符,即轻巧又简单。函数

    了解了一些基本知识后,如今从底层往上层来分析事件的传递过程。oop

  首先列出整个事件处理的结构图性能

  Android输入子系统

事件处理系统的初始化过程

  Android事件传递系统是以共享内存和管道的进程间通讯方式来实现传递的,为了便于理解它的传递机制,事件传递系统的初始化工做的理解则会显得很是的重要。ui

建立管道链接

  件传递系统中的管道的主要做用是在有事件被存储到共享内存中时,system_server端通知ViewRoot去读取事件的通讯机制。

  既然是ViewRoot和system_server之间创建管道通讯,那么ViewRoot和WindowManagerService(负责事件传递,运行在system_server进程中)各需维护管道的一个文件描述符,其实ViewRoot和WindowManagerService不是各维护了一个管道的文件描述符,而是两个,固然了这两个描述符不属于同一管道,实际上也就是ViewRoot和WindowManagerService之间实现了全双工的管道通讯。

  1. WindowManagerService--->ViewRoot方向的管道通讯,表示WMS通知ViewRoot有新事件被写入到共享内存;
  2. ViewRoot-->WindowManagerService方向的管道通讯,表示ViewRoot已经消化完共享内存中的新事件,特此通知WMS。

  ViewRoot和WindowManagerService的管道的文件描述符都是被存储在一个名为InputChannel的类中,这个InputChannel类是管道通讯的载体。

  ViewRoot端的管道的创建

复制代码
//setView()@ViewRoot.java

requestLayout();  
mInputChannel = new InputChannel();  
try {  
    res = sWindowSession.add(mWindow, mWindowAttributes,  
            getHostVisibility(), mAttachInfo.mContentInsets,  
            mInputChannel);  
} catch (RemoteException e) { 
复制代码

  在ViewRoot和WMS(WindowManagerService)创建起链接以前首先会建立一个InputChannel对象,一样的WMS端也会建立一个InputChannel对象,不过WMS的建立过程是在ViewRoot调用add()方法时调用的。InputChannel的构造不作任何操做,因此在ViewRoot中建立InputChannel时还没有初始化,它的初始化过程是在调用WMS方法add()时进行的,看到上面代码中将mInputChannel做为参数传递给WMS,目的就是为了初始化。

  下面转到WMS代码看看InputChannel的初始化过程

复制代码
//addWindow()@WindowManagerService.java

if (outInputChannel != null) {  
    String name = win.makeInputChannelName();  
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);  
    win.mInputChannel = inputChannels[0];  
    inputChannels[1].transferToBinderOutParameter(outInputChannel);  
      
    mInputManager.registerInputChannel(win.mInputChannel);  
}  
复制代码

  OutInputChannel为ViewRoot传递来的InputChannel对象,上述代码主要工做其实就是建立一对InputChannel,这一对InputChannel中实现了一组全双工管道。

  在建立InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符。

  InputChannel建立完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRoot端InputChannel对象的初始化,这样随着ViewRoot和WMS两端的InputChannel对象的建立,事件传输系统的管道通讯也就创建了起来。

    建立InputChannel pair的过程以及管道创建,共享内存申请的过程就再也不列出它的代码了,请参考openInputChannelPair()@InputTransport.cpp。

  下图为ViewRoot和WMS两端建立InputChannel pair以后的结构

  

InputChannel的注册过程

  上面介绍了InputChannel对象的建立过程,这个过程将管道通讯创建了起来,可是咱们须要清楚的一点是,一个管道通讯只是对应一个Activity的事件处理,也就是当前系统中有多少个Activity就会有多少个全双工管道,那么系统须要一个管理者来管理以及调度每个管道通讯,所以咱们在建立完InputChannel对象后,须要将其注册到这个管理者中去。

  明白了InputChannel对象须要注册的缘由以后,再看ViewRoot和WMS端的InputChannel对象各自须要注册到哪里?

  其实也很好理解,两个InputChannel对象WMS端的是管道通讯的sender, ViewRoot端的是Receiver(尽管建立的全双工,可是目前只使用到了它的一贯的通讯,另外一方向的通讯还没有使用),那么着两个InputChannel对象确定须要被两个不一样的管理者来管理。

  ViewRoot端的通常状况下会注册到一个NativeInputQueue对象中;WMS端注册在InputManager对象中。其实从NativeInputQueue和InputManager的名字中也就能知道各自的功能了。

注册到NativeInputQueue

  ViewRoot端InputChannel对象在向NativeInputQueue注册时,须要注册3个参数:

  1. 将InputChannel对象对应的Native InputChannel传递给NativeInputQueue
  2. 将ViewRoot的成员变量InputHandler传递给NativeInputQueue,这个InputHandler则是事件的处理函数,传递它的做用主要是明确当前ViewRoot的事件处理函数;
  3.  还有一个很重要的参数须要传递给NativeInputQueue,那就是当前Application的主进程的MessageQueue

  Android在实现事件传输时,很大程度上借用了线程Looper和MessageQueue的轮询(poll)机制,经过它的轮询机制来检测管道上是否有消息通知事件发生,借用Looper机制可以很大限度的保证事件可以第一时间被Application知晓。

  在注册过程当中,android会将InputChannel对象中保存的管道的文件描述符交给MessageQueue的native looper去监听,同时向native looper指示一个回调函数,一旦有事件发生,native looper就会检测到管道上的数据,同时会去调用指示的回调函数。这个回调函数为handleReceiveCallback()@android_view_InputQueue.cpp.

  固然了,NativeInputQueue对象,整个系统中只有一个,它为了负责管理这么多的Application的事件传递,android在NativeInputQueue类中定义了一个子类Connection,每一个InputChannel对象在注册时都会建立一个本身的Connection对象。

  

  这一块的代码在registerInputChannel()@android_view_InputQueue.cpp

注册到InputManager

  因为WMS端的对linux Input 系统的检测和ViewRoot对管道接收端的检测机制不一样,ViewRoot端很好的复用了Application 主线程的Looper轮询机制来实现对事件响应的实时性,而WMS尽管也有本身的Looper,WMS却没像ViewRoot同样复用本身的Looper机制,至于缘由android的code上没有明确说明,猜想应该是WMS是整个系统的,不像ViewRoot同样每一个Activity都有一套,为了避免影响系统的总体性能,尽可能不要去影响WMS。

  不采用Looper来轮询是否有事件发生,InputManager启动了2个进程来管理事件发生与传递,InputReaderThread和InputDispatcherThread,InputReaderThread进程负责轮询事件发生; InputDispatcherThread负责dispatch事件。

  为何须要2个进程来管理,用一个会出现什么问题?很明显,若是用一个话,在轮询input系统event的时间间隔会变长,有可能丢失事件。

  虽然没有使用Looper来轮询事件的发生,可是InputDispatcher使用了native looper来轮询检查管道通讯,这个管道通讯表示InputQueue是否消化完成dispatch过去的事件。

  注意的是这个native looper并非WMS线程的,而是线程InputDispatcher自定定义的,所以全部的轮询过程,须要InputDispatcher主动去调用,如

     mLooper->pollOnce(timeoutMillis);或者mLooper->wake();。而不像NativeInputQueue同样,彻底不用操心对looper的操做。

  WMS在初始化时会建立这么一个InputManager实例,固然了,它也是系统惟一的

  JAVA层的InputManager实例并无实现太多的业务,真正实现Input Manager业务是Native的NativeInputManager实例,它在被建立时,创建起了整个WMS端事件传递系统的静态逻辑。

  整个WMS端事件传递系统的静态逻辑

  

  NativeInputManager的整个业务的核心实际上是InputReaderInputDispatcher两个模块,下面简单介绍一下这两个模块。

InputReader

  InputReader从名称就能够看出主要任务是读事件,基本上它全部的业务都包含在了process()的函数中

复制代码
void InputReader::process(const RawEvent* rawEvent) {  
    switch (rawEvent->type) {  
    case EventHubInterface::DEVICE_ADDED:  
        addDevice(rawEvent->deviceId);  
        break;  
  
    case EventHubInterface::DEVICE_REMOVED:  
        removeDevice(rawEvent->deviceId);  
        break;  
  
    case EventHubInterface::FINISHED_DEVICE_SCAN:  
        handleConfigurationChanged(rawEvent->when);  
        break;  
  
    default:  
        consumeEvent(rawEvent);  
        break;  
    }  
}  
复制代码

  process()函数的输入参数时EventHub模块提供的,

  1. 当EventHub还没有打开input系统eventX设备时,InputReader去向EventHub获取事件时,EventHub会首先去打开全部的设备,并将每一个设备信息以RawEvent的形式返给InputReader,也就是process()中处理的EventHubInterface::DEVICE_ADDED类型,该过程会根据每一个设备的deviceId去建立InputDevice,并根据设备的classes来建立对应的InputMapper。如上图所示。
  2. 当全部的设备均被打开以后,InputReader去向EventHub获取事件时,EventHub回去轮询event节点,若是有事件,InputReader则会消化该事件consumeEvent(rawEvent);

InputDispatcher

  数据传输管理的核心业务是在InputDispatcher中完成的,所以最终WMS端InputChannel对象会注册到InputDispatcher中,一样的因为整个系统中InputDispatcher实例只有一个,而WMS端InputChannel对象是和ViewRoot一一对应的。所以InputDispatcher类中也定义了一个内部类Connect来管理各自的InputChannel对象。不一样于NativeInputQueue类中的Connect类,InputDispatcher中的Connect类的核心业务是由InputPublisher对象来实现的,该对象负责将发生的事件信息写入到共享内存。
  相关代码在registerInputChannel()@InputDispatcher.cpp

 


事件传递

  通过分析事件处理系统的初始化过程以后,咱们已经对事件处理系统的总体架构有了必定程度的理解,那么下面看看事件传递过

InputReaderThread线程操做

  当input系统有事件发生时,会被InputReaderThread线程轮询到,InputReader会根据事件的device id来选择的InputDevice,而后再根据事件的类型来选择InputDevice中的InputMapper,InputMapper会将事件信息通知给InputDispatcher;

  目前adroid在InputReader中实现了5种设备类型的InputMapper,分别为滑盖/翻盖SwitchInputMapper、键盘KeyboardInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单点触屏SingleTouchInputMapper。

设备类型

InputManager

EventType

Notify InputDispatcher

滑盖/翻盖

SwitchInputMapper

EV_SW

notifySwitch()

键盘

KeyboardInputMapper

EV_KEY

notifyKey()

轨迹球

TrackballInputMapper

EV_KEY, EV_REL,

EV_SYN

notifyMotion()

单点触屏

SingleTouchInputMapper

EV_KEY, EV_ABS,

EV_SYN

notifyMotion()

多点触屏

MultiTouchInputMapper

EV_ABS,

EV_SYN

notifyMotion()

 

  其中事件类型表明:

  1. EV_REL为事件相对坐标
  2. EV_ABS为绝对坐标
  3. EV_SYN表示Motion的一系列动做结束。

  Notify InputDispatcher表示不一样的事件通知InputDispatcher的函数调用,这几个函数虽然是被InputReaderThread调用的,单倒是在InputDispatcher定义的。

  

InputDispatcherThread线程操做

  InputDispatcherThread线程的轮询过程dispatchOnce()-->dispatchOnceInnerLocked()。

  InputDispatcherThread线程不停的执行该操做,以达到轮询的目的,重点也就是这2个函数处理了。

InputDispatcherThread基本流程

  InputDispatcherThread的主要操做是分两块同时进行的,

  一部分是对InputReader传递过来的事件进行dispatch前处理,好比肯定focus window,特殊按键处理如HOME/ENDCALL等,在预处理完成 后,InputDispatcher会将事件存储到对应的focus window的outBoundQueue,这个outBoundQueue队列是InputDispatcher::Connection的成员函数,所以它是和ViewRoot相关的。

  一部分是对looper的轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件,若是NativeInputQueue处理完成事件,它就会向经过管道向InputDispatcher发送消息指示consume完成,只有NativeInputQueue consume完成一个事件,InputDispatcher才会向共享内存写入另外一个事件。

  

 

总结输入事件的处理流程

  首先从Kernel传递上来的键值由EventHub进行转码,以后由InputReader将其解释成各个事件,再由InputDispatcher分发。

  InputManager是InputReader和InputDispatcher线程的建立者,它只有一个职责,就是被WindowManagerService使用,从Native层获取按键事件。

  WindowManagerService则负责与窗口对接,分发按键消息。

 


源码位置

  事件分发给最前面的窗口:

      /frameworks/base/services/java/com/android/server/WindowManagerService.java

  拦截消息的处理类:
      /frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

  按键事件定义:
      /frameworks/base/core/java/android/view/KeyEvent.java

  Java层输入管理:
      /frameworks/base/services/java/com/android/server/InputManager.Java

  native层输入管理:
      /frameworks/base/libs/ui/InputManager.cpp

  事件读取线程:
      /frameworks/base/libs/ui/InputReader.cpp

  事件分发线程:
      /frameworks/base/libs/ui/InputDispatcher.cpp

  键码与键值转换:/frameworks/base/libs/ui/EventHub.cpp

相关文章
相关标签/搜索