Android Input子系统 - 触控事件处理机制

本文深入探讨Android系统触控事件处理机制,包括从设备读取触控事件、事件分发、寻找目标窗口、事件发送以及目标窗口内部的事件处理流程。通过分析Android 11源码,揭示了InputReader、InputDispatcher、InputChannel在触控事件处理中的角色,以及跨进程通信机制。此外,还介绍了触控问题的调试方法。

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

目录

1 概述

2 触控事件的读取

3 触控事件的分发

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

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

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

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

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

5.2.2 注册服务端InputChannel到InputDispatcher

5.2.3 App客户端监听触控事件

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

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

6.2 View的触控事件分发机制

7 触控问题调试机制

8 总结


1 概述

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

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

下面将结合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事件。详细流程如下图所示:

InputReader.png

简要代码流程如下:

  /*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也拥有自己的独立的工作线程。这样设计的好处在于符合单一职责的设计思想,且事件的读取与分发工作在各自的线程中,处理效率更高,避免出现事件丢失。整个架构模型参见业界大神的一张图:

InputManager.png

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

InputDispatcher.png

简化的代码流程如下:

/*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;
}

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

  1. 首先通过findTouchedWindowTargetsLocked找到处理此次触控事件的目标应用窗口;
  2. 下来通过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,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值