输入管理服务IMS创建和默认触摸设备为pointer设备
主要以触摸事件为例
IMS创建
systemserver中创建
同其他系统服务一样,IMS在SystemServer中的ServerThread线程中启动
SystemServer.java
- ServerThread.run()
- inputManager= new InputManagerService(context, wmHandler) 建IMS对象。注意第二个参数wmHandler,这说明IMS的一部分功能可能会在WMS的线程中完成
- ServiceManager.addService(Context.INPUT_SERVICE,inputManager) 将IMS发布给ServiceManager,以便其他人可以访问IMS提供的接口
- inputManager.setWindowManagerCallbacks(wm.getInputMonitor()); 设置向WMS发起回调的callback对象
- inputManager.start(); 正式启动IMS
- display.setInputManager(inputManager) 设置IMS给DisplayManagerService。DisplayManagerService将会把屏幕的信息发送给输入系统作为事件加工的依据
IMS的诞生分为两个阶段:
· 创建新的IMS对象。
· 调用IMS对象的start()函数完成启动。
InputManagerService创建
InputManagerService.java
- InputManagerService() 构造函数
- this.mHandler = new InputManagerHandler(handler.getLooper()); 使用wmHandler的Looper新建一个InputManagerHandler。InputManagerHandler将运行在WMS的主线程中
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); 每一个分为Java和Native两部分的对象在创建时都会有一个nativeInput函数
IMS的构造函数非常简单。绝大部分的初始化工作都位于Native层
com_android_server_input_InputManagerService.cpp
- nativeInit()
- NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper()); 此对象将是Native层组件与Java层IMS进行通信的桥梁
- return reinterpret_cast(im); 返回了NativeInputManager对象的指针给Java层的IMS,IMS将其保存在mPtr成员变量中
nativeInit()函数创建了一个类型为NativeInputManager的对象,它是Java层与Native层互相通信的桥梁
com_android_server_input_InputManagerService.cpp
- NativeInputManager()
- sp eventHub = new EventHub();
重点 NativeInputManager创建了EventHub
- mInputManager = new InputManager(eventHub, this, this); 接着创建了Native层的InputManager
- sp eventHub = new EventHub();
创建了两个关键类型,分别是EventHub与InputManager
。EventHub
复杂的构造函数使其在创建后便拥有了监听设备节点的能力
InputManager.cpp
- InputManager()
- mDispatcher = new InputDispatcher(dispatcherPolicy); 创建InputDispatcher
- mReader= new InputReader(eventHub, readerPolicy, mDispatcher); 创建 InputReader
- initialize(); 初始化
- mReaderThread = new InputReaderThread(mReader); 创建供InputReader运行的线程InputReaderThread
- mDispatcherThread = new InputDispatcherThread(mDispatcher); 创建供InputDispatcher运行的线程InputDispatcherThread
InputManager的构造函数也比较简洁,它创建了四个对象,分别为IMS的核心参与者InputReader与InputDispatcher,以及它们所在的线程InputReaderThread与InputDispatcherThread。注意InputManager的构造函数的参数readerPolicy与dispatcherPolicy,它们都是NativeInputManager。
至此,IMS的创建完成了
IMS启动
完成IMS的创建之后,ServerThread执行了InputManagerService.start()函数以启动IMS
。InputManager的创建过程分别为InputReader与InputDispatcher创建了承载它们运行的线程,然而并未将这两个线程启动,因此IMS的各员大将仍处于待命状态。此时start()函数的功能就是启动这两个线程,使得InputReader于InputDispatcher开始工作
当两个线程启动后,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由事件处理函数进行事件响应
。整个过程共有三个线程首尾相接,像三台水泵似的一层层地将事件交付给事件处理函数
InputManagerService.start()函数的作用,就像为Reader线程、Dispatcher线程这两台水泵按下开关,而Looper这台水泵在窗口创建时便已经处于运行状态了
InputReader的流程
InputReader被InputManager创建,并运行于InputReaderThread线程中
InputReaderThread继承自C++的Thread类,Thread类封装了pthread线程工具,提供了与Java层Thread类相似的API。C++的Thread类提供了一个名为threadLoop()的纯虚函数,当线程开始运行后,将会在内建的线程循环中不断地调用threadLoop(),直到此函数返回false,则退出线程循环,从而结束线程
InputReaderThread仅仅重写了threadLoop()函数:
InputReader.cpp
-
InputReaderThread::threadLoop()
- mReader->loopOnce(); 执行InputReader的loopOnce()函数
- return true;
-
InputReader::loopOnce()
- count = mEventHub->getEvents(timeoutMillis,mEventBuffer, EVENT_BUFFER_SIZE); 通过EventHub抽取事件列表。读取的结果存储在参数mEventBuffer中,返回值表示事件的个数当EventHub中无事件可以抽取时,此函数的调用将会阻塞直到事件到来或者超时
- processEventsLocked(mEventBuffer, count); 如果有抽得事件,则调用processEventsLocked()函数对事件进行加工处理
- mQueuedListener->flush(); 发布事件,通过调用flush()函数将所有事件交付给InputDispatcher
InputReader的一次线程循环的工作思路非常清晰,一共三步:
- 首先从EventHub中抽取未处理的事件列表。这些事件分为两类,一类是从设备节点中读取的原始输入事件,另一类则是输入设备可用性变化事件,简称为设备事件。
- 通过processEventsLocked()对事件进行处理。对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则在进行转译、封装与加工后将结果暂存到mQueuedListener中。
- 所有事件处理完毕后,调用mQueuedListener.flush()将所有暂存的输入事件一次性地交付给InputDispatcher。
这便是InputReader的总体工作流程。
EventHub
EventHub的直译是事件集线器,顾名思义,它将所有的输入事件通过一个接口getEvents()将从多个输入设备节点中读取的事件交给InputReader,是输入系统最底层的一个组件。基于INotify与Epoll两套机制
EventHub.cpp
- EventHub::EventHub()
- mEpollFd= epoll_create(EPOLL_SIZE_HINT) 首先使用epoll_create()函数创建一个epoll对象。EPOLL_SIZE_HINT指定最大监听个数为8这个epoll对象将用来监听设备节点是否有数据可读(有无事件)
- mINotifyFd = inotify_init(); 创建一个inotify对象。这个inotify对象将被用来监听设备节点的增删事件
- int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);将存储设备节点的路径
/dev/input
作为监听对象添加到inotify对象中。当此文件夹下的设备节点发生创建与删除事件时,都可以通过mINotifyFd读取事件的详细信息 - result =epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); 将对mINotifyFd的监听注册到epoll对象中
- 在构造函数剩余的代码中,EventHub创建了一个名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。因为
InputReader在执行getEvents()时会因无事件而导致其线程阻塞在epoll_wait()的调用里,然而有时希望能够立刻唤醒InputReader线程使其处理一些请求。此时只需向wakeFds管道的写入端写入任意数据,此时读取端有数据可读,使得epoll_wait()得以返回,从而达到唤醒InputReader线程的目的
EventHub的构造函数初识化了Epoll对象和INotify对象,分别监听原始输入事件与设备节点增删事件。同时将INotify对象的可读性事件也注册到Epoll中,因此EventHub可以像处理原始输入事件一样监听设备节点增删事件了。
getEvents 函数的实现比较关键也比较长,详细分析看 参考
中 第五章 深入理解Android输入系统
对应的内容
总结:
- EventHub通过Device结构体描述输入设备的各种信息。
- EventHub在getEvents()函数中进行设备的加载与卸载操作。设备的加载与卸载分为按需加载或卸载以及通过INotify动态加载或卸载特定设备两种方式。
- getEvents()函数进行了设备的加载与卸载操作后,会生成DEVICE_ADDED、DEVICE_REMOVED以及FINISHED_DEVICE_SCAN三种设备增删事件,并且设备增删事件拥有高于Epoll事件的优先级。
EventHub作为直接操作设备节点的输入系统组件,隐藏了INotify与Epoll以及设备节点读取等底层操作,通过一个简单的接口getEvents()向使用者提供抽取设备事件与原始输入事件的功能。EventHub的核心功能都在getEvents()函数中完成,因此深入理解getEvents()的工作原理对于深入理解EventHub至关重要。
getEvents()函数的本质是通过epoll_wait()获取Epoll事件到事件池,并对事件池中的事件进行消费的过程。从epoll_wait()的调用开始到事件池中最后一个事件被消费完毕的过程称之为EventHub的一个监听周期。由于buffer参数的尺寸限制,一个监听周期可能包含多个getEvents()调用。周期中的第一个getEvents()调用一定会因事件池的枯竭而直接进行epoll_wait(),而周期中的最后一个getEvents()一定会将事件池中的最后一条事件消费完毕并将事件返回给调用者。前文所讨论的事件优先级都是在同一个监听周期内而言的。
触摸设备不配置 idc 文件默认被配置为 pointing device
- InputReader::loopOnce()
- count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE)
- processEventsLocked(mEventBuffer, count)
- addDeviceLocked(rawEvent->when, rawEvent->deviceId) 新设备加入
- device = createDeviceLocked(deviceId, controllerNumber, identifier, classes)
- device->configure(when, &mConfig, 0)
- mapper->configure(when, config, changes) 为 TouchInputMapper::configure
- configureParameters() 解析 对应设备的 idc 配置获取设备类型
- mapper->configure(when, config, changes) 为 TouchInputMapper::configure
- device->reset(when)
- mDevices.add(deviceId, device)
- addDeviceLocked(rawEvent->when, rawEvent->deviceId) 新设备加入
- mPolicy->notifyInputDevicesChanged(inputDevices)
- mQueuedListener->flush()
void TouchInputMapper::configureParameters() {
...
if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
// The device is a touch screen.
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
} else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
// The device is a pointing device like a track pad.
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
} else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
|| getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
// The device is a cursor device with a touch pad attached.
// By default don't use the touch pad to move the pointer.
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
} else {
// The device is a touch pad of unknown purpose.
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
}
...
}
在最后的 else 中将设备类型配置为了 DEVICE_TYPE_POINTER,这就是默认值,可以将其直接改为触摸设备
触摸idc文件配置
idc(Input Device Configuration)为输入设备配置文件,它包含设备具体的配置属性,这些属性影响输入设备的行为。对于touch screen设备,总是需要一个idc文件来定义其行为。
Android基于输入设备驱动汇报的事件类型和属性来检测和配置大部分输入设备的能力。然而有些分类是模棱两可的,如:多点触摸屏(multi-touch touch screen)和touch pad都支持EV_ABS事件类型和ABS_MT_POSITION_X和ABS_MT_POSTION_Y事件,然而这两类设备的使用是不同的,且不总是能自动判断。所以,需要另外的信息来指示设备上报的pressrue和size信息的真正含义。因为,触摸设备,特别是内嵌的touch screen,经常需要idc文件
。
//device/friendlyelec/nanopi3,增加以下
PRODUCT_COPY_FILES += 本地路径/DEVICE_NAME.idc:system/usr/idc/DEVICE_NAME.idc
idc文件命名有三种方式
:
Vendor_xxxx_Product_xxxx_Version_xxxx.idc
Vendor_xxxx_Product_xxxx.idc
device-name.idc
注:
当以DEVICE_NAME来命名时,设备名称中除“0-9”、“a-z”、“A-Z”、“-”或“”之外的所有字符都会被替换为“”.
如果三种命名规则的文件都存在,则系统查阅kcm顺序为由上至下。
如果查找不到对应的kcm文件,则使用默认的Generic.kcm(此按键字符映射旨在支持各种标准外部键盘)。
device-name 为 input_device->name
xxxx分别对应的VID、PID和版本号,第一种命名方式不常用
在android中 /system/usr/idc 目录下
getevent 工具用于获取输入事件,可以获取对应的 xxxx 值,如:
nanopi3:/system/usr/idc # getevent -li
add device 1: /dev/input/event1
bus: 0019
vendor 0001
product 0001
version 0100
name: "gpio_keys"
location: "gpio-keys/input0"
id: ""
version: 1.0.1
events:
KEY (0001): KEY_POWER
input props:
<none>
add device 2: /dev/input/event0
bus: 0018
vendor 0000
product 0000
version 0000
name: "gslX680"
location: ""
id: ""
version: 1.0.1
events:
ABS (0003): ABS_MT_TOUCH_MAJOR : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
ABS_MT_POSITION_X : value 0, min 0, max 1024, fuzz 0, flat 0, resolution 0
ABS_MT_POSITION_Y : value 0, min 0, max 600, fuzz 0, flat 0, resolution 0
ABS_MT_PRESSURE : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
input props:
<none>
could not get driver version for /dev/input/mice, Not a typewriter
可知gslX680对应 idc名可以为:
Vendor_0000_Product_0000_Version_0000.idc
Vendor_0000_Product_0000.idc
gslX680.idc
Android获取input设备的相关信息(VID、PID等)
- 通过查询proc/bus/input/devices文件
cat proc/bus/input/devices - 使用getevent命令
getevent -i - 使用dumpsys input命令
dumpsys input
参考
十分钟让你了解Android触摸事件原理(InputManagerService)
《深入理解Android 卷III》第五章 深入理解Android输入系统
AOSP文档触摸设备
Android10 (.idc)配置文件的解析过程分析
Android idc输入设备配置文件