SwallowJoe的博客

Be a real go-getter,
NEVER SETTLE!

0%

WMS(9)-Input事件分发给窗口的过程

以下分析基于Android S.

简述

在前面两篇文章中我们打通了应用进程和Input进程,这两者通过一对名为InputChannel实际是通过socket实现的通道来通信。然后我们又梳理了窗口信息是如何更新并传递给Input进程的。现在我们简单梳理一下一次触摸事件分发给窗口的过程。

事件要分发,首先是需要找到被分发的事件和对应的目标窗口。

一. 查找Input事件的目标窗口

Input事件是显示屏驱动收到中断后通知给InputReader,由其打包交给InputDispatcher分发的,我们直接看InputDispatcher的MotionEvent分发过程。

1.1 InputDispatcher.dispatchMotionLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {

......
// 通过输入源确认是否为点击触摸事件
bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
// 输入事件的目标窗口集合
std::vector<InputTarget> inputTargets;
// 触摸屏事件
if (isPointerEvent) {
// [1.2] 找到此次触摸事件的目标窗口
injectionResult =
findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
&conflictingPointerActions);
}
......
// [2.1] Input事件的分发
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
}

Android的输入源有如下几种:

名称 含义
AINPUT_SOURCE_CLASS_NONE 0 未定义,交给应用自行处理
AINPUT_SOURCE_CLASS_BUTTON 1 按键之类的设备
AINPUT_SOURCE_CLASS_POINTER 2 输入源是一个与显示器相关联的指向设备,如触摸屏
AINPUT_SOURCE_CLASS_NAVIGATION 4 输入源轨迹球导航设备
AINPUT_SOURCE_CLASS_POSITION 8 输入源是与显示器无关的绝对定位装置,如触控板
AINPUT_SOURCE_CLASS_JOYSTICK 16 输入源是一个操纵杆,摇杆设备

当然,MotionEvent肯定是来自AINPUT_SOURCE_CLASS_POINTER了。

1.2 InputDispatcher.findTouchedWindowTargetsLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
......
sp<InputWindowHandle> newTouchedWindowHandle;
......
TouchState tempTouchState;
......
bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
// 区分是否为新的手势事件(区别与ACTION_MOVE)
bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
......
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
int32_t x;
int32_t y;
......
// 获取此次触摸事件在屏幕坐标中的位置
x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
......
// [1.3] 根据触摸事件的坐标找到可触摸的窗口
newTouchedWindowHandle =
findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
......
if (newTouchedWindowHandle != nullptr) {
......
// [1.4] 将找到的目标窗口存入TouchState中
tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
}
.......
}
......
// 依次遍历找到的目标窗口
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
// [1.5] 将找到的目标窗口存入inputTargets中
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, inputTargets);
}
......
}

1.3 InputDispatcher.findTouchedWindowAtLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
int32_t y, TouchState* touchState,
bool addOutsideTargets,
bool addPortalWindows,
bool ignoreDragWindow) {
......
// [1.3.1] 从mWindowHandlesByDisplay中拿到当前该display中所有的窗口信息
const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
......
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId == displayId) {
auto flags = windowInfo->flags;
// 首先窗口必须是可见的,不可见窗口无法接收input事件
if (windowInfo->visible) {
// 窗口必须不携带NOT_TOUCHABLE的flag
if (!flags.test(InputWindowInfo::Flag::NOT_TOUCHABLE)) {
bool isTouchModal = !flags.test(InputWindowInfo::Flag::NOT_FOCUSABLE) &&
!flags.test(InputWindowInfo::Flag::NOT_TOUCH_MODAL);
// [1.3.2] 判断坐标是否位于该窗口内或者该窗口是TOUCH_MODAL的
if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
......
// 找到的第一个窗口直接返回
return windowHandle;
}
}
......
}
}
}
return nullptr;
}

首先从mWindowHandlesByDisplay中拿到当前该display中所有的窗口信息,依次遍历,判断窗口是否可见,不可见窗口无法接收input事件。如果可见,在判断窗口是否携带NOT_TOUCHABLE的flag。如果不携带,只要触摸事件的坐标位于该窗口的可触摸区域内,就返回该窗口。

注意getWindowHandlesLocked拿到的窗口是有顺序的,index越小,Z轴越大。因为在SurfaceFlinger.updateInputWindowInfo时我们遍历layer的顺序是沿着Z轴反向遍历的。那么窗口在上面的会存储在mWindowHandlesByDisplay中对应队列的前面,所以这里只需要找到第一个窗口返回即可。

1.3.1 InputDispatcher.getWindowHandlesLocked

1
2
3
4
5
6
const std::vector<sp<InputWindowHandle>>& InputDispatcher::getWindowHandlesLocked(
int32_t displayId) const {
static const std::vector<sp<InputWindowHandle>> EMPTY_WINDOW_HANDLES;
auto it = mWindowHandlesByDisplay.find(displayId);
return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES;
}

从mWindowHandlesByDisplay中拿到当前该display中所有的窗口信息(WMS中addWindow后添加的)。

1.3.2 InputWindowInfo.touchableRegionContainsPoint

1
2
3
bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
return touchableRegion.contains(x,y);
}

判断该窗口的可触摸区域是否包含此坐标点。

1.4 TouchState.addOrUpdateWindow

1
2
3
4
5
6
7
8
9
10
void TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags,
BitSet32 pointerIds) {
......
// 封装成TouchedWindow存入windows队列的末尾
TouchedWindow touchedWindow;
touchedWindow.windowHandle = windowHandle;
touchedWindow.targetFlags = targetFlags;
touchedWindow.pointerIds = pointerIds;
windows.push_back(touchedWindow);
}

将找到的目标窗口封装成TouchedWindow存入TouchState.windows队列的末尾。

1.5 InputDispatcher.addWindowTargetLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds,
std::vector<InputTarget>& inputTargets) {
// 使用find_if函数,查找inputTargets中inputChannel的token和传入的窗口token一致的InputTarget
std::vector<InputTarget>::iterator it =
std::find_if(inputTargets.begin(), inputTargets.end(),
[&windowHandle](const InputTarget& inputTarget) {
return inputTarget.inputChannel->getConnectionToken() ==
windowHandle->getToken();
});

const InputWindowInfo* windowInfo = windowHandle->getInfo();
// 当it是指向inputTargets.end()时,说明inputTargets中不存在和传入的窗口token一致的InputTarget对象
if (it == inputTargets.end()) {
// 创建新的InputTarget,对应传入的窗口,放在inputTargets队列的末尾
InputTarget inputTarget;
std::shared_ptr<InputChannel> inputChannel =
getInputChannelLocked(windowHandle->getToken());
if (inputChannel == nullptr) {
ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
return;
}
inputTarget.inputChannel = inputChannel;
inputTarget.flags = targetFlags;
inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
inputTarget.displaySize =
vec2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
inputTargets.push_back(inputTarget);
it = inputTargets.end() - 1;
}

ALOG_ASSERT(it->flags == targetFlags);
ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
// 将pointerIds存入InputTarget, 顺便将transform存入pointerTransforms中,以方便做转换
it->addPointers(pointerIds, windowInfo->transform);
}

这里也是比较简单的一个函数,首先使用find_if函数,查找inputTargets中inputChannel的token和传入的窗口token一致的InputTarget,如果不存在就根据传入的窗口信息windowHandle创键新的InputTarget并存入inputTargets队列的末尾。

二. Input事件分发

2.1 InputDispatcher.dispatchEventLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
std::shared_ptr<EventEntry> eventEntry,
const std::vector<InputTarget>& inputTargets) {
......
// 这里通过JNI调用到PowerManagerService, 更新自动灭屏的时间,这样触碰屏幕后自动灭屏就重新开始计时了
pokeUserActivityLocked(*eventEntry);
// 依次遍历所有的目标窗口
for (const InputTarget& inputTarget : inputTargets) {
// [2.1.1] 拿到目标窗口InputChannel对应的Connection
sp<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
// [2.2] 准备分发
prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
} else {
if (DEBUG_FOCUS) {
ALOGD("Dropping event delivery to target with channel '%s' because it "
"is no longer registered with the input dispatcher.",
inputTarget.inputChannel->getName().c_str());
}
}
}
}

2.1.1 InputDispatcher.getConnectionLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
if (inputConnectionToken == nullptr) {
return nullptr;
}

for (const auto& [token, connection] : mConnectionsByToken) {
if (token == inputConnectionToken) {
return connection;
}
}

return nullptr;
}

传入的token就是InputChannel初始化时创建BBinder, 而InputChannel创建完成后会生成Connection存入mConnectionsByToken,这个发生在InputDispatcher::createInputChannel中。

2.2 InputDispatcher.prepareDispatchCycleLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection,
std::shared_ptr<EventEntry> eventEntry,
const InputTarget& inputTarget) {
......
// 将Input事件和对应分发的连接作为一次分发事件存入Input分发队列
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection,
std::shared_ptr<EventEntry> eventEntry,
const InputTarget& inputTarget) {
......
// 判断该连接是否存在input事件待分发
bool wasEmpty = connection->outboundQueue.empty();
......
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_IS);
......
// 如果出站队列之前为空,则启动调度周期
if (wasEmpty && !connection->outboundQueue.empty()) {
// [2.3] 启动input事件分发
startDispatchCycleLocked(currentTime, connection);
}
}

2.3 InputDispatcher.startDispatchCycleLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
......
// InputChannel连接正常,将待分发队列里的事件一一分发
while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
// 每次都取第一个事件,保证先进先出
DispatchEntry* dispatchEntry = connection->outboundQueue.front();
......
const EventEntry& eventEntry = *(dispatchEntry->eventEntry);
switch (eventEntry.type) {
......
case EventEntry::Type::MOTION: {
......
// [2.4] 发布input事件,将input事件所有信息都发出去
status = connection->inputPublisher
.publishMotionEvent(......);
......
}

当InputChannel对应的连接正常,则将待分发队列里的事件一一分发,每次都取outboundQueue中的第一个事件,保证先进先出。

Connection中的InputPublisher就是Connection初始化时创建的InputPublisher。

2.4 InputPublisher.publishMotionEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
status_t InputPublisher::publishMotionEvent(
uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
int32_t edgeFlags, int32_t metaState, int32_t buttonState,
MotionClassification classification, const ui::Transform& transform, float xPrecision,
float yPrecision, float xCursorPosition, float yCursorPosition, int32_t displayWidth,
int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
......
// 将input事件封装成InputMessage
InputMessage msg;
msg.header.type = InputMessage::Type::MOTION;
......
msg.body.motion.eventTime = eventTime;
msg.body.motion.pointerCount = pointerCount;
......
// 交给InputChannel将input事件通知给应用进程
return mChannel->sendMessage(&msg);
}

将input事件封装成InputMessage, 在交给InputChannel将input事件通知给应用进程.

2.5 InputChannel.sendMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
status_t InputChannel::sendMessage(const InputMessage* msg) {
const size_t msgLength = msg->size();
InputMessage cleanMsg;
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
// 交给Socket进行通信
nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);

......
return OK;
}

经过InputChannel, 此次的input事件就通知给了目标窗口所在进程。回忆一下,我们应用进程中注册该InputChannel对的Socket文件描述符是在NativeInputEventReceiver中的:

1
2
3
4
5
6
7
8
9
10
11
12
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {
// 将InputChannel中的客户端Socket的文件描述符加入的Looper中监听
mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}

所以我们应用进程收到该socket文件描述符消息时,会调用NativeInputEventReceiver::handleEvent函数了(Looper机制)。

三. 应用进程收到Input事件

3.1 NativeInputEventReceiver.handleEvent

1
2
3
4
5
6
7
8
9
10
11
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
......
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
// [3.2] 消费此次input事件
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
}
......
}

3.2 NativeInputEventReceiver.consumeEvents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
......
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
// [3.3] 将接收到的数据封装成InputEvent
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
......
if (!skipCallbacks) {
......
jobject inputEventObj;
switch (inputEvent->getType()) {
......
case AINPUT_EVENT_TYPE_MOTION: {
MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
// 通过JNI创建java层的MotionEvent对象
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
......
}
if (inputEventObj) {
// [3.4] 通过JNI调用dispatchInputEvent将Input事件分发给应用java层的View
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
......
// 此次Input事件使用之后就回收
env->DeleteLocalRef(inputEventObj);
}
}
}
}

consumeEvents就是接收socket数据,将input事件封装成对应类型的对象,如触摸事件对应MotionEvent,按键事件对应KeyEvent. 然后通过JNI创建对应java层的input事件对象,最后调用InputEventReceiver.dispatchInputEvent将Input事件分发给应用java层的View。

3.3 InputConsumer.consume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
......
*outSeq = 0;
*outEvent = nullptr;
while (!*outEvent) {
......
// [3.3.1] 通过recv函数接收Socket数据
status_t result = mChannel->receiveMessage(&mMsg);
......
switch (mMsg.header.type) {
......
case InputMessage::Type::MOTION: {
......
// 创建MotionEvent
MotionEvent* motionEvent = factory->createMotionEvent();
if (!motionEvent) return NO_MEMORY;
// 更新触摸状态信息
updateTouchState(mMsg);
// 使用收到的InputMessage填充创建的MotionEvent
initializeMotionEvent(motionEvent, &mMsg);
}
......
}
}
return OK;
}

这里首先在InputChannel中通过recv函数接收来自Input系统的Socket数据(InputMessage), 然后根据input类型分别封装成对应的input事件,比如MotionEvent、KeyEvent等。

3.3.1 InputChannel.receiveMessage

1
2
3
4
5
6
7
8
status_t InputChannel::receiveMessage(InputMessage* msg) {
ssize_t nRead;
do {
// 通过recv函数接收Socket数据
nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
} while (nRead == -1 && errno == EINTR);
......
}

通过recv函数接收Socket数据, 还原成InputMessage.

3.4 WindowInputEventReceiver.dispatchInputEvent

1
2
3
4
5
6
7
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}

注意这里JNI注册的InputEventReceiver其实是ViewRootImpl中的WindowInputEventReceiver, 所以onInputEvent其实是调用了被Override的子类函数。

1
2
3
4
5
6
7
8
// WindowInputEventReceiver.java
@Override
public void onInputEvent(InputEvent event) {
......
} else {
enqueueInputEvent(event, this, 0, true);
}
}

后面将事件分发给对应的View组件的过程,其实也不难猜测,因为View是树形结构,只需要前序遍历该树找到input事件坐标所在的最叶子节点的View,如果该View消耗了此次事件,也就是设置了对应的Listener并实现了接口返回true, 那么该事件就不继续分发了。否则沿着树形结构依次遍历父View,看是否需要使用该事件。

到这里,Input和窗口的关系分析就告一段落了。接下来分析窗口对应的View和SurfaceFlinger通信过程。

欢迎关注我的其它发布渠道