11.从输入设备中获取消息

从输入设备中获取消息是所有GUI系统的核心问题之一,该问题主要包含三个方面:

  • 获取原始的用户消息,包括按键、触摸屏、鼠标、轨迹球等各种输入设备的消息;
  • 对元素消息进行一定的加工,使之转换为程序可以理解的消息。比如所有的按键消息都包括“按下、弹起”等原始消息,而对程序而言,可能只关心该按键被“按了一次”或者“长按”,因此,需要把原始的消息转换为程序可以理解的消息;
  • 把转换后的消息发送到相应的用户窗口所在进程。如果消息获取线程和用户线程在同一进程空间中,则传递消息比较简单,但对于多进程系统来讲,消息获取线程和用户线程往往在不同的进程空间中,因此需要使用IPC机制把消息传递到用户窗口所在的线程中。

从2.3开始,获取消息的代码全部使用C++完成,包括对消息进行加工转换,抛弃了使用Binder的方式传递用户消息到客户端,而是使用Linux的pipe机制。

InputReader线程会持续调用输入设备的驱动,读取所有用户输入的消息,该线程和InputDispatcher线程都在系统进程system_server空间中进行。InputDispatcher线程从自己的消息队列中取出原始消息,取出的消息有可能经过两种方式进行派发,第一种是经过管道pipe直接派发到客户窗口中,另一种则是先派发到WMS中,由WMS经过一定的处理,如果WMS没有处理该消息,则再派发到客户窗口中,否则,不派发到客户窗口。

应用程序添加窗口时,会在本地创建一个ViewRootImpl对象,然后同IPC调用WMS的Session对象的addWindow()方法,从而请求WMS创建一个窗口。WMS会把窗口的相关信息保存在内部的一个窗口列表类InputMonitor中,然后使用InputManager类把这些窗口信息传递给InputDispatcher线程。传递的过程中,InputManager类需调用JNI代码,把这些窗口信息传递到NativeInputManager对象中。

当InputDispatcher得到用户消息后,会根据NatvieInputManager中保存的所有窗口信息判断当前的活动窗口是哪个,并把消息传递到该活动窗口。另外,如果是按键消息,InputDispatcher会先回调InputManager中定义的回调函数,这既会回调InputMonitor中的回调函数,又会调用WMS中定义的相关函数,所有这些回调函数的返回值类型都是boolean。对于系统按键消息,比如HOME键等,WMS内部会按照默认的方式处理,并返回false,从而InputDispatcher不会继续把这些按键消息传递给客户窗口;对于触摸屏消息,InputDispatcher则直接传递给客户窗口。

在InputDispatcher和客户窗口之间使用了管道pipe机制来进行消息传递。pipe是linux的一种系统调用,linux会在内核地址空间中开辟一段共享内存,并产生一个pipe对象。每个pipe对象内部都会自动创建两个文件描述符,一个用于读,另一个用于写。应用程序可以调用pipe()函数产生一个pipe对象,并获得该对象中的读、写文件描述符。文件描述符是全局唯一的,从而使得两个进程之间可以借助这两个描述符,一个往管道中写数据,另一个从管道中读数据。管道只能是单向的,因此,如果两个进程要进行双向消息传递,必须创建两个管道。

当客户窗口请求WMS创建窗口时,WMS内部会创建两个管道,其中一个管道用于InputDispatcher向客户窗口传递消息,另一个用于客户窗口向InputDispatcher报告消息的执行结果。因此,有多少个客户窗口,就有多少个管道与InputDispatcher相连。

InputEvent是所有输入消息的基类,它是一个abstract类型,并且实现了Parcelable接口,即所有的输入消息都是可以跨进程传递的数据类。KeyEvent和MotionEvent是InputEvent的两个实现,分别对应按键key消息和触摸屏消息。

Android2.3版本中对MessageQueue的next()方法进行了扩展,以往该方法只从MessageQueue中读取消息,而在新版中,next方法会首先从native的消息队列中读取消息,而native的消息队列正是InputQueue类。如果native的消息队列有消息,则会直接回调该消息对应的InputHandler对象。

Java中的Looper.loop()函数内部,会调用MessageQueue.next()方法获取消息,next()函数的调用了nativePollOnce()函数,该函数的第一个参数是mPtr,在JNI代码中,会把这个mPtr强制转换为一个native的NativeMessageQueue对象。nativePollOnce()内部会调用native的Looper对象的pollOnce()方法从所包含的文件描述符中读取一个用户消息,如果没有用户消息,则UI线程会进入wait状态。

Looper.myQueue()是UI线程的MessageQueue对象,native环境中的looper对象需要这个对象作为同步锁。同步锁的作用如下:假设Java环境中开始调用nativePollOnce(),而此时没有用户消息,这就会导致UI线程进入wait状态,而接下来当客户进程中的其他线程向该MessageQueue中发送了一个消息时,那么,此时next()函数需要从nativePollOnce()函数中醒过来,以便对该消息进行处理,可是怎么醒过来呢?MessageQueue的enqueueMessage中,在函数的最后调用了一个native方法nativeWake(mPtr),在nativeWake函数内部则使用了Looper.myQueue()作为同步对象,并调用了该对象的notify()方法,从而使UI线程从nativePollOnce()函数中跳出来。

results matching ""

    No results matching ""