Android Input System分析(三)--Native

来源:互联网 发布:淘宝怎样用花呗付款 编辑:程序博客网 时间:2024/06/06 08:05

本来想跟大家讲一下设备节点的,后来发现这方面的资料很多,大家可以到网站自行搜索一下就可以了。在linux系统里,万物皆以文件的形式存在,设备节点其实就是一个个文件,而且这些个文件对用户空间是开放的,而且是对不同的进程访问都是开放的,也就是说用户空间可以对内核空间的设备节点文件进行读写操作,从而到达数据传输的目的。Android大名鼎鼎的Binder其实也是这个原理实现的。

好了,我们还是进入到input system的framework部分吧,我称之为中间层,在这层包括Native和app framework两部分,app framework是很多android开发的兄弟感兴趣的,也是能很直接感受的到的一部分。

这一章节重点讨论native部分的实现,在进一步开始学习之前,我们先学习一些必要的基础知识。

生产者/消费者模式:工厂的工人负责产生数据,这些数据由需要的人来负责处理。产生数据的工人,就形象地称为生产者;而处理数据的人,就称为消费者。这个设计模式解决了很多并发的问题,很多的并发架构也是基于这个模式设计的。


★优点:
◇解耦
◇支持并发(concurrency)
◇支持忙闲不均

举例:
1、你把信写好——相当于生产者制造数据
2、你把信放入邮筒——相当于生产者把  数据放入缓冲区
3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据


Epoll机制:

简单地说就是高效地I/O多路复用机制,使用epoll_wait来监听所需要的文件描述符的变化。

inotify:Inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。您还可以跟踪活动的源头和目标等细节。使用 inotify 很简单:创建一个文件描述符,附加一个或多个监视器(一个监视器 是一个路径和一组事件),然后使用 read 方法从描述符获取事件。read 并不会用光整个周期,它在事件发生之前是被阻塞的。

在android native层,有个eventhub负责input消息的接收和分发,用的就是消费者和生产者的模式。


在这里,大家肯定有个疑问,用户空间是怎么知道内核空间的设备节点内容发生了变化了呢?用的其实就是上述的Epoll和inotify机制,流程如下:

中断触发->中断响应->设备节点更新->Inotify->EventHub read(epoll实现阻塞)

EventHub的主要功能是通过epoll_wait来实现的,所以EventHub所在的线程应该会阻塞在epoll_wait方法中,一直等到epoll_wait设置的超时时间。看看EventHub的实现,在EventHub的构造函数中,建立了一个管道,并把这个管道的读端和写端的文件描述符添加到epoll的监视之下,以便于其他的线程或者进程能够使EventHub所在的线程从epoll_wait的阻塞中返回。

Native有两个独立的线程,一个是inputreaderThread,负责从eventhub读取input消息并入队列,一个是inputdispatcherThread,负责从消息队列获取消息并向上分发。Inputmanager负责read和dispatcher的调度管理。

我们已经知道了native的基本工作流程,下面我们来看看,这个Native 服务是什么时候启动的。

在Android systemserver启动的时候,创建一个InputManagerService对象:

    private void startOtherServices() {

...........................................

            traceBeginAndSlog("StartInputManagerService");
            inputManager = new InputManagerService(context);
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

...........................................

}

在InputManagerService中start方法会通过JNI调用,启动Native层的InputReaderThread,InputDispatcherThread线程,从而开始Input系统的运行。

public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());


        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());


        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }

重点关注

mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

nativeInit的实现native层,用的是JNI实现,代码在com_android_server_input_InputManagerService.cpp,如下:

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }


    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

这样NativeInputManager的实例就被创建出来了。

这个对象是很重要的,因为它主要负责和系统的其他模块交互,而且InputReader和InputDispatcher都是只运行在Native层中,如果需要调用Java函数也是通过这个对象进行的,另外他实现了InputReaderPolicyInterface和InputDispatcherPolicyInterface,是一个重要的Policy。NativeInputManager在构造过程中,完成了InputManager在native基本运行组件的创建,比如创建了EventHub对象,它是事件的Android系统的起源地,所有的事件都是它从驱动中读取出来的;还创建了InputReaderThread线程用来执行InputReader的功能;InputDispatcherThread用来执行InputDispatcher的功能;同时也创建了InputManager来管理EventHub,InputReader,InputReaderThread,InputDispatcher,InputDispatcherThread这些Native运行的基本对象。


下面重点说一下eventhub,是系统中所有事件的中央处理站,管理所有系统中可以识别的输入设备的输入事件,此外,当设备增加或删除时,EventHub将产生相应的输入事件给系统。 Android的 EventHub 组件 通过打开dev/input 中的设备文件的方式从linux kernel读取这些输入事件,然后android的 InputReader 组件会把这些linux输入事件翻译成一个android输入事件流。

流程:初始化-->等待用户输入-->事件拆分

Eventhub--初始化:

EventHub是与EventHub是输入设备的控制中心,它直接与input driver打交道。负责处理输入设备的增减,查询,输入事件的处理并向上层提供getEvents()接口接收事件。在它的构造函数中,主要做三件事:

1. 创建epoll对象,之后就可以把各输入设备的fd挂在上面多路等待输入事件。
2. 建立用于唤醒的pipe,把读端挂到epoll上,以后如果有设备参数的变化需要处理,而getEvents()又阻塞在设备上,就可以调用wake()在pipe的写端写入,就可以让线程从等待中返回。
3. 利用inotify机制监听/dev/input目录下的变更,如有则意味着设备的变化,需要处理。kernel打交道的管理器 它处理了所有kernel传上来的input事件并转换处理发送给framework层使用.

EventHub打开设备:

EventHub是通过扫描/dev/input/目录下所有可用的设备,然后逐一打开这些设备,打开这些设备过程中,EventHub又做了一些Input系统必要的工作,比如构造Device对象,把这些设备加入到epoll的监视队列中等,时间戳的设定等。在构造Device对象的时候,是通过InputDeviceIdentifier来构造的,主要思路就是通过ioctl函数从内容中读取出一些必要的信息,然后把这些信息经过InputDeviceIdentifier存入Device中,然后再通过ioctl函数测试设备的属性,把这些属性信息也存入Device中。代码如下:

Eventhub--事件拆分:

FlyingEvent.process里面主要调用了FlyingEvent.consume方法来处理用户事件。这里只分析touch事件。touch事件可以分为三种:down,move,up。
down类型的touch事件需要四个RawEvent来完成,第一个是X坐标(ABS_X),第二个是Y坐标(ABS_Y),第三个代表方向(ABS_PRESSURE)(0的时候是up,1的时候是down,所以这里应该是1),第四个是结束标志(SYN_REPORT)。
move类型的touch事件需要三个RawEvent来完成,第一个是X坐标,第二个是Y坐标,第三个是结束标志。
up类型的touch事件需要两个RawEvent来完成,第一个代表方向(0的时候是up,1的时候是down,所以这里应该是0),第四个是结束标志。
可能你已经注意到了up事件是没有坐标信息的,它的坐标信息与down(没有move时)或最后一个move(down和up之间有move事件产生)事件的坐标相同。
从FlyingEvent.consume方法中,每一个事件最终都会生成一个TouchEvent,然后调用printTouchEvent进行打印,最后把它存储到eventBuffer中。

InputReader概述

(1) 从EventHub读取事件,这些事件是元事件,即没有经过加工或者仅仅是简单加工的处理的事件;
(2)把这些事件加工处理,生成inputEvent事件,这样封装之后的事件,可以满足Android系统的一些需求;
(3)把这些事件发送到事件监听器,即QueuedInputListener,这个监听器可以把事件传递给InputDispatcher。


InputReaderThread:

Android系统在Native层中实现了一个类似于Java中的线程对象,即C++中的Thread类
这个线程类有个特点就是,当线程开始执行后,会一直重复执行threadLoop方法,直到这个线程的强引用计数变为零为止。所以,这里的threadLoop函数会不停地执行下去,也即是mReader->loopOnce()会循环执行下去,每循环一次就能从EventHub中读取出若干事件。

InputReader从EventHub获取事件:

EvenHub,这个类的主要功能就是主动监视Input驱动的变化,一旦有事件产生,就从产生事件相应的驱动中读取出这个事件。实现这个监视驱动功能,是通过Linux提供的epoll机制来实现。

RawEvent:

RawEvent来自两种,一种是在打开设备时自己赋值,比如设备的添加,移除等,这些事件对应的RawEvent都是getEvents自己赋值的,便于InputReader处理;还有一种是来自驱动的产生的事件,由驱动产生的这类事件,在内容中有其自己的定义的类型,就是input_event。 getEvents可以根据input_event产生相应的RawEvent便于InputReader处理。这里要额外说明一点的就是RawEvent的type,如果是由输入设备产生的事件,那么这个type是和输入设备本身的特性相关的,下面列举出Linux中支持的事件类型:


InputReader对元事件的处理:

先从设备添加类的事件说起,看看在添加设备的时候,都创建了那些数据结构。对于addDeviceLocked的源码,这里就不列举出来,主要说说在InputReader在功能实现时用的变量有那些,分别是是InputDevice,InputMapper。InputDevice代表输入设备的一个状态;InputMapper是某一类事件是如何处理的;两者之间的关系是,一个InputDevice可以产生多种类型的事件,因此他可以对应多个InputMapper。另外,在InputReader中也保存了一个vector来保存InputDevice,这个Vector的名字也叫mDevices,和EventHub中的mDevices类似,不过保存的内容有些不同。在InputReader的mDevices中保存的<id, InputDevice*>,而在EventHub中保存的是<id, Device*>,不过两者的id是一致的,而且每个InputDevice都是通过Devices来构造的。能够完成加工RawEvent工作的还是通过不同的InputMapper来完成的,这些InputMapper根据Android系统支持的类型分成了一下几类,

这里就基本完成了对于添加设备类的事件的处理,接下来就看是分析对于输入设备产生的元事件的处理。对于输入事件的处理主要是通过方法processEventsForDeviceLocked进行的,在这个方法执行之前,已经找到了产生这个事件的输入设备了,然后把输入设备作为参数传递进去,processEventsForDeviceLocked方法根据deviceId找到相应的InputDevice,然后调用InputDevice的process方法进行处理这个事件。下面,结合InputDevice的process方法的这段代码,我们一起看看输入事件是如何处理的,代码如下:


InputReader把事件发送到InputDispatcher:

InputReader把元事件处理完毕后,构造了一个NotifyArgs,并把这个对象压入了QueuedInputListener的队列中,然后就返回了。当时我们并不知道如何把这些队列中的事件发送的InputDispatcher中的。这里,就给出了这个过程。InputReader调用QueuedInputListener的flush方法,把QueuedInputListener队列中的所有事件都发送到InputDispatcher中。下面我们就分析这个过程,从QueuedInputListener的flush方法说起,代码如下:

这里从队列中逐个取出NotifyArgs,然后调用他们的notify方法。在QueuedInputListener创建的时候,我们传入构造函数的的参数是一个InputDispatcher,在这里就使用到了,把这个InputDispatcher作为参数向下传递。在NotifyArgs的notify方法中,基本都类似于

基本过程:InputReader从EventHub中读取出来元事件,预处理加工这些元事件成为NotifyArgs,然后通过QueuedInputListener把他们通知给InputDispatcher。

InputDispatcher:

把输入事件发送到他的目标中去,他的目标可能是应用程序,也可能是WindowManagerService。如果是应用程序的话,可以通过registerInputChannel来定义输入事件的目标。我们已经了解InputDispatcher的唯一一个功能就是分发事件。知道了其功能之后,我们就开始分析InputDispatcher是如何实现这些功能的吧。先看他的构造函数,InputDispatcher创建了一个Looper,代码如下:

这意味着,InputDispatcher有自己的Looper,没有和别人共用,信息也是自己在循环的。这个Looper是native层的Looper,由C++代码实现。在构建Looper过程中,新建了一个管道,这个管道仅仅起到了唤醒Looper,让其能从阻塞等待中返回。Looper中创建的管道是实现Looper功能的重要的方式,是通用的,不是仅仅为了InputDispatcher准备的.


InputDispatcher实现分发:

先从代码中的line 8开始,这行代码的意思是,把KeyEvent发送出去,至于目的地是哪儿,在InputDispatcherPolicy中会有决定。是NativeInputManager实现了这个Policy,所以代码的执行会进入NativeInputManager中

事件在入队前(before enqueue)的处理:

传递过程如下:
NativeInputManager->interceptKeyBeforeQueueing  ----> InputManagerService.interceptKeyBeforeQueueing ----> InputMonitor.interceptKeyBeforeQueueing ---->  PhoneWindowManager.interceptKeyBeforeQueueing

在传递过程中是跨线程的。通过这一系列的方法的名字可以看出,是在事件进入InputDispatcher的队列之前,进行的一些处理。在PhoneWindowManager处理事件之后,会有一个返回值来标记这一事件处理的结果是怎样的,为后面的事件进入队列做准备。在PhoneWindowManager对事件进行前期的拦截处理过程时,一般首先把事件都标记上PASS_TO_USER,即这个事件要交给应用程序去处理,但是在处理过程中决定,有些事件是没必要传递给应用程序的,比如:在通过过程中按下音量相关的事件,挂断电话的事件,power键的处理,以及拨打电话的事件。这些事件的处理结果都是不必传递到应用程序的,这个结果最为返回值,最终会一步一步地返回到NativeInputManager中,这个返回值会作为NativeInputManager的policyFlags的一部分,供InputDispatcher使用

InputDispatcher的threadLoop函数,之后就调用InputDispatcher的dispatchOnce方法,代码如下:

InputDispatcher的主要功能就在这段代码中,这是个轮廓。要想知道具体的功能的实现,还要需要逐步分析下去。先看line7和line8中的代码,如果是一次正常的分发循环(dispatch loop)的话,应该是没有等待执行的命令


最后总结一下:

InputManager里创建了InputDispatch和InputReader,就是在此时将这两者关联了起来
InputManager使用两个线程:
1)InputReaderThread :它负责读取 并预处理RawEvent,applies policy把消息送入DispatcherThead管理的队列中。
2)InputDispatcherThread :它在队列上等待新的输入事件,并且异步地把这些事件 分发给应用程序。
InputReaderThread类与InputDispatcherThread类 不共享内部状态,所有的通信都是单向的,从InputReaderThread 到 InputDispatcherThread。两个类可以通过InputDispatchPolicy进行交互。
InputManager类从不与Java交互,而InputDispatchPolicy 负责执行 所有与系统的外部交互,包括调用DVM业务。

原创粉丝点击