对于触摸事件会首先通过findTouchedWindowTargetsLocked找到目标Window,进而通过dispatchEventLocked将消息发送到目标窗口。
这几天修改input驱动,InputManagerService过程介绍下:
1、输入驱动系统简单介绍
- Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等;
- 用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的中断处理以及设备驱动转换成一个Event,并传递给用户空间的应用程序进行处理;
- 每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程里(上面说过只有一个InputReader Thread)把所有的用户输入都给捕捉到? 这首先要归功于Linux 内核的输入子系统(Input Subsystem);
- 它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程序按照这层抽象接口来实现,上层应用就可以通过统一的接口来访问所有的输入设备;
- 这个抽象层有三个重要的概念,input handler, input handle 和 input_dev,
- input_dev代表底层驱动
- input_handler代表某类输入设备的处理方法,相当于一个上层驱动
- 一个input_dev 可以有多个input_handler,同样,一个input_handler 可以用于多种输入设备;
- 用来关联某个input_dev 和 某个 input_handler, 它对应上图中的紫色的原点。每个input handle 都会生成一个文件节点;
input $ ls
event0 event1 event2 event3 event4 event5 event6
通过Linux input system获取用户输入的流程简单如下:
- 设备通过input_register_dev 将自己的驱动注册到Input 系统。
- 各种Handler 通过 input_register_handler将自己注册到Input系统中。
- 每一个注册进来的input_dev 或 Input_handler 都会通过input_connect() 寻找对方,生成对应的 input_handle,并在/dev/input/下产成一个设备节点文件.
- 应用程序通过打开(Open)Input_handle对应的文件节点,打开其对应的input_dev 和 input_handler的驱动。这样,当用户按键时,底层驱动就能捕捉到,并交给对应的上层驱动(handler)进行处理,然后返回给应用程序。
2、InputManagerService详解
2.1、InputManagerService启动
InputManagerService是Android为了处理各种用户操作而抽象的一个服务,自身可以看做是一个Binder服务实体,在SystemServer进程启动的时候实例化,并注册到ServiceManager中去,不过这个服务对外主要是用来提供一些输入设备的信息的作用,作为Binder服务的作用比较小
private void startOtherServices() {
...
inputManager = new InputManagerService(context);
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
...
}
InputManagerService跟WindowManagerService几乎同时被添加,从一定程度上也能说明两者几乎是相生的关系;
而触摸事件的处理也确实同时涉及两个服务,最好的证据就是WindowManagerService需要直接握着InputManagerService的引用;
如果对照上面的处理模型,InputManagerService主要负责触摸事件的采集;
而WindowManagerService负责找到目标窗口。接下来,先看看InputManagerService如何完成触摸事件的采集;
2.2、如何捕获触摸事件
InputManagerService会单独开一个线程专门用来读取触摸事件,
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
...
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
- 这里有个EventHub,它主要是利用Linux的inotify和epoll机制;
- 监听设备事件:包括设备插拔及各种触摸、按钮事件等,可以看做是一个不同设备的集线器,主要面向的是/dev/input目录下的设备节点,比如说/dev/input/event0上的事件就是输入事件,通过EventHub的getEvents就可以监听并获取该事件:
在new InputManager时候,会新建一个InputReader对象及InputReaderThread Loop线程,这个loop线程的主要作用就是通过EventHub的getEvents获取Input事件
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
<!--事件分发执行类-->
mDispatcher = new InputDispatcher(dispatcherPolicy);
<!--事件读取执行类-->
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
Vector<InputDeviceInfo> inputDevices;
{
...<!--监听事件-->
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
....<!--处理事件-->
processEventsLocked(mEventBuffer, count);
...
<!--通知派发-->
mQueuedListener->flush();
}
输入事件就可以被读取,经过processEventsLocked被初步封装成RawEvent,最后发通知,请求派发消息;
2.3、事件的派发
- 在新建InputManager的时候,不仅仅创建了一个事件读取线程;
- 还创建了一个事件派发线程,虽然也可以直接在读取线程中派发,但是这样肯定会增加耗时,不利于事件的及时读取;
- 因此,事件读取完毕后,直接向派发线程发个通知,请派发线程去处理,这样读取线程就可以更加敏捷,防止事件丢失,因此InputManager的模型就是如下样式:
InputReader的mQueuedListener其实就是InputDispatcher对象,所以mQueuedListener->flush()就是通知InputDispatcher事件读取完毕,可以派发事件了, InputDispatcherThread是一个典型Looper线程,基于native的Looper实现了Hanlder消息处理模型,如果有Input事件到来就被唤醒处理事件,处理完毕后继续睡眠等待,代码如下:
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{
<!--被唤醒 ,处理Input消息-->
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
...
}
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
<!--睡眠等待input事件-->
mLooper->pollOnce(timeoutMillis);
}
以上就是派发线程的模型,dispatchOnceInnerLocked是具体的派发处理逻辑,这里看其中一个分支,触摸事件:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
...
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
...
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
...
Vector<InputTarget> inputTargets;
bool conflictingPointerActions = false;
int32_t injectionResult;
if (isPointerEvent) {
<!--关键点1 找到目标Window-->
injectionResult = findTouchedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
} else {
injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
}
...
<!--关键点2 派发-->
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
从以上代码可以看出,对于触摸事件会首先通过findTouchedWindowTargetsLocked找到目标Window,进而通过dispatchEventLocked将消息发送到目标窗口;
2.4、总结
现在把所有的流程跟模块串联起来,流程大致如下:
- 点击屏幕
- InputManagerService的Read线程捕获事件,预处理后发送给Dispatcher线程
- Dispatcher找到目标窗口
- 通过Socket将事件发送到目标窗口
- 找到目标窗口处理事件