Android 输入系统架构 笔记2

本文详细解析了Android系统中输入事件的处理流程,包括EventHub初始化、设备扫描、事件读取与分发等关键环节,并深入探讨了不同类型的InputMapper在处理触摸屏和其他输入设备事件时的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在eventHub初始化的时候mNeedToScanDevices的值是ture的所以会直接进入到scanDevices Locked。而在内核里面所有的input device在注册的时候都会在linux的文件系统下的/dev/input 下面。所以按照一般的HAL的思想,如果要去操作这个设备,首先还是要打开这个设备节点的。

Framework/base/services/input/EventHub.cpp
 
void EventHub::scanDevicesLocked() {
status_t res = scanDirLocked(DEVICE_PATH);    
if(res < 0) {
LOGE("scan dir failed for %s\n", DEVICE_PATH);   
}
}

status_t EventHub::scanDirLocked(const char *dirname)
{
……
openDeviceLocked(devname);
……
}

代码中的while循环会对DEVICE_PATH(/dev/input)下的所有的设备节点调用openDeviceLocked方法。


Framework/base/services/input/EventHub.cpp
 
status_t EventHub::openDeviceLocked(const char *devicePath) {
……
int fd = open(devicePath, O_RDWR);
……
InputDeviceIdentifier identifier;
……
if(ioctl(fd, EVIOCGNAME(sizeof(buffer)-1), &buffer) < 1 )
……
if(ioctl(fd, EVIOCGVERSION, &driverVersion))
……
if(ioctl(fd, EVIOCGID, &inputId))
……
identifier.bus = inputId.bustype;
identifier.product = inputId.product;
identifier.vendor = inputId.vendor;
identifier.version = inputId.version;
……
Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
……
struct epoll_event eventItem;  
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = deviceId;
……
mDevices.add(deviceId, device);  
device->next = mOpeningDevices;
mOpeningDevices = device;
return 0;
}

首先通过open系统调用得到设备节点的文件描述符然后新构造一个叫InputDeviceIdentifier类。接着通过对刚才得到的设备节点描述下ioctl的命令获取设备的一些简单信息譬如设备的名字设备驱动的版本号设备的唯一id和描述符轮询的方式。得到的这些信息保存在InputDeviceIdentifier类里面。最后又构造了一个Device类其中设备描述符和刚才的构造InputDeviceIdentifier类作为参数重新构造了Device类。然后在构造成功了Device类又会通过ioctl系统调用获取input设备的一些比较重要的参数。比如设备上报事件的类型是相对事件还是绝对事件相对事件一般是指像鼠标滑动绝对事件就好比触摸屏上报的坐标设备所属的class等一些比较重要的信息。举一些例子INPUT_DEVICE_CLASS_KEYBOARD(按键类型)INPUT_DEVICE_CLASS_CURSOR(带游标类型鼠标和轨迹球等)INPUT_DEVICE_CLASS_TOUCH(触摸类型单点触摸或多点触摸)INPUT_DEVICE_CLASS_TOUCH_MT(这个类型特指多点触摸)等。如果一个设备的驱动没有指明设备的类型的话那么他在android中上报的数据时不会被处理的。这个函数的最后是将input设备的文件描述符加入到轮询的集合中去如果接收到事件就会去处理。


Framework/base/services/input/InputReader.cpp
 
void InputReader::loopOnce() {
……
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
……
if (count) {          
processEventsLocked(mEventBuffer, count);
}
……
}

回到之前的InputReader的线程中通过EventHub的getevents方法得到了input事件。函数返回的是获取的事件数目。如果事件不为零或者负数就会调用processEventLocked来处理。

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
……
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
……
case EventHubInterface::DEVICE_ADDED:              
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
……
case EventHubInterface::DEVICE_REMOVED:              
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
}
在处理input的事件时候如果不是设备的添加删除和完成扫描的时候。就会调用process eventsForDeviceLocked来处理。

Framework/base/services/input/InputReader.cpp
 
void InputReader::processEventsForDeviceLocked(int32_t deviceId,const RawEvent* rawEvents, size_t count) {
……
device->process(rawEvents, count);
}
 
这个函数也很简单直接回调了device的process方法来进行处理这个device就是在之前的eventHub打开设备时候构造了一个device类。下面来具体看看device的process是如何进行处理的。 
Framework/base/services/input/InputReader.cpp
 
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
……
for (size_t i = 0; i < numMappers; i++) {              
InputMapper* mapper = mMappers[i];             
mapper->process(rawEvent);     
}
……
}
这里直接调用了mapper的process。那InputMapper是什么时候初始化的呢前面提到如果设备不是设备的添加或删除的时候就调用processEventsForDeviceLocked来处理。也就是说当设备第一次添加的时候就会调用addDeviceLocked。InputMapper就是从这个地方注册过来的。具体看下面的代码

Framework/base/services/input/InputReader.cpp
 
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
……
InputDevice* device = createDeviceLocked(deviceId, name, classes);
……
}
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,const String8& name, uint32_t classes) {
……
InputDevice* device = new InputDevice(&mContext, deviceId, name, classes);
……
if (classes & INPUT_DEVICE_CLASS_SWITCH) {   
device->addMapper(new SwitchInputMapper(device));
}
……
if (classes & INPUT_DEVICE_CLASS_CURSOR) {    
device->addMapper(new CursorInputMapper(device));
}
……
if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
device->addMapper(new MultiTouchInputMapper(device));
} else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
device->addMapper(new SingleTouchInputMapper(device));
}
……
if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {    
device->addMapper(new JoystickInputMapper(device));
}
return device;
}
从上面的代码可以非常明显的看出InputMapper是根据inputDevice的class来构造的这么一个类。也就是说如果我们的设备是Switch类就为这个设备构造一个SwitchInputMapper类我们假设我们现在的事件是由触摸屏上报的事件处理流程。在前面处理的过程中我们就会调用MultiTouchInputMapper的process来继续处理。也就是说当处理到mapper->process的时候代码就会根据具体的设备class类型来处理相应的事件。这么多inputMapper我们就以MultiTouchInputMapper的流程继续来分析事件的处理流程。

Framework/base/services/input/InputReader.cpp
 
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
TouchInputMapper::process(rawEvent);
mMultiTouchMotionAccumulator.process(rawEvent);
}

void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
……
switch (rawEvent->scanCode) {
case ABS_MT_POSITION_X:
slot->mInUse = true;      
slot->mAbsMTPositionX = rawEvent->value;
……
case ABS_MT_POSITION_Y:
 slot->mAbsMTPositionY = rawEvent->value;
……
case ABS_MT_TOUCH_MAJOR:             
slot->mInUse = true;            
slot->mAbsMTTouchMajor = rawEvent->value;            
break;
……
case ABS_MT_TOUCH_MINOR:         
slot->mInUse = true;          
slot->mAbsMTTouchMinor = rawEvent->value;      
slot->mHaveAbsMTTouchMinor = true;
……
}

在这个处理过程中将上报的原始事件的一些重要参数都赋值给了一个叫slot的结构的成员。我们知道多点触摸上报的一些参数主要包括X轴和Y轴的坐标点ABS_MT_TOUCH_MAJOR代表手指和触摸屏接触面的长轴(如果假定人手指和触摸屏接触面一般都是椭圆)ABS_MT_TOUCH_MINOR代表手指和屏幕接触面的短轴等等一些重要的参数都对号入座的赋值给了一个叫slot的结构体成员具体更多的参数代表什么意思我就不多说了(大家可以参照linux内核文档多点触摸协议)。MultiTouchInputMapper的process处理分了两部分首先是调用了TouchInputMapper的process方法然后调用了mMultiTouchMotionAccumulator.process来进行处理。先看TouchInputMapper的处理流程。 

Framework/base/services/input/InputReader.cpp
 
void TouchInputMapper::process(const RawEvent* rawEvent) {
……
if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
sync(rawEvent->when);
}
}
 
在linux内核往上层上报input event的时候是由顺序的以触摸屏为例input_report_abs会被用来上报一些绝对事件(如接触面积半径X和Y轴的坐标点)但是在每一次上报完成后都会调用input_sync(input_dev);来表示一次完整的事件上报换句话说这个rawEvent->type为EV_SYN的事件就是许多input event的分隔符。
Framework/base/services/input/InputReader.cpp
 
void TouchInputMapper::sync(nsecs_t when) {
……
bool havePointerIds = true;
mCurrentRawPointerData.clear();
syncTouch(when, &havePointerIds);
……
cookPointerData();
……
dispatchHoverExit(when, policyFlags);
dispatchTouches(when, policyFlags);
dispatchHoverEnterAndMove(when, policyFlags);
……
}

 
通过同步触摸事件然后调用cookPointerData来计算X和Y的坐标前后两次上报数据的距离和根据缩放的大小及方向来判断是不是需要旋转屏幕。然后dispatchTouches中又调用了disaptchMotion方法。

Framework/base/services/input/InputReader.cpp
 
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,…) {
……
getListener()->notifyMotion(&args);
}
通过getLister的方法得到InputListener并调用其notifyMotion。 

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
mArgsQueue.push(new NotifyMotionArgs(*args));
}

在构造了一个NotifyMotionArgs类之后我们回到之前的InputReader的loopOnce方法中在最后调用了mQueuedListener->flush();Flush函数就是要把mArgsQueue中的所有NotifyArgs进行处理。为描述方便先看看其代码
Framework/base/services/input/InputLisenter.cpp
 
void QueuedInputListener::flush() { 
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];     
args->notify(mInnerListener);  
delete args;
}
mArgsQueue.
clear();
}

 
继续在NotifyArgs中调用了其notify的方法。还是在这个文件中可以看看我们NotifyMotionArgs的notify方法。 

Framework/base/services/input/InputLisenter.cpp
void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyMotion(this);
}

在InputDispatcher.h中InputDispatcherInterface是继承InputListenerInterface父类调用了子类的虚函数notifyMotion的实例化。在inputDispacher的notifyMotion中继续这就重新回到了我们上面分析的InputDispatch线程的代码。正好完成了一边生产事件而在另一边消费事件。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值