Android 系统服务分析——输入子系统
一、两个探讨对象:输入设备、输入系统
1.输入设备:
比如:触摸屏、键盘
当输入设备可用时,Linux内核会在/dev/input/下创建对应的名为event0~n或其他名称的设备节点。
当输入设备不可用时,则会将对应的节点删除。
在用户空间可以通过ioctl的方式从这些设备节点中获取其对应的输入设备的类型、厂商、描述等信息。
从哪里来: 用户操作输入设备时–》设备产生电信号–》内核接收到相应设备的中断–》然后将中断加工成原始的输入事件数据并写入其对应的设备节点中–》用户空间可以通过read()函数将事件数据读出
到哪里去:用户空间监控/dev/input/下的所有设备节点–》当某个节点有数据可读时–》将数据读出并进行一系列的翻译加工–》然后在所有的WindowsManagerServer管理的窗口中寻找合适的事件接收者,并派发给它。
(1)getevent与sendevent工具
Android系统提供了getevent与sendevent两个工具供开发者从设备节点中直接读取输入事件或写入输入事件。
2.输入系统
(1)linux内核:接受输入设备的中断,并将原始事件的数据写入到设备节点中。
(2)设备节点:作为内核与IMS的桥梁,它将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件。
(3)InputManagerService:一个Android系统服务,它分为Java层和Native层两部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器。
(3.1)EventHub:直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者(InputReader)。这些事件包括原始输入事件、设备节点的增删等。
(3.2)InputReader:它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表于配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件,然后交给InputDispatcher进行派发。
(3.3)InputPolicy:它为InputReader的事件加工处理提供一些策略配置,例如键盘布局信息等
(3.4)InputDispacher:它也运行于一个独立的线程中。InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。
(3.5)InputDispacherPolicy:它为InputDispatcher的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDispatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件。
(4)WindowsManagerService:虽说不是输入系统中的一员,但是它却对InputDispatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDispatcher中,使得InputDispatcher可以正确地将事件派发到指定的窗口。
(5)ViewRootImpl:对于某些窗口,如壁纸窗口、SurfaceView的窗口来说,窗口即是输入事件派发的终点。而对于其他的如Activity、对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件(View)。ViewRootImpl将窗口所接收到的输入事件沿着控件树将事件派发给感兴趣的控件
简单来说,内核将原始事件写入到设备节点中,InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDispatcher。InputDispatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件作出响应,更新自己的画面、执行特定的动作。所有这些参与者以IMS为核心,构建了Android庞大而复杂的输入体系。
3.输入系统的启动过程:
IMS分为Java层与Native层两个部分,其启动过程是从Java部分的初始化开始,进而完成Native部分的初始化。
(1)frameworks\base\services\Java\com\Android\server\SystemServer.java
inputManager= new InputManagerService(context, wmHandler);
ServiceManager.addService(Context.INPUT_SERVICE,inputManager);
(2)frameworks\base\services\java\com\Android\server\input/InputManagerService.java–>InputManagerService.InputManagerService()
mPtr=nativeInit(this,mContext,mHandler.getLooper().getQueue());
(3)InputManagerService.cpp–>nativeInit()
frameworks\base\services\jni\com_android_server_input_InputManagerService.cpp
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
(4)com_android_server_input_InputManagerService.cpp
–>NativeInputManager::NativeInputManager()]
sp eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
(5)InputManager.cpp–>InputManager::InputManager()
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader= new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
(6)InputManager.cpp–>InputManager::initialize()
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
完成IMS的创建之后,ServerThread执行了InputManagerService.start()函数以启动IMS。InputManager的创建过程分别为InputReader与InputDispatcher创建了承载它们运行的线程,然而并未将这两个线程启动,因此IMS的各员大将仍处于待命状态。此时start()函数的功能就是启动这两个线程,使得InputReader于InputDispatcher开始工作。
当两个线程启动后,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由事件处理函数进行事件响应。整个过程共有三个线程首尾相接,像三台水泵似的一层层地将事件交付给事件处理函数。如图所示。
根据对IMS的创建过程的分析,可以得到IMS的成员关系如图所示,这幅图省略了一些非关键的引用与继承关系。
注意IMS内部做了很多的抽象工作,EventHub、nputReader以及InputDispatcher等实际上都继承自相应的名为XXXInterface的接口,并且仅通过接口进行相互之间的引用。鉴于这些接口各自仅有唯一的实现,为了简化叙述我们将不提及这些接口,但是读者在实际学习与研究时需要注意这一点。