Framework学习之input触摸事件原理

移动开发 Android
对于触摸事件会首先通过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将事件发送到目标窗口
  • 找到目标窗口处理事件
责任编辑:武晓燕 来源: Android开发编程
相关推荐

2011-08-03 17:32:17

IOS UIScrollVi touch

2013-04-15 15:22:06

2021-08-11 14:29:20

鸿蒙HarmonyOS应用

2016-12-08 22:59:47

触摸事件android

2013-04-22 15:40:00

Android开发触摸事件与点击事件区别

2011-08-02 16:28:40

iPhone Web开发 事件

2017-01-11 18:44:43

React Nativ触摸事件Android

2013-05-14 11:08:23

AIR Android触摸事件鼠标事件

2011-06-23 14:05:32

Qt 事件机制

2010-01-05 10:29:43

.NET Framew

2009-08-18 11:08:24

.Net Framew

2012-06-01 10:27:44

Cocos2d触摸分发原理

2011-09-13 10:07:10

PhoneGap

2017-12-21 15:42:08

iOS传递机制

2011-07-01 11:16:14

Struts

2016-06-13 15:53:34

SDN开放网络操作系统ONOS

2013-07-25 15:19:23

iOS开发学习Xcode打包framiOS开发

2012-02-22 17:23:51

JavaPlay Framew

2023-10-12 22:44:16

iOS事件响应链

2013-09-17 14:00:19

AndroidListView原理
点赞
收藏

51CTO技术栈公众号