作者:安卓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>& 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<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& 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<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& 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<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> 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<InputListenerInterface>& 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<InputTarget>& 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<InputWindowHandle> newTouchedWindowHandle =
findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
...
} else {
...
}
...
}
sp<InputWindowHandle> 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<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
// 2.遍历所有可见窗口windowHandles找到目标窗口
for (const sp<InputWindowHandle>& 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<sp<InputWindowHandle>> 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<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
...
// Insert or replace
// 唯一给mWindowHandlesByDisplay集合赋值的地方
mWindowHandlesByDisplay[displayId] = newHandles;
}
void InputDispatcher::setInputWindowsLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
...
updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
...
}
// 外部通过调用setInputWindows接口给mWindowHandlesByDisplay赋值
void InputDispatcher::setInputWindows(
const std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>& 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<InputWindowInfo> 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<InputTarget>& inputTargets) {
ATRACE_CALL();
...
// 遍历需要发送触控事件的目标窗口inputTargets
for (const InputTarget& inputTarget : inputTargets) {
// 获取目标窗口的连接
sp<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
// 执行prepareDispatchCycleLocked完成具体的触控事件发送动作
prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
} else {
...
}
}
}
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& 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>& 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>& 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<uint8_t, 32> 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<InputChannel>& outServerChannel, sp<InputChannel>& 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<IBinder> 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<NativeInputManager*>(ptr);
// 1.获取native层的InputChannel
sp<InputChannel> 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>& 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>& inputChannel) {
...
{ // acquire lock
std::scoped_lock _l(mLock);
// 如果已经存在Connection,则不必重复注册
sp<Connection> existingConnection = getConnectionLocked(inputChannel->getConnectionToken());
if (existingConnection != nullptr) {
...
return BAD_VALUE;
}
// 1.封装创建Connection连接,将服务端inputChannel传进去
sp<Connection> 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<InputEventReceiver>(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> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
...
// 2.APP UI线程对应native层的MessageQueue
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
...
// 3.创建NativeInputEventReceiver,将inputChannel,messageQueue保存到其内部成员变量
sp<NativeInputEventReceiver> 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 知识点。最后还请大家点点赞支持下!!!