Android触控事件处理机制(基于Android 11)

  • Home
  • Android触控事件处理机制(基于Android 11)
  • By: admin

作者:安卓M豆先生

1 概述

用户手指点击按压屏幕后,屏幕触控驱动产生中断,Linux内核会将硬件产生的触控事件包装成Event放在设备的dev/input/目录下。此后Android系统需要解决以下几个问题以实现整个触控事件的分发处理:

如何从设备上读取触控事件?读取到触控事件后该如何派发事件?派发事件时如何找到接收事件的目标应用窗口?找到目标应用窗口后如何将事件传递到目标窗口?目标应用窗口内部中的事件如何处理?

下面将结合最新Android 11系统源码,通过分析回答这些问题来了解Android系统触控事件处理机制的全貌。

2 触控事件的读取

Android所有的input输入设备都会在/dev/input目录下生成对应的设备节点,一旦有任何输入事件产生,Linux内核便会将事件写到这些节点下,同时对于外部输入设备(鼠标、键盘等)的插拔还会引起这些节点的创建和删除。系统封装了一个叫EventHub的对象,它负责利用Linux的inotify和epoll机制监听/dev/input目录下的设备事件节点,通过EventHub的getEvents接口就可以监听并获取该事件。

系统开机启动system_server进程时会创建启动InputManagerService核心服务,其中会先通过JNI调用创建InputManager对象,然后进一步新建一个InputReader对象;然后通过start方法为InputReader创建一个InputThread的Loop工作线程,这个线程的工作就是通过EventHub的getEvents监听读取Input事件。详细流程如下图所示:

简要代码流程如下:

/*frameworks/base/services/java/com/android/server/SystemServer.java*/

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {

...

// 1.先创建InputManagerService对象

inputManager = new InputManagerService(context);

...

// 2.start启动相关input线程

inputManager.start();

...

}

/*frameworks/base/services/core/java/com/android/server/input/InputManagerService.java*/

public InputManagerService(Context context) {

...

// 1.JNI调用native接口完成InputManager初始化动作

mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

...

}

public void start() {

// 2.JNI调用native接口启动InputManager工作线程

nativeStart(mPtr);

...

}

/*frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp*/

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,

jobject serviceObj, jobject contextObj, jobject messageQueueObj) {

...

// 创建NativeInputManager对象

NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,

messageQueue->getLooper());

...

}

NativeInputManager::NativeInputManager(jobject contextObj,

jobject serviceObj, const sp& looper) :

mLooper(looper), mInteractive(true) {

...

// 1.创建InputManager对象

mInputManager = new InputManager(this, this);

...

}

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {

...

// 2.调用InputManager的start函数

status_t result = im->getInputManager()->start();

...

}

/*frameworks/native/services/inputflinger/InputManager.cpp*/

InputManager::InputManager(

const sp& readerPolicy,

const sp& dispatcherPolicy) {

// 创建InputDispatcher触控事件分发对象

mDispatcher = createInputDispatcher(dispatcherPolicy);

mClassifier = new InputClassifier(mDispatcher);

// 1.createInputReader创建InputReader事件读取对象,间接持有InputDispatcher的引用,以便事件读取完成后通知其进行事件分发

mReader = createInputReader(readerPolicy, mClassifier);

}

status_t InputManager::start() {

// 启动InputDispatcher工作线程

status_t result = mDispatcher->start();

...

// 2.启动InputReader工作线程

result = mReader->start();

...

return OK;

}

/*frameworks/native/services/inputflinger/reader/InputReader.cpp*/

InputReader::InputReader(std::shared_ptr eventHub,

const sp& policy,

const sp& listener)

: mContext(this),

// 初始化EventHub对象

mEventHub(eventHub),

mPolicy(policy),

mGlobalMetaState(0),

mGeneration(1),

mNextInputDeviceId(END_RESERVED_ID),

mDisableVirtualKeysTimeout(LLONG_MIN),

mNextTimeout(LLONG_MAX),

mConfigurationChangesToRefresh(0) {

// 持有InputDispatcher的引用

mQueuedListener = new QueuedInputListener(listener);

...

}

status_t InputReader::start() {

if (mThread) {

return ALREADY_EXISTS;

}

// 创建名为“InputReader”的InputThread带loop工作线程,并在其中循环执行loopOnce函数

mThread = std::make_unique(

"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });

return OK;

}

void InputReader::loopOnce() {

int32_t oldGeneration;

int32_t timeoutMillis;

bool inputDevicesChanged = false;

std::vector inputDevices;

{

...

// 1\. 从EventHub中监听读取触控事件

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

{

...

if (count) {

// 2.处理读取到的触控事件

processEventsLocked(mEventBuffer, count);

}

...

}

...

// 3.mQueuedListener其实就是InputDispatcher对象,flush会通知唤醒InputDispatcher分发事件

mQueuedListener->flush();

}

通过上面流程,输入事件就可以被读取,经过InputReader::processEventsLocked被初步封装成RawEvent,最后通知InputDispatcher分发事件,下节继续分析事件的分发。

3 触控事件的分发

从上一节的代码流程分析中可以看到:在新建InputManager对象的时候,不仅创建了事件读取对象InputReader,还创建了事件分发对象InputDispatcher。InputReader在事件读取完毕后,会交给InputDispatcher去执行事件分发的逻辑,而InputDispatcher也拥有自己的独立的工作线程。这样设计的好处在于符合单一职责的设计思想,且事件的读取与分发工作在各自的线程中,处理效率更高,避免出现事件丢失。整个架构模型参见业界大神的一张图:

接上一节代码分析,InputReader::loopOnce函数处理中,在触控事件读取完成后,最后一行会调用 mQueuedListener->flush()。mQueuedListener本质上就是InputDispatcher,所以调用flush动作就是通知InputDispatcher进行事件分发。InputDispatcher和InputReader一样,内部也有一个名为“InputDispatcher”的InputThread的Loop工作线程,当触控事件到来时其被唤醒处理事件。此处流程如下图所示:

简化的代码流程如下:

/*frameworks/native/services/inputflinger/InputListener.cpp*/

void QueuedInputListener::flush() {

size_t count = mArgsQueue.size();

for (size_t i = 0; i < count; i++) {

NotifyArgs* args = mArgsQueue[i];

// 触发调用notify

args->notify(mInnerListener);

delete args;

}

mArgsQueue.clear();

}

void NotifyMotionArgs::notify(const sp& listener) const {

// 就是调用InputDispatcher的notifyMotion

listener->notifyMotion(this);

}

/*frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp*/

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {

...

// 1.可以增加业务逻辑,在事件分发前做一些事情

mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);

...

bool needWake;

{

...

if (shouldSendMotionToInputFilterLocked(args)) {

...

// 2\. filterInputEvent此处可以过滤触控事件,不再分发

if (!mPolicy->filterInputEvent(&event, policyFlags)) {

return; // event was consumed by the filter

}

}

...

// 3.将触控事件放入"iq"队列等待分发

needWake = enqueueInboundEventLocked(newEntry);

} // release lock

...

if (needWake) {

// 4.唤醒loop工作线程,触发执行一次dispatchOnce逻辑

mLooper->wake();

}

}

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {

bool needWake = mInboundQueue.empty();

mInboundQueue.push_back(entry);

// 触控事件放入mInboundQueue队列后,并增加"iq"的systrace tag信息

traceInboundQueueLengthLocked();

...

return needWake;

}

void InputDispatcher::traceInboundQueueLengthLocked() {

if (ATRACE_ENABLED()) {

// mInboundQueue队列对应的systrace tag为“iq”

ATRACE_INT("iq", mInboundQueue.size());

}

}

void InputDispatcher::dispatchOnce() {

nsecs_t nextWakeupTime = LONG_LONG_MAX;

{

...

if (!haveCommandsLocked()) {

// 1.具体的事件分发逻辑封装在dispatchOnceInnerLocked中具体处理

dispatchOnceInnerLocked(&nextWakeupTime);

}

...

} // release lock

...

// 2.处理完本次事件分发逻辑后,loop工作线程进入休眠等待状态

mLooper->pollOnce(timeoutMillis);

}

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {

...

switch (mPendingEvent->type) {

...

case EventEntry::Type::MOTION: {

...

// 以Motion event屏幕触控事件类型为例,具体的事件分发处理逻辑封装在dispatchMotionLocked函数中

done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);

break;

}

}

...

}

bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry,

DropReason* dropReason, nsecs_t* nextWakeupTime) {

ATRACE_CALL();

...

if (isPointerEvent) {

// 1\. findTouchedWindowTargetsLocked中找到具体接收处理此触控事件的目标窗口

injectionResult =

findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,

&conflictingPointerActions);

} else {

...

}

...

// 2\. 将此次触控事件分发给目标应用窗口

dispatchEventLocked(currentTime, entry, inputTargets);

return true;

}

从以上代码可以看出,对于触控事件的处理核心逻辑分为两步:

首先通过findTouchedWindowTargetsLocked找到处理此次触控事件的目标应用窗口;下来通过dispatchEventLocked将触控事件发送到目标窗口。

下面将分两节来分别分析讲解这两个步骤。

4 寻找触控事件的目标窗口

Android系统的Primary Display主显示屏上同一时间总是会存在多个可见窗口,如状态栏、导航栏、应用窗口、应用界面Dialog弹框窗口等。用adb shell dumpsys SurfaceFlinger命令可以看到:

Display 19260578257609346 HWC layers:

-----------------------------------------------------------------------------------------------------------------------------------------------

Layer name

Z | Window Type | Layer Class | Comp Type | Transform | Disp Frame (LTRB) | Source Crop (LTRB) | Frame Rate (Explicit) [Focused]

-----------------------------------------------------------------------------------------------------------------------------------------------

com.android.systemui.ImageWallpaper#0

rel 0 | 2013 | 0 | DEVICE | 0 | 0 0 1080 2400 | 0.0 0.0 1080.0 2400.0 | [ ]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cn.nubia.launcher/com.android.launcher3.Launcher#0

rel 0 | 1 | 0 | DEVICE | 0 | 0 0 1080 2400 | 0.0 0.0 1080.0 2400.0 | [*]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

FloatBar#0

rel 0 | 2038 | 0 | DEVICE | 0 | 1065 423 1080 623 | 0.0 0.0 15.0 200.0 | [ ]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

StatusBar#0

rel 0 | 2000 | 0 | DEVICE | 0 | 0 0 1080 111 | 0.0 0.0 1080.0 111.0 | [ ]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

NavigationBar0#0

rel 0 | 2019 | 0 | DEVICE | 0 | 0 2280 1080 2400 | 0.0 0.0 1080.0 120.0 | [ ]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

那如何从这些窗口中找到处理触控事件的那个目标窗口呢?我们接着上一节的分析来看看InputDispatcher::findTouchedWindowTargetsLocked的简化代码逻辑:

/*frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp*/

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,

const MotionEntry& entry,

std::vector& inputTargets,

nsecs_t* nextWakeupTime,

bool* outConflictingPointerActions) {

ATRACE_CALL();

...

// 读取触控事件所属的Display的id信息

int32_t displayId = entry.displayId;

...

// 只有ACTION_DOWN类型代表新的触控事件,需要寻找新的目标窗口,后续的ACTION_MOVE或ACTION_UP类型的触控事件直接发送导已经找到的目标窗口

bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||

maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);

...

if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {

// 读取触控事件的x、y坐标位置

int32_t x;

int32_t y;

if (isFromMouse) {

...

} else {

x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));

y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));

}

// 根据触控事件所属的displayid、x/y坐标位置等属性信息,调用findTouchedWindowAtLocked找到目标窗口

sp newTouchedWindowHandle =

findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,

isDown /*addOutsideTargets*/, true /*addPortalWindows*/);

...

} else {

...

}

...

}

sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,

int32_t y, TouchState* touchState,

bool isMirrorInject,/*Nubia add for mirror*/

bool addOutsideTargets,

bool addPortalWindows) {

...

// 1.根据传入的displayid调用getWindowHandlesLocked找到同一display下的所有可见窗口集合windowHandles

const std::vector> windowHandles = getWindowHandlesLocked(displayId);

// 2.遍历所有可见窗口windowHandles找到目标窗口

for (const sp& windowHandle : windowHandles) {

const InputWindowInfo* windowInfo = windowHandle->getInfo();

if (windowInfo->displayId == displayId) {

int32_t flags = windowInfo->layoutParamsFlags;

if (windowInfo->visible) {

if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {

...

// 3.根据触控事件的x/y坐标位置信息与窗口的可见区域进行匹配找到目标窗口

if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {

...

return windowHandle;

}

}

...

}

}

}

return nullptr;

}

std::vector> InputDispatcher::getWindowHandlesLocked(

int32_t displayId) const {

// 从集合mWindowHandlesByDisplay中根据传入的displayId取出对应的可见窗口集合

return getValueByKey(mWindowHandlesByDisplay, displayId);

}

从上面的代码分析可以看到,整个findTouchedWindowTargetsLocked寻找目标窗口的大概逻辑就是根据读取到的触控事件所属的屏幕displayid、x、y坐标位置等属性,遍历从mWindowHandlesByDisplay中匹配找到目标窗口。那么问题来了,这个掌握着所有可见窗口信息的mWindowHandlesByDisplay集合信息又是从哪儿来的呢?我们接着看看代码:

/*frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp*/

void InputDispatcher::updateWindowHandlesForDisplayLocked(

const std::vector>& inputWindowHandles, int32_t displayId) {

...

// Insert or replace

// 唯一给mWindowHandlesByDisplay集合赋值的地方

mWindowHandlesByDisplay[displayId] = newHandles;

}

void InputDispatcher::setInputWindowsLocked(

const std::vector>& inputWindowHandles, int32_t displayId) {

...

updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);

...

}

// 外部通过调用setInputWindows接口给mWindowHandlesByDisplay赋值

void InputDispatcher::setInputWindows(

const std::unordered_map>>& handlesPerDisplay) {

{ // acquire lock

std::scoped_lock _l(mLock);

for (auto const& i : handlesPerDisplay) {

setInputWindowsLocked(i.second, i.first);

}

}

...

}

从以上代码分析可以知道,其实外部是通过调用InputDispatcher的setInputWindows接口给mWindowHandlesByDisplay赋值的。那又是谁会调用这个函数呢?早期的Android版本上是由系统框架的窗口“大管家”——WindowManagerService中的InputMonitor通过JNI调用的方式间接调用InputDispatcher::setInputWindows,以实现同步可见窗口信息给InputDispatcher。但是最新的Android 11上,这个逻辑改由SurfaceFlinger来完成。SurfaceFlinger会在每一帧合成任务SurfaceFlinger::OnMessageInvalidate中搜集每个可见窗口Layer的信息通过Binder调用InputDispatcher::setInputWindows同步可见窗口信息给InputDispatcher。详细流程如下图所示:

个人理解google的工程师之所以会这么修改的原因可能是出于以下两点:1.触控事件的分发其实只关心所有可见窗口的信息,而SurfaceFlinger其实最清楚所有可见窗口的Layer的触控区域、透明度、图层Z_Order顺序等信息;2.这样WindowManagerService只需要统一负责将窗口信息同步给SurfaceFlinger即可,不再同时维护将可见窗口信息同步给InputDispatcher的逻辑,整体职责更加清晰。最后我们看看简化代码分析:

/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/

void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) {

ATRACE_CALL();

...

updateInputFlinger();

...

}

void SurfaceFlinger::updateInputFlinger() {

ATRACE_CALL();

if (!mInputFlinger) {

return;

}

// 当判断存在窗口Layer图层dirty待更新脏区或Layer图层的触控属性发生变化时,调用updateInputWindowInfo同步窗口信息给InputDispatcher

if (mVisibleRegionsDirty || mInputInfoChanged) {

mInputInfoChanged = false;

updateInputWindowInfo();

} else if (mInputWindowCommands.syncInputWindows) {

...

}

...

}

void SurfaceFlinger::updateInputWindowInfo() {

std::vector inputHandles;

// 1.遍历mDrawingState中的所有可见窗口Layer

mDrawingState.traverseInReverseZOrder([&](Layer* layer) {

if (layer->needsInputInfo()) {

// When calculating the screen bounds we ignore the transparent region since it may

// result in an unwanted offset.

// 2\. 调用Layer::fillInputInfo接口,从每个Layer中读取其可见区域、透明度、触控区域等信息,并封装成InputWindowInfo对象放置到inputHandles集合中

inputHandles.push_back(layer->fillInputInfo());

}

});

// 3.通过Binder调用InputManager服务端的接口setInputWindows将封装好的可见窗口触控信息间接同步到InputDispatcher

mInputFlinger->setInputWindows(inputHandles,

mInputWindowCommands.syncInputWindows ? mSetInputWindowsListener

: nullptr);

}

/*frameworks/native/services/surfaceflinger/Layer.cpp*/

InputWindowInfo Layer::fillInputInfo() {

if (!hasInputInfo()) {

mDrawingState.inputInfo.name = getName();

mDrawingState.inputInfo.ownerUid = mCallingUid;

mDrawingState.inputInfo.ownerPid = mCallingPid;

mDrawingState.inputInfo.inputFeatures =

InputWindowInfo::INPUT_FEATURE_NO_INPUT_CHANNEL;

mDrawingState.inputInfo.layoutParamsFlags = InputWindowInfo::FLAG_NOT_TOUCH_MODAL;

mDrawingState.inputInfo.displayId = getLayerStack();

}

InputWindowInfo info = mDrawingState.inputInfo;

...

// 同步Layer的触控区域信息

info.touchableRegion = info.touchableRegion.translate(info.frameLeft, info.frameTop);

...

return info;

}

5 触控事件发送到目标窗口

5.1 触控事件发送到目标窗口流程

到目前为止,我们已经读取到了触控事件,也找到了触控事件对应的目标窗口。那么下一个问题就是如何将触控事件通知发送到目标窗口?根据前面的分析可以知道,InputReader和InputDispatcher都是在系统system_server进程中创建各自的工作线程来开展具体工作的,而目标窗口又是属于App应用进程的,那么这里就涉及到跨进程通信的问题了。分析源码我们可以发现,目前Android系统采用的是Socket机制来实现跨进程将触控事件通知到目标应用窗口的。下面我们接着前面的分析,从InputDispatcher::dispatchEventLocked函数入手,分析系统是如何一步步实现将触控事件发送到目标窗口的。整个流程如下图所示:

简化的代码流程如下:

/*frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp*/

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,

const std::vector& inputTargets) {

ATRACE_CALL();

...

// 遍历需要发送触控事件的目标窗口inputTargets

for (const InputTarget& inputTarget : inputTargets) {

// 获取目标窗口的连接

sp connection =

getConnectionLocked(inputTarget.inputChannel->getConnectionToken());

if (connection != nullptr) {

// 执行prepareDispatchCycleLocked完成具体的触控事件发送动作

prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);

} else {

...

}

}

}

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,

const sp& connection,

EventEntry* eventEntry,

const InputTarget& inputTarget) {

...

// Skip this event if the connection status is not normal.

// We don't want to enqueue additional outbound events if the connection is broken.

if (connection->status != Connection::STATUS_NORMAL) {

// 当前connection连接状态不正常则返回

...

return;

}

...

// 继续调用enqueueDispatchEntriesLocked

enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);

}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,

const sp& connection,

EventEntry* eventEntry,

const InputTarget& inputTarget) {

...

// 判断目标窗口的connection的outboundQueue "oq"是否为空

bool wasEmpty = connection->outboundQueue.empty();

// 将触控事件放入目标窗口的outboundQueue队列中

enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);

...

// If the outbound queue was previously empty, start the dispatch cycle going.

if (wasEmpty && !connection->outboundQueue.empty()) {

// 调用startDispatchCycleLocked进一步处理

startDispatchCycleLocked(currentTime, connection);

}

}

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,

const sp& connection) {

...

while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {

...

// 1.目标窗口连接状态正常,并且连接的outboundQueue队列不为空

// Publish the event.

status_t status;

EventEntry* eventEntry = dispatchEntry->eventEntry;

switch (eventEntry->type) {

...

case EventEntry::Type::MOTION: {

...

// 2.判断为touch事件(我们本次分析暂时只关心这种类型的触控事件),调用publishMotionEvent函数分发按键

status = connection->inputPublisher

.publishMotionEvent(dispatchEntry->seq,

dispatchEntry->resolvedEventId,

motionEntry->deviceId, motionEntry->source,

motionEntry->displayId, std::move(hmac),

dispatchEntry->resolvedAction,

motionEntry->actionButton,

dispatchEntry->resolvedFlags,

motionEntry->edgeFlags, motionEntry->metaState,

motionEntry->buttonState,

motionEntry->classification, xScale, yScale,

xOffset, yOffset, motionEntry->xPrecision,

motionEntry->yPrecision,

motionEntry->xCursorPosition,

motionEntry->yCursorPosition,

motionEntry->downTime, motionEntry->eventTime,

motionEntry->pointerCount,

motionEntry->pointerProperties, usingCoords);

...

}

...

}

...

// 3.将处理完成的dispatchEntry从connection的outboundQueue移除,从“oq”队列移除

// Re-enqueue the event on the wait queue.

connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),

connection->outboundQueue.end(),

dispatchEntry));

// 更新systrace的相关“oq”队列tag打印

traceOutboundQueueLength(connection);

// 将outboundQueue队列的dispatchEntry放入connection的waitQueue队列,从“oq”队列移除,放入“wq”队列中等待应用进程处理

connection->waitQueue.push_back(dispatchEntry);

if (connection->responsive) {

// 4.开启应用对触控事件处理的ANR追踪

mAnrTracker.insert(dispatchEntry->timeoutTime,

connection->inputChannel->getConnectionToken());

}

// 更新systrace的相关“wq”队列tag打印

traceWaitQueueLength(connection);

}

}

继续分析publishKeyEvent函数:

/*frameworks/native/libs/input/InputTransport.cpp*/

status_t InputPublisher::publishMotionEvent(

uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,

std::array hmac, int32_t action, int32_t actionButton, int32_t flags,

int32_t edgeFlags, int32_t metaState, int32_t buttonState,

MotionClassification classification, float xScale, float yScale, float xOffset,

float yOffset, float xPrecision, float yPrecision, float xCursorPosition,

float yCursorPosition, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,

const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {

...

// 1.拿到输入事件的各种信息之后构造一个InputMessage

InputMessage msg;

msg.header.type = InputMessage::Type::MOTION;

msg.body.motion.seq = seq;

msg.body.motion.eventId = eventId;

msg.body.motion.deviceId = deviceId;

msg.body.motion.source = source;

msg.body.motion.displayId = displayId;

msg.body.motion.hmac = std::move(hmac);

msg.body.motion.action = action;

msg.body.motion.actionButton = actionButton;

msg.body.motion.flags = flags;

msg.body.motion.edgeFlags = edgeFlags;

msg.body.motion.metaState = metaState;

msg.body.motion.buttonState = buttonState;

msg.body.motion.classification = classification;

msg.body.motion.xScale = xScale;

msg.body.motion.yScale = yScale;

msg.body.motion.xOffset = xOffset;

msg.body.motion.yOffset = yOffset;

msg.body.motion.xPrecision = xPrecision;

msg.body.motion.yPrecision = yPrecision;

msg.body.motion.xCursorPosition = xCursorPosition;

msg.body.motion.yCursorPosition = yCursorPosition;

msg.body.motion.downTime = downTime;

msg.body.motion.eventTime = eventTime;

msg.body.motion.pointerCount = pointerCount;

for (uint32_t i = 0; i < pointerCount; i++) {

msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);

msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);

}

// 2.最后通过InputChannel将InputMessage分发给目标窗口

return mChannel->sendMessage(&msg);

}

status_t InputChannel::sendMessage(const InputMessage* msg) {

const size_t msgLength = msg->size();

InputMessage cleanMsg;

msg->getSanitizedCopy(&cleanMsg);

ssize_t nWrite;

do {

// 通过socket机制,向mFd写入数据,发送触控事件到对端目标窗口进程中

nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);

} while (nWrite == -1 && errno == EINTR);

...

return OK;

}

复制代码

最后sendMessage的核心就是向mFd写入数据,mFd是什么呢?mFd其实就是一对socket的其中一个,InputChannel在构造的时候是一对,对应了一对socket,一个代表"client"端,一个代表"server"端,"server"端被注册到了InputDispatcher,"client"端返回给了APP进程,InputDispatcher和APP进程都会对自己的socket一端进行监听,所以APP进程和InputDispatcher就这样完成了通信。至于这个socket是如何创建与注册的,下一小节中我们会详细分析。

5.2 应用APP与InputDispatcher的InputChannel注册与监听

从上一节的分析中给我们了解到,触控事件是被InputDispatcher使用InputChannel封装的Socket发送到目标应用窗口所属的App应用进程的。那么这个InputChannel是什么时候创建和注册的呢?或者说InputDispatcher和APP之间是如何建立这种InputChannel连接的呢?回答这个问题我们需要从App应用启动时应用窗口创建的流程开始讲起:

一般App应用启动时,都需要调用WindowManagerGlobal::addView接口完成向系统WMS服务中添加应用窗口的的逻辑。还是老规矩,我们先放一张流程图来看看这个过程:

进一步结合代码来看看这块流程:

/*frameworks/base/core/java/android/view/WindowManagerGlobal.java*/

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow, int userId) {

...

synchronized (mLock) {

...

// 创建ViewRootImpl实例并调用setView函数完成窗口添加的具体逻辑

root = new ViewRootImpl(view.getContext(), display);

...

// do this last because it fires off messages to start doing things

try {

root.setView(view, wparams, panelParentView, userId);

} catch (RuntimeException e) {

...

}

}

}

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,

int userId) {

synchronized (this) {

if (mView == null) {

...

InputChannel inputChannel = null;

if ((mWindowAttributes.inputFeatures

& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

// 1.创建一个空的InputChannel

inputChannel = new InputChannel();

}

try {

...

// 2.Binder调用WMS的addToDisplayAsUser接口,并将InputChanel作为参数传入,以完成赋值

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

mTempInsets, mTempControls);

} catch (RemoteException e) {

...

}

...

if (inputChannel != null) {

...

// 3.根据赋值后的InputChannel封装创建WindowInputEventReceiver对象,以便后续接收input事件

mInputEventReceiver = new WindowInputEventReceiver(inputChannel,

Looper.myLooper());

}

...

}

}

}

APP应用启动时通过addView添加应用窗口到WMS的过程中,会new一个空的InputChanel,然后通过Binder调用WMS的addToDisplayAsUser接口添加应用窗口时,顺带将其作为参数传到WMS,所以具体给InputChannel赋值是在WMS中完成的。下面我们继续看看WMS中的处理过程:

/*frameworks/base/services/core/java/com/android/wm/Session.java*/

@Override

public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,

int viewVisibility, int displayId, int userId, Rect outFrame,

Rect outContentInsets, Rect outStableInsets,

DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,

InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {

// 具体通过调用WMS的addWindow实现窗口的添加逻辑

return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,

outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,

outInsetsState, outActiveControls, userId);

}

/*frameworks/base/services/core/java/com/android/wm/WindowManagerService.java*/

public int addWindow(Session session, IWindow client, int seq,

LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,

Rect outContentInsets, Rect outStableInsets,

DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,

InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,

int requestUserId) {

...

final boolean openInputChannels = (outInputChannel != null

&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);

if (openInputChannels) {

// 调用WindowState的openInputChannel具体完成inputchannel的赋值动作

win.openInputChannel(outInputChannel);

}

...

return res;

}

/*frameworks/base/services/core/java/com/android/wm/WindowState.java*/

void openInputChannel(InputChannel outInputChannel) {

...

// 应用窗口名称

String name = getName();

// 1.创建InputChannelPair,实现创建socketpair全双工通信信道

InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);

mInputChannel = inputChannels[0];

mClientChannel = inputChannels[1];

// 2.将服务端InputChannel注册到InputDispatcher中

mWmService.mInputManager.registerInputChannel(mInputChannel);

// 这个token唯一标识了接收input事件的App窗口(务必注意这个token保留在WMS中,会同步给SurfaceFlinger然后再到InputDispatcher中去,最终会用来匹配触控事件的目标窗口时使用,注册后,用于后续根据token匹配找到与App应用的connect连接)

mInputWindowHandle.token = mInputChannel.getToken();

if (outInputChannel != null) {

// 3.将客户端InputChannel赋值给outInputChannel,并Binder回传给应用进程中ViewRootImpl中

mClientChannel.transferTo(outInputChannel);

mClientChannel.dispose();

mClientChannel = null;

} else {

...

}

...

}

其实整个建立InputChannel连接过程的核心逻辑就是:

先通过InputChannel::openInputChannelPair创建socketpair全双工的通道,并分别填充到“server”服务端的InputChannel和“client”客户端的InputChannel中。

再通过InputManager::registerInputChannel将“server”服务端的InputChannel注册到InputDispatcher中。

最后通过InputChannel::transferTo通过Binder将outInputChannel回传到APP端,以便APP端那边实现监听input事件的逻辑。

整个InputChannel信道的模型如下图所示:

下面结合代码进一步分析这三个过程:

5.2.1 创建客户端与服务端的InputChannel

从InputChannel.openInputChannelPair入手我们继续分析:

/*frameworks/base/core/java/android/view/InputChannel.java*/

public static InputChannel[] openInputChannelPair(String name) {

...

// 根据方法名我们知道,这是个典型的JNI调用实现,会调用native层的对应接口实现功能

return nativeOpenInputChannelPair(name);

}

/*frameworks/base/core/jni/android_view_InputChannel.cpp*/

static const JNINativeMethod gInputChannelMethods[] = {

/* name, signature, funcPtr */

{ "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;",

(void*)android_view_InputChannel_nativeOpenInputChannelPair },

...

}

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,

jclass clazz, jstring nameObj) {

...

// 1.构建一对native层的InputChannel

status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

...

// 构造一个java层InputChannel类型数组

jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);

...

// 2\. 将native层InputChannel转换为java层InputChannel

jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);

jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);

...

// 将转换的java层InputChannel存到前面构造的java数组

env->SetObjectArrayElement(channelPair, 0, serverChannelObj);

env->SetObjectArrayElement(channelPair, 1, clientChannelObj);

// 3.返回给java层

return channelPair;

}

复制代码

InputChannel::openInputChannelPair的主要逻辑就是JNI调用构造了一对native层InputChannel,然后再根据其创建java层InputChannel,最后返回给java层。我们继续往下看native层InputChannel创建的逻辑:

/*frameworks/native/libs/input/InputTransport.cpp*/

status_t InputChannel::openInputChannelPair(const std::string& name,

sp& outServerChannel, sp& outClientChannel) {

int sockets[2];

// 1.创建基于linux的socketpair全双工的socket通道

if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {

...

return result;

}

// 目前buffer的大小SOCKET_BUFFER_SIZE的默认值是32*1024也就是32K

int bufferSize = SOCKET_BUFFER_SIZE;

// 2.设置socket发送和接收缓冲区大小为bufferSize

setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));

setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));

setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

// 3.创建BBinder,用于唯一标识APP进程的应用窗口

sp token = new BBinder();

std::string serverChannelName = name + " (server)";

android::base::unique_fd serverFd(sockets[0]);

// server端InputChannel保存了server服务端socket的fd,注意创建InputChannel时是会传入token唯一标识的

outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);

std::string clientChannelName = name + " (client)";

android::base::unique_fd clientFd(sockets[1]);

// client端InputChannel保存了client客户端socket的fd,注意创建InputChannel时是会传入token唯一标识的

outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);

return OK;

}

先调用linux的标准接口socketpair创建了全双工的通信信道(这里socketpair的创建与访问其实是还是借助文件描述符),设置好传输buffer的大小,然后创建了客户端的InputChannel并持有client客户端的socket的fd,创建了服务端的InputChannel并持有server服务端的socket的fd。最终客户端和服务端的InputChannel都转换成java对象返回到WindowState中。

5.2.2 注册服务端InputChannel到InputDispatcher

我们回到WindowState::openInputChannel继续往下看registerInputChannel的逻辑:

/*frameworks/base/services/core/java/com/android/server/input/InputManagerService.java*/

public void registerInputChannel(InputChannel inputChannel) {

...

// 根据方法名我们知道,这是个典型的JNI调用实现,会调用native层的对应接口实现功能

nativeRegisterInputChannel(mPtr, inputChannel);

}

/*frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp*/

static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,

jlong ptr, jobject inputChannelObj) {

NativeInputManager* im = reinterpret_cast(ptr);

// 1.获取native层的InputChannel

sp inputChannel = android_view_InputChannel_getInputChannel(env,

inputChannelObj);

...

// 2.调用InputManager.cpp的registerInputChannel接口

status_t status = im->registerInputChannel(env, inputChannel);

...

}

status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,

const sp& inputChannel) {

ATRACE_CALL();

// 调用InputDispatcher.cpp的registerInputChannel实现注册逻辑

return mInputManager->getDispatcher()->registerInputChannel(inputChannel);

}

/*frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp*/

status_t InputDispatcher::registerInputChannel(const sp& inputChannel) {

...

{ // acquire lock

std::scoped_lock _l(mLock);

// 如果已经存在Connection,则不必重复注册

sp existingConnection = getConnectionLocked(inputChannel->getConnectionToken());

if (existingConnection != nullptr) {

...

return BAD_VALUE;

}

// 1.封装创建Connection连接,将服务端inputChannel传进去

sp connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator);

// 读取server服务端inputChannel的socket的fd

int fd = inputChannel->getFd();

// 2.以socket的fd为key,connection为value,保存到map mConnectionsByFd中

mConnectionsByFd[fd] = connection;

// 3.以token为key,inputChannel为value,保存在map mInputChannelsByToken中

mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;

// 4.将服务端的socket的fd添加到looper监听,待客户端发送事件后会触发回调handleReceiveCallback函数

mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);

} // release lock

// Wake the looper because some connections have changed.

mLooper->wake();

return OK;

}

这块核心的逻辑其实就是:

1.判断如果是首次注册InputChannel,则会封装创建一个Connection连接,并以InputChannel的socket的fd为key,此连接为value,存储到InputDispatcher的mConnectionsByFd中,InputChannel的token对象则保证了注册InputChannel的窗口的唯一性;token为key,InputChannel为value保存在了InputDispatcher的mInputChannelsByToken中。

2.将server端socket的fd添加到InputDispatcher内部的looper进行监听,待应用APP那边的client客户端socket写入了数据时触发回调其handleReceiveCallback函数进行处理。

(重要)让我们回忆结合5.1节中的分析触控事件发送到目标窗口的逻辑,在InputDispatcher::dispatchEventLocked函数中第一步就是需要遍历目标窗口,然后根据找到的目标窗口的token就能直接取出对应的Connection连接,其实就是因为这些目标窗口在创建时就已经完成了上述注册过程,并已经添加相关信息到mConnectionsByFd和mInputChannelsByToken集合中了,所以在事件分发时就能根据唯一的toke身份标识从集合中直接取出,至此完成前后逻辑呼应。

5.2.3 App客户端监听触控事件

我们回到WindowState::openInputChannel中继续往下看,服务端InputChannel注册完成之后,mClientChannel.transferTo(outInputChannel)会将客户端InputChannel赋值给outInputChannel,并Binder回传给App应用进程的ViewRootImpl中。这之后App应用进程需要根据换船过来的客户端的InputChannel完成注册监听触控事件的逻辑,以便后续框架InputDispatcher的触控事件传递过来之后,应用能及时响应。我们先用一张流程图看一下这个过程:

下面回到应用客户端ViewRootImpl的setView中结合代码分析一下这个过程:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,

int userId) {

synchronized (this) {

if (mView == null) {

...

InputChannel inputChannel = null;

if ((mWindowAttributes.inputFeatures

& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

// 1.创建一个空的InputChannel

inputChannel = new InputChannel();

}

try {

...

// 2.Binder调用WMS的addToDisplayAsUser接口,并将InputChanel作为参数传入,以完成赋值

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

mTempInsets, mTempControls);

} catch (RemoteException e) {

...

}

...

if (inputChannel != null) {

...

// 3.根据赋值后的InputChannel封装创建WindowInputEventReceiver对象,以便后续接收input事件

mInputEventReceiver = new WindowInputEventReceiver(inputChannel,

Looper.myLooper());

}

...

}

}

}

通过WMS的addToDisplayAsUser给空的客户端InputChannel的赋值后,创建一个WindowInputEventReceiver对象,其构造方法接收客户端的InputChannel和应用UI主线程的Looper对象。

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/

final class WindowInputEventReceiver extends InputEventReceiver {

public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {

// 调用父类InputEventReceiver的构造函数

super(inputChannel, looper);

}

}

/*frameworks/base/core/java/android/view/InputEventReceiver.java*/

public InputEventReceiver(InputChannel inputChannel, Looper looper) {

...

// 通过JNI调用nativeInit完成初始化

mReceiverPtr = nativeInit(new WeakReference(this),

inputChannel, mMessageQueue);

...

}

/*frameworks/base/core/jni/android_view_InputEventReceiver.cpp*/

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,

jobject inputChannelObj, jobject messageQueueObj) {

// 1.java层InputChannel转换得到native层InputChannel,client端

sp inputChannel = android_view_InputChannel_getInputChannel(env,

inputChannelObj);

...

// 2.APP UI线程对应native层的MessageQueue

sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

...

// 3.创建NativeInputEventReceiver,将inputChannel,messageQueue保存到其内部成员变量

sp receiver = new NativeInputEventReceiver(env,

receiverWeak, inputChannel, messageQueue);

status_t status = receiver->initialize();

...

}

status_t NativeInputEventReceiver::initialize() {

setFdEvents(ALOOPER_EVENT_INPUT);

return OK;

}

void NativeInputEventReceiver::setFdEvents(int events) {

if (mFdEvents != events) {

mFdEvents = events;

// 1.mInputConsumer就是保存到其内部的client端InputChannel,fd指向client端InputChannel内部的client端socket的fd

int fd = mInputConsumer.getChannel()->getFd();

if (events) {

// 2.将client端socket的fd添加到Looper进行监听,监听事件类型为ALOOPER_EVENT_INPUT

mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);

} else {

mMessageQueue->getLooper()->removeFd(fd);

}

}

}

这块核心的逻辑其实就是:取出client端InputChannel的socket的fd,添加到APP进程的UI线程的Looper进行监听,且监听事件类型为ALOOPER_EVENT_INPUT,并在接收到事件之后(server端socket有写入数据时,也就是前面分析的InputChannel::sendMessage中InputDispatcher向目标窗口发送触控事件的send动作)在UI线程回调NativeInputEventReceiver::handleEvent函数进行处理。最后来看看NativeInputEventReceiver::handleEvent中的处理:

/*frameworks/base/core/jni/android_view_InputEventReceiver.cpp*/

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {

...

if (events & ALOOPER_EVENT_INPUT) {

JNIEnv* env = AndroidRuntime::getJNIEnv();

// 判断ALOOPER_EVENT_INPUT事件后调用consumeEvents消费事件

status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);

mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");

return status == OK || status == NO_MEMORY ? 1 : 0;

}

...

return 1;

}

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,

bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {

...

for (;;) {

...

InputEvent* inputEvent;

// 1.读取获取input事件

status_t status = mInputConsumer.consume(&mInputEventFactory,

consumeBatches, frameTime, &seq, &inputEvent,

&motionEventType, &touchMoveNum, &flag);

...

if (!skipCallbacks) {

...

if (inputEventObj) {

if (kDebugDispatchCycle) {

ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());

}

// 2.JNI回调java层的InputDispatcherReceiver中的dispatchInputEvent函数

env->CallVoidMethod(receiverObj.get(),

gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);

...

} else {

...

}

}

...

}

}

/*frameworks/base/core/java/android/view/InputEventReceiver.java*/

// Called from native code.

@SuppressWarnings("unused")

@UnsupportedAppUsage

private void dispatchInputEvent(int seq, InputEvent event) {

mSeqMap.put(event.getSequenceNumber(), seq);

// 调用onInputEvent具体处理input event触控事件

onInputEvent(event);

}

对于APP UI线程,在接收到server端socket的消息时会回调InputEventReceiver对应的native层对象NativeInputEventReceiver的handleEvent函数。然后会JNI通知回调到应用App的java层的ViewRootImpl::WindowInputDispatcherReceiver::onInputEvent函数进一步处理,之后的逻辑就是一下节中需要具体分析的App应用目标窗口内部的事件传递了。

6 目标窗口内部的事件传递机制

到了这里就进入了App应用开发者最熟悉也是最关心的部分了,input触控事件已经进入了App应用进程这边且进入了Java层的世界了。在本节中我们将接上一节的分析,从InputEventReceiver::onInputEvent函数入手,分两个小节分析Input触控事件在应用目标窗口内的传递流程。

6.1 UI线程对触控事件的分发处理

从ViewRootImpl::WindowInputDispatcherReceiver::onInputEvent函数开始,UI线程对触控事件的处理流程如下图所示:

从上面的流程图中我们可以看到:Input事件到来后先通过enqueueInputEvent函数放入“aq”本地待处理队列中,简化代码如下:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/

@UnsupportedAppUsage

void enqueueInputEvent(InputEvent event,

InputEventReceiver receiver, int flags, boolean processImmediately) {

// 构造一个QueuedInputEvent对象,使用obtainQueuedInputEvent构造对象,通过对象池的形式保存回收对象,内部使用链表数据结构

QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

QueuedInputEvent last = mPendingInputEventTail;

if (last == null) {

mPendingInputEventHead = q;

mPendingInputEventTail = q;

} else {

last.mNext = q;

mPendingInputEventTail = q;

}

mPendingInputEventCount += 1;

// QueuedInputEvent数量加1,更新相关trace Tag的打印,从systrace上看tag信息为“aq:pending:XXX”,最后一节中详细分析

Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,

mPendingInputEventCount);

if (processImmediately) {

// doProcessInputEvents具体处理此次input事件

doProcessInputEvents();

} else {

scheduleProcessInputEvents();

}

}

具体对input触控事件的处理逻辑都封装在InputUsage中,在ViewRootImpl创建Window时的setView逻辑中会创建了多个不同类型的InputUsage对象依次进行事件处理,设计上采用责任链模式,将每个InputStage实现类通过mNext变量连接起来。每个InputUsage主要逻辑是在OnProcess函数中具体处理触控事件,然后判断处理是否完成,没有则onDeliverToNext交给下一个InputUsage继续处理,否则就调用finishInputEvent结束事件。代码流程如下:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,

int userId) {

...

// Set up the input pipeline.

CharSequence counterSuffix = attrs.getTitle();

// 创建多个不同类型InputUsage以处理不同类型的触控事件

mSyntheticInputStage = new SyntheticInputStage();

// ViewPostImeInputStage中封装我们应用一般触控事件处理逻辑(重要)

InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);

InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,

"aq:native-post-ime:" + counterSuffix);

...

}

abstract class InputStage {

...

/**

* Delivers an event to be processed.

*/

public final void deliver(QueuedInputEvent q) {

// 是否带有FLAG_FINISHED的flag,如有则将事件分发给mNext

if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {

forward(q);

// 是否应该丢弃事件

} else if (shouldDropInputEvent(q)) {

finish(q, false);

} else {

traceEvent(q, Trace.TRACE_TAG_VIEW);

final int result;

try {

// 1.实际处理事件

result = onProcess(q);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

// 2.拿到处理事件的结果后决定进一步行动

apply(q, result);

}

}

protected void apply(QueuedInputEvent q, int result) {

if (result == FORWARD) {

forward(q);

} else if (result == FINISH_HANDLED) {

finish(q, true);

} else if (result == FINISH_NOT_HANDLED) {

finish(q, false);

}

}

protected void finish(QueuedInputEvent q, boolean handled) {

q.mFlags |= QueuedInputEvent.FLAG_FINISHED;

if (handled) {

q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;

}

// 交给下一个InputUsage继续处理

forward(q);

}

protected void forward(QueuedInputEvent q) {

// 交给下一个InputUsage继续处理

onDeliverToNext(q);

}

protected void onDeliverToNext(QueuedInputEvent q) {

if (DEBUG_INPUT_STAGES) {

Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);

}

if (mNext != null) {

// 1.交给下一个InputUsage继续处理

mNext.deliver(q);

} else {

// 2.判断所有的InputUsage都处理完成则调用finishInputEvent结束触控事件

finishInputEvent(q);

}

}

}

注意最后触控事件处理完成后会调用finishInputEvent结束应用对触控事件处理逻辑,这里面会通过JNI调用到native层InputConsumer的sendFinishedSignal函数,最终还是通过client端InputChannel的sendMessage通知InputDispatcher事件已经处理完成,而作为server端的InputDispatcher这边的主要就是在收到消息后将此触控事件从waitQueue等待队列中移除并重置ANR时间(所以我们开发应用过程中,如果UI线程存在长时间的耗时或阻塞的操作,导致主线程不能及时处理完触控事件逻辑而向框架InputDispatcher回传finishInputEvent的消息,最终出现waitQueue中计时超时而出现ANR问题)。

6.2 View的触控事件分发机制

接着上一节的分析中我们知道:触控事件首先分发到View树的根节点DecorView的dispatchTouchEvent函数中,这也是应用窗口界面布局View树的触控事件处理的入口位置,这一节也是App应用开发者最熟悉和关心的部分,我们看看它的具体代码实现:

/*frameworks/base/core/java/com/android/internal/policy/DecorView.java*/

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

final Window.Callback cb = mWindow.getCallback();

// 交给应用Activity的dispatchTouchEvent处理触控事件

return cb != null && !mWindow.isDestroyed() && mFeatureId < 0

? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);

}

其中Window.Callback指向当前Activity,在Activity启动时会调用自己的attach方法,此方法中会将自己作为callback传给window,继续看Activity的dispatchTouchEvent处理逻辑:

/*frameworks/base/core/java/android/app/Activity.java*/

public boolean dispatchTouchEvent(MotionEvent ev) {

...

// 先交给窗口PhoneWindow处理

if (getWindow().superDispatchTouchEvent(ev)) {

return true;

}

// PhoneWindow没有消费事件则继续交给Activity的onTouchEvent继续处理

return onTouchEvent(ev);

}

Activity中会先将事件交给PhoneWindow处理,实际上就是交给DecorView中处理,逻辑如下:

/*frameworks/base/core/java/com/android/internal/policy/DecorView.java*/

public boolean superDispatchTouchEvent(MotionEvent event) {

// 具体交给其父类ViewGroup处理

return super.dispatchTouchEvent(event);

}

继续看ViewGroup中的处理:

/*frameworks/base/core/java/android/ViewGroup.java*/

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

...

if (onFilterTouchEventForSecurity(ev)) {

...

if (actionMasked == MotionEvent.ACTION_DOWN

|| mFirstTouchTarget != null) {

// 1.判断子View中是否设置了禁止父ViewGroup拦截触控事件,解决滑动冲突的关键

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

if (!disallowIntercept) {

// onInterceptTouchEvent中判断ViewGroup是否拦截触控事件

intercepted = onInterceptTouchEvent(ev);

}

}

...

if (!canceled && !intercepted) {

if (actionMasked == MotionEvent.ACTION_DOWN

|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

...

if (newTouchTarget == null && childrenCount != 0) {

...

final View[] children = mChildren;

// 2.对所有子View进行遍历

for (int i = childrenCount - 1; i >= 0; i--) {

...

if (!child.canReceivePointerEvents()

|| !isTransformedTouchPointInView(x, y, child, null)) {

// 3.如果此View无法接收事件或者当前事件的或落点不在这个View区域内则返回进行下一轮循环

continue;

}

...

// 4.这里就会执行子View事件分发处理逻辑了

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

...

break;

}

...

}

}

...

}

}

...

}

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (ev.isFromSource(InputDevice.SOURCE_MOUSE)

&& ev.getAction() == MotionEvent.ACTION_DOWN

&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)

&& isOnScrollbarThumb(ev.getX(), ev.getY())) {

return true;

}

// ViewGroup默认不拦截事件

return false;

}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

View child, int desiredPointerIdBits) {

...

// Perform any necessary transformations and dispatch.

if (child == null) {

handled = super.dispatchTouchEvent(transformedEvent);

} else {

...

// 调用子View的dispatchTouchEvent继续进行事件分发

handled = child.dispatchTouchEvent(transformedEvent);

}

...

}

ViewGroup中的件分发的主要逻辑就是:判断子View中是否设置了禁止父ViewGroup拦截触控事件(解决滑动冲突的一个主要思路就是子View通过调用requestDisallowInterceptTouchEvent禁止父ViewGroup拦截触控事件,从而将事件统一交给子View处理以避免产生滑动冲突),如果没有则onInterceptTouchEvent判断是否拦截此事件;如果不拦截,则遍历所有子View找到匹配的子View,然后交给该子View的dispatchTouchEvent继续处理。

我们继续往下看View中对触控事件的处理逻辑:

/*frameworks/base/core/java/android/View.java*/

public boolean dispatchTouchEvent(MotionEvent event) {

...

boolean result = false;

if (onFilterTouchEventForSecurity(event)) {

...

// 1.判断是否调用过setOnTouchListener方法,如果有则将事件传入其onTouch方法进行处理并返回结果

if (li != null && li.mOnTouchListener != null

&& (mViewFlags & ENABLED_MASK) == ENABLED

&& li.mOnTouchListener.onTouch(this, event)) {

result = true;

}

// 2.onTouchEvent中判断是否消费触控事件

if (!result && onTouchEvent(event)) {

result = true;

}

}

...

return result;

}

首先判断该View是否设置了触摸监听,即是否调用过setOnTouchListener方法,如果有则将事件传入其onTouch方法进行处理并返回结果,如果没有设置,则会调用View的onTouchEvent方法,此方法中会有条件的调用onClick,对于事件处理的顺序就是:onTouch,onTouchEvent,onClick。

总体上View对触控事件的处理逻辑如下图所示:

7 触控问题调试机制

根据笔者多年的经验总结一些常见的系统触控调试机制:

1、查看支持的触控输入设备节点:

2、adb getevent 命令查看屏幕报点情况: adb shell getevent -ltr 可以滑动查看屏幕触控报点是否正常和均匀:

3、显示触控小白点:

设置->开发者选项->显示触摸操作

开启后触摸屏幕能看到小白点实时显示,能够主观感受屏幕滑动跟手度等状况。

4、Systrace上查看触控事件分发的全流程:

从前面的分析可以知道:InputDispatcher进行事件分发是会有一些处理队列,源码都加了一些trace tag的的打印和计数,如InputReader读取到触控事件后唤醒InputDispatcher会放入“iq”队列中,然后进行事件分发时每个目标窗口都有对应的队列“oq”和等待目标窗口事件处理的“wq”队列,最后应用这边收到触控事件后还有对应的“aq”队列,从systrace上看如下图所示: system_server进程的InputDispatcher:

目标窗口应用App进程:

应用UI线程处理的systrace tag:

5、adb shell dumpsys input 通过dump查看触控事件处理系统框架部分:EventHub、InputReader、InputDispatcher的工作状态:

8 总结

最后借用业界大牛做的一张图来描述Android系统触控事件处理机制的整体全貌作为总结。

最后

大家如果还想了解更多Android 相关的更多知识点,可以点进我的GitHub项目中:https://github.com/733gh/GH-Android-Review-master自行查看,里面记录了许多的Android 知识点。最后还请大家点点赞支持下!!!