Android鼠标移动事件在framework层的传递流程

前言

上一篇博文介绍了利用uinput创建和使用虚拟设备,设备产生的事件序列会被存储在/dev/input目录下,同一设备产生的事件单独存储在同一文件中。那么存入/dev/input目录下的事件序列是怎样被读取、处理、传递到应用层的呢,这篇文章就以鼠标的一个移动事件为例,讲解事件在framework层的传递流程。

EventHub对事件的收集

设备事件的读取方式

写入到/dev/input目录下的设备事件,将由Android framework目录中EventHub.cpp文件进行读取。但事件的产生并非周期性的,并且需要主动获取/dev/input的变动事件。Android系统并没有采取轮询的方式进行处理,而是借由linux系统中的INotify与Epoll机制。
在EventHub.cpp的构造函数中,有以下一段代码

mEpollFd = epoll_create(EPOLL_SIZE_HINT);
...
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
...
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

程序中,INotify用于检查文件系统中/dev/input目录的发生的变化,当目录中有添加、删除文件,或是在文件中写入设备事件,都会产生一个inotify事件。而Epoll的作用就是监听inotify事件的发生。这样通过epoll_wait(),就能够实时检测到设备事件的发生。

收集事件与确定设备类型

在EventHub.cpp中,确定设备与事件类型的入口是在getEvent(),该函数的调用位于InputReader.cpp的loopOnce函数中。在EventHub.cpp中,首先通过如下方式确定读取事件的路径

static const char *DEVICE_PATH = "/dev/input";

于是在getEvent函数中,通过调用scanDevicesLocked()函数获取事件,
获取事件类型的关键代码如下,可见这段代码之上的部分有对事件的收集处理,之后将一个事件的数据统一放入结构体RawEvent中,方便后续传递,其中event为RawEvent结构体的对象

event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;

另外又是如何知道发出事件的设备类型呢?也是在getEvent()中,该函数通过readNotifyLocked()间接调用openDeviceLocked()
首先看readNotifyLocked()调用openDeviceLocked()函数的代码

strcpy(devname, DEVICE_PATH);
filename = devname + strlen(devname);
*filename++ = '/';

while(res >= (int)sizeof(*event)) {
    
    
    event = (struct inotify_event *)(event_buf + event_pos);
    //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
    if(event->len) {
    
    
        strcpy(filename, event->name);
        if(event->mask & IN_CREATE) {
    
    
            openDeviceLocked(devname);
        } else {
    
    
            ALOGI("Removing device '%s' due to inotify event\n", devname);
            closeDeviceByPathLocked(devname);
        }
    }
    event_size = sizeof(*event) + event->len;
    res -= event_size;
    event_pos += event_size;
}

接下来,在函数openDeviceLocked()中分辨出设备的类型,如鼠标

// See if this is a cursor device such as a trackball or mouse.
if (test_bit(BTN_MOUSE, device->keyBitmask)
        && test_bit(REL_X, device->relBitmask)
        && test_bit(REL_Y, device->relBitmask)) {
    
    
    device->classes |= INPUT_DEVICE_CLASS_CURSOR;
}

InputReader对事件的处理与存储

事件的处理过程

通过前面的流程可获取一个设备事件并进行分类,正如前面所说,getEvent()的调用位于InputReader.cpp的loopOnce函数中

size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

该过程返回mEventBuffer,即第2部分讲述的使用getEvent函数获得的RawEvent结构体。这样InputReader.cpp便获取到设备事件,接下来就是对事件的处理过程。从该句代码继续往下看,有这样一句

processEventsLocked(mEventBuffer, count);

对事件的处理过程便从这里开始,processEventsLocked()遍历获取到的event数组

for (const RawEvent* rawEvent = rawEvents; count;)

在for 循环中的主要处理代码如下

if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
    
    
    int32_t deviceId = rawEvent->deviceId;
    while (batchSize < count) {
    
    
        if (rawEvent[batchSize].type >= 
                        EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
    
    
            break;
        }
        batchSize += 1;
    }
#if DEBUG_RAW_EVENTS
    ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
#endif
    processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
}

以上的语句中,使用if判断是否为常规的event,随后通过deviceId判断是否为同一设备,这样便对每一个事件进行了分组。之后调用processEventsForDeviceLocked()做进一步的处理,processEventsForDeviceLocked()内容定义为

ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
    
    
    ALOGW("Discarding event for unknown deviceId %d.", deviceId);
    return;
}

InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()) {
    
    
    //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
    return;
}

device->process(rawEvents, count);

可见,通过以上代码,去除了属性为unknown deviceId以及ignored deviceId的event事件。如为正常事件,则调用InputDevice类中process()函数进行处理。函数的原型为

void InputDevice::process(const RawEvent* rawEvents, size_t count)

函数中第一句为注释,可见是为每一个mapper依次进行事件处理

// Process all of the events in order for each mapper.

通过判断rawEvent->type及rawEvent->code,决定是否执行mapper的proces函数,如果调用,那么接下来是如何处理的呢?这里以一个鼠标的移动事件为例。对于鼠标的移动,执行一下语句后

mapper->process(rawEvent);

会调用CursorInputMapper的process函数,定义为

void CursorInputMapper::process(const RawEvent* rawEvent) {
    
    
    mCursorButtonAccumulator.process(rawEvent);
    mCursorMotionAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT){
    
    
        sync(rawEvent->when);
    }
}

鼠标移动是调用的mCursorMotionAccumulator.process(rawEvent)这一句,用以上报鼠标移动的Rel_X,Rel_Y。经过3次process的处理后,便完成了一个事件的处理。

将处理后的事件存入队列

依然是在CursorInputMapper的process()函数中,如果为一个同步sync的事件,那么就执行sync()进行同步,函数原型为

void CursorInputMapper::sync(nsecs_t when)

经过一系列同步过程后,随后执行如下语句

getListener()->notifyMotion(&args);

上面一句的作用是调用InputListener.cpp中QueuedInputListener类的notifyMotion函数,如下

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

函数的功能便是将事件push进mArgsQueue链表队列,此即为InputDispatcher的mInboundQueue队列。

通知InputDispatcher读取队列中事件

在loopOnce函数中,做完一次事件的处理与分发后,将会执行如下语句

mQueuedListener->flush();

函数flush()中,会执行

args->notify(mInnerListener);

该语句又将调用InputListener.cpp中NotifyMotionArgs类的notify函数,如下

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    
    
    listener->notifyMotion(this);
}

可见这里又调用了一次notifyMotion(),而这次调用的即为InputDispatcher中的notifyMotion(),至此InputDispatcher接受到通知,进行后续处理。

InputDispatcher对事件的分发

InputDispatcher分发事件的触发机制

通过上述可知,InputReader处理及存储完一个事件后,通过使用InputListener中NotifyMotionArgs类来调用 InputDispatcher的notify函数,其原型为

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args)

该函数通过调用InputManager.cpp的initialize函数唤醒InputDispatcherThread,然后通过调用dispatchOnce()开始事件的分发

bool InputDispatcherThread::threadLoop() {
    
    
    mDispatcher->dispatchOnce();
    return true;
}

对事件的分发过程

事件的分发从dispatchOnce()开始。首先看函数最后一行

mLooper->pollOnce(timeoutMillis);

作用是通过执行pollOnce(),进入epoll_wait等待状态,等待回调、超时或被唤醒。当有事件时,判断是否有挂起命令,如果没有则执行以下语句

dispatchOnceInnerLocked(&nextWakeupTime);

该函数的作用是调用dropInboundEventLocked()从InboundQueue中读取事件以及调用dispatchMotionLocked()进行事件分发
首先看取出InboundQueue队列中事件的代码,先判断了dropReason

if (done) {
    
    
    if (dropReason != DROP_REASON_NOT_DROPPED) {
    
    
        dropInboundEventLocked(mPendingEvent, dropReason);
    }
    mLastDropReason = dropReason;
    releasePendingEventLocked();
    *nextWakeupTime = LONG_LONG_MIN;
}

接下来看看进行事件分发的前提条件,即判断后获取dropReason的值

case EventEntry::TYPE_MOTION: {
    
    
    MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
    if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
    
    
        dropReason = DROP_REASON_APP_SWITCH;
    }
    if (dropReason == DROP_REASON_NOT_DROPPED && isStaleEventLocked(currentTime, typedEntry)) {
    
    
            dropReason = DROP_REASON_STALE;
    }
    if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
    
    
            dropReason = DROP_REASON_BLOCKED;
    }
    done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
    break;
}

现在进入dispatchMotionLocked()函数,首先执行以下代码找到目标窗口,可以看到内容中出现了两个重要的寻找窗口函数

bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
    if (isPointerEvent) {
    
    
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
    
    
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
    
    
        return false;
}

在找到目标窗口后,通过调用dispatchEventLocked()执行事件分发,见以下语句,通过getConnectionIndexLocked()从mConnectionByFd队列获得目标connection,再执行prepareDispatchCycleLocked()做后续处理

ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
    if (connectionIndex >= 0) {
    
    
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
}

之后,在prepareDispatchCycleLocked函数中过滤掉不正常状态的connection后,调用enqueueDispatchEntriesLocked()函数。
在enqueueDispatchEntriesLocked()函数中,通过eventEntry、inputTarget的差异调用不同的enqueueDispatchEntryLocked()函数。被调用的enqueueDispatchEntryLocked()函数末尾有如下语句,作用就是将事件加入到OutboundQueue队列中。

// Enqueue the dispatch entry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
traceOutboundQueueLengthLocked(connection);

之后,enqueueDispatchEntriesLocked()函数执行的语句如下

if (wasEmpty && !connection->outboundQueue.isEmpty()) {
    
    
    startDispatchCycleLocked(currentTime, connection);
}

现在进入到startDispatchCycleLocked()函数,函数执行以下语句将事件从OutboundQueue取出事件,重新放入connection的wait queue中等待被发送至应用层

// Re-enqueue the event on the wait queue.
connection->outboundQueue.dequeue(dispatchEntry);
traceOutboundQueueLengthLocked(connection);
connection->waitQueue.enqueueAtTail(dispatchEntry);
traceWaitQueueLengthLocked(connection);

通知应用层接收事件

同样是在startDispatchCycleLocked()函数中,当事件分发完毕后,便通知应用层接收事件,处理的方法是执行如下语句

status = connection->inputPublisher.publishMotionEvent();

该语句实际上是执行InputTransport.cpp中的publishMotionEvent函数。该函数创建InputMessage对象msg后,定义消息msg的属性,通过sendMessage()函数以socket方式发送出去,通知应用层接收事件。
至此,事件进入framework层直至被发送到应用层的流程已分析完毕。

总结

framework层传递事件的整个程序执行顺序如下
① InputManager.cpp中创建EventHub对象,调用EventHub构造函数。随EventHub对象创建的还有InputDispatcher及InputReader对象。
② 在EventHub.cpp中,EventHub构造函数建立Epoll机制监听设备事件发生,当有事件发生时,从/dev/input目录读取事件,并辨别事件类型。
③ InputReader获取EventHub收集到的事件,经过处理后存储到InboundQueue队列中,通过notify唤醒InputDispatcherThread。
④ InputDispatcher从InboundQueue取出事件,寻找到事件传递的窗口,然后将事件临时放入OutboundQueue,之后取出利用socket发送给目标窗口。
本文主要介绍鼠标的移动事件在framework层的传递流程,若是其他设备事件,参考本文的分析方法,也能梳理出其进入framework层后是怎样被传递到应用层的。

参考文献

https://blog.csdn.net/cheris_cheris/article/details/53306833
https://blog.csdn.net/jscese/article/details/42739197
https://www.jianshu.com/p/d22d86cb04c7

猜你喜欢

转载自blog.csdn.net/qq_33594636/article/details/103119062
今日推荐