从这一期我们来分析一下Android Input,首先我们来看一下他的一个框架图
安卓的Input包括触摸屏事件,按键消息,还可以配上一些游戏手柄的一些输入消息,或者是鼠标键盘,这个都是Linux系统的一个标准的Input事件。我们来看一下这张图,首先是上层APP,每一个Activity都能够收到我们底层发上来的Input消息,包括我们的返回键、home键、音量键等,那么我们当前活动的应用,他的消息是通过我们WindowManagerService来进行分发的,因为我们可能在一个手机上集N个应用,但是有的应用被压在后台,但是WindowManagerService这一层级会把底层发出来的Input事件只发送给当前的活动窗口,而这一整套管理机制是由WindowManagerService以及InputChannel、ViewRootImpl、PhoneWindowManager等一系列的类和对象来共同维护的。当我们在SystemServer启动的时候我们初始化WindowManagerService就会创建一个InputManager,并且把InputManager对象传给WindowManagerService,在WindowManagerService初始化的时候,就会得到InputManager的对象,这样就可以处理底层的消息,而我们WindowManagerService这一层级,它是通过JNI方法来对我们底层的时间进行捕获的,这个捕获就是我们InputManager来实现的。我们Jni的方法包括我们InputManager的初始化,start,以及注册InputChannel,这个Channel的作用就是从底层C++层,接收按键消息,传给我们上层的应用,上层的应用也就是framework层java部分他会处理按键消息,并且把按键消息发送给当前的Activity。JNI以下就是我们的InputManager,他用来管理我们底层所有的输入事件,在这一部分有四个比较重要的对象:mDispatcher负责分发,mReader负责消息的读,mReaderThread和mDispatcherThread是用来启动的,他在线程里不断地读取分发消息。在我们最底层InputReader,他在读消息的时候进入的是我们EventHub这个对象,这个对象在初始化的时候会获取我们device Input下面的所有输入设备,并且根据底层的输入设备可以实时的检测dev/input这个目录,如果这个目录有热插拔设备,比如一个USB的鼠标插过来,它能够快速的捕捉到这这个input设备,捕获到之后他会把新添加的设备添加到我们整个设备列表中,然后根据设备的属性,创建和他相应对象的一系列匹配的方法,接收这个方法,并且把这些键值通过InputReader来向上分发,InputReader获取到这个按键之后做一系列处理,在传给我们的mDispatcherThread这个线程,而这个线程会通过InputDispatcher这个对象拿到相应的输入消息,然后通过InputChannel发送给我们的java层,java层收到消息之后就会做一些处理,或者是发送给我们的Activity,如果Activity注册了这个事件,那么他就会捕获到当前的按键消息。比如我们应用层写一个onclick事件,注册的时候就会捕获到这个click消息,还有就是有时候捕获到back这个事件,点完返回键他会提示你再点一次,这都是我们上层来做的,但是这个分发机制也就是和按键和Input事件的一个处理机制,是由framework(java)层来做的,他会知道他应该把当前的Input事件传给那个Activity。这就是我们Android Input子系统的一个框架图。当然我们的/dev/input对应的就是我们最底层的驱动设备了,而我们底层设备是由我们内核这边来实现的。比如我们手机支持基本的按键,音量键和电源键,这些都会在我们dev/input目录下映射出一个相对应的设备,然后我们的EventHub去获取这个设备,然后监听这几个设备。下面我们再来看一下Input的主要模块
下面我们来看一下Input和我们的WindowManagerService最开始初始化的一个流程,打开framework/base/services/java/com/android/server/SystemServer.java文件
在我们SystemServer启动的时候会创建一个inputManager的一个对象,创建完之后把inputManager当做一个参数,传递给WindowManagerService。首先我们来看一下InputManagerService
在初始化的时候会调用一个native的方法进行初始化,在初始化的时候会传一个queue过去,我们来看一下他具体初始化的地方,打开framework/base/services/jni/com_android_server_input_InputManagerService.cpp我们找到他的nativeInit方法
首先他会拿到一个消息队列messageQueue,然后在下面会做一些初始化的工作,首先第一个就是NativeInputManager,他在初始化的时候传了一个serviceObj的对象和一个looper的对象。首先new了一个EventHub,然后又new了一个InputManager,并且EventHub当做一个参数传给了我们的InputManager,我们首先看一下InputManager的初始化过程
他创建了一个InputDispatcher和一个InputReader,以及这里有一个readerPolicy,读的一个代理,并且有一个DispatcherPolicy的代理,然后做了一个初始化。在这个initialize()里面创建了两个线程,一个是InputReaderThread,另一个是InputDispatcherThread,在我们初始化read的时候,会把eventHub当做一个参数传给InputReader。这个就是InputManager的初始化过程,当他初始化完成之后就返回了,我们再看一下EventHub
他初始化的时候直接创建了一个参数,然后创建了一些mEpollFd,然后在下面会监控我们的device,如果这个目录有变化,我们会通过Poll的方法知道哪里有变化。然后返回我们来看一下WindowManagerService。
他在初始化的时候new了一个WindowManagerService,并且传了一个参数,我们看一下inputManager这个参数,他传给本地的一个成员mInputManager,mInputManager会做一件事情,也就是monitorInput,我们来看一下这个monitorInput,他是我们InputChannel所做的一个事情
他返回了一个inputchannel,这个inputchannel就是打开了一对ChannelPair,这个openInputChannel的具体实现就是一个native的方法,这个native的方法就是创建了一个套接字,和我们上一节课讲的Sensor都是类似的,当我们创建完成之后就返回了一个inputchannels【1】,这个Channel【1】就是来读取消息的,当我们拿到这个返回的描述符之后,创建了一个PointerEventDispatcher,这样就能从我们的底层获取消息,获取之后,我们的WindowManagerService来进行一系列处理。比如说我们的button控件,之所以能截获到button控件是因为经过WindowManagerService经过一些处理转化成一个click事件,而这个事件的入口就是在这块发上来的,发送上来之后,WindowManagerService会做处理,这样就能理解WindowManagerService和Input设备他们是如何挂接起来的。下面我们来看一下Android inputManager的对象关系图
首先我们会调用InputManagerService这是我们的JNI,然后创建一个eventHub,他呢就是下边黄色框中EventHub的对象,然后还会创建一个mInputManager,他是左上角那个类,我们来看一下他的成员包括:mDispatcher、mReader、mReaderThread、mDispatcherThread,还有一个start和stop的方法,这样我们的Input就和下面几个类结合起来了,还能把eventHub当做一个参数传给mReader,其实我们的mReader就是InputReader这个类的一个对象,我们InputReader在他里面还有一些成员包括:mEventHub、mPolicy、mQueuedListener,其中的mEventHub就是下面黄色框中的EventHub,它用来读取底层的事件。比如我临时增加一个设备,或者撤销一个设备,一个button事件,一个power键消息等都是通过我们InputReader来读的。而我们InputReader和mDispatcherThread是通过mQueuedListener这个对象来建立链接的,而这个对象又是QueuedInputListener的一个实例。而mQueuedListener在创建的时候,在QueuedInputListener中会有一个mInnerListener,他是一个内置的Listener,而这个mInnerListener就是我们InputDispatcher的一个实例,也就是我们mDispatcherThread,这样的话我们InputReader从我们EventHub中拿到了消息,解析完消息,如果是当前系统正常的消息,那么会通过QueuedInputListener,将我们的消息发送给mInnerListener。在我们InputDispatcher中有Connection,他就会和InputPublisher建立联系,这个建立联系有一个mChannel,这个Channel就是当我们注册一个Channel的时候在这里产生的那个Channel,也就是当我们创建一对套接字的时候那个写的Channel,就会在这里保存着,注册的时候他会在InputDispatcher中创建一个connection,这个connection中就会有一个Channel。这样我们整个流程就比较清晰了。上层创建了一个inputManager,然后注册inputchannel,inputchannel就和我们出口建立了关系,出口就是InputDispatcher,在connection中有好多connection,它会找到当前Activity,把这个消息通过Activity所对应的Channel,发送给我们上层的java,这样我们的出口就找到了,而我们的入口也有,就在EventHub这里getEvents(),通过getEvents将消息发送给InputReader,它在通过一个消息的对象(QueuedInputListener)发送给我们的InputDispatcher,他在通过mChannel发送给上面的接口,然后这个接口在做上层的处理,这就是我们整个Input事件的一个大概流程。下面我们来看一下我们的WindowManagerService是在什么时候注册InputChannel的
我们现在所在的目录是InputManagerService.cpp中。他在这里调用的是我们的mInputManager这个对象的getDispatcher中的registerInputChannel,这样我们传的Channel是有三个参数的,包括inputChannel、inputWindowHandle、monitor,monitor是一个布尔量就是看一下是否在监控,这样就能和上层的窗口建立关系了。如果说我们有n个窗口,那么会有n个connection和InputManager中的Dispatcher来建立一个链接,然后我们看一下registerInputChannel是在什么时候做的,我们打开WindowManagerService.java找到registerInputChannel的具体调用
在这里有一个addWindow的方法,在这里边会传入一个InputChannel,而这个InputChannel就是一对无连接的套接字,而这对套接字就会从window中来和我们底层建立关系,我们看一下他注册的过程
他在这里会注册一个Channel,这个Channel就会和我们的底层建立联系,这样我们每一个窗口都能和我们的Input对应起来,而当我们切换窗口的时候,我们底层的事件也会知道我们这个按键消息应该发送给那个窗口,然后由这个窗口来做具体的处理。我们对于上层的讲解和Input的流程就讲到这里。