Android源码解析之——10、Input

346 阅读6分钟

一、Linux 触摸协议概述

Linux 触摸协议分为 A 协议 和 B 协议,两者的区别如下:

  1. A 协议:

    • 不支持多触点(Multi-Touch)的槽位(Slot)机制。
    • 所有触点信息通过连续的事件上报,每次上报都需要包含所有触点的状态。
    • 事件处理效率较低,尤其是在多触点场景下。
  2. B 协议:

    • 支持多触点的槽位(Slot)机制,每个槽位可以独立存储一个触点的信息。
    • 通过 ABS_MT_SLOT 切换槽位,只需更新当前槽位的触点信息,避免重复上报。
    • 事件处理效率更高,适合多触点场景。

使用A协议还是B协议,是根据触摸屏硬件所决定的。现在的智能手机,一般都是采用B协议。

常用事件类型:

  • EV_SYN:同步事件,表示一组事件的结束。
  • EV_REL:相对坐标事件(如鼠标移动)。
  • EV_ABS:绝对坐标事件(如触摸屏)。
  • EV_KEY:按键事件。
  • SYN_REPORT:同步报告,标志一组事件的完成。
  • ABS_MT_SLOT:多触点槽位切换。
  • ABS_MT_TRACKING_ID:标识某个触点。
  • BTN_TOUCH:触摸按键事件。
事件上报流程:
  1. 切换 ABS_MT_SLOT,选择当前操作的槽位。
  2. 设置 ABS_MT_TRACKING_ID,表示该槽位保存了某个手指的触摸信息。
  3. 如果不切换槽位,后续事件都会在当前槽位中处理。
  4. 如果需要处理其他触点,切换到新的槽位,避免重复上报。
  5. 最后通过 SYN_REPORT 上报一组完整的事件。

注意:Android 默认最多支持 16 个槽位。


二、Android InputManager

InputManager是Android系统的输入管理系统,运行在SystemSyster进程中。

InputManagerSystemServer.startOtherService 中创建,并添加到 ServiceManager 中。

并且在InputManager创建时,会通过 JNI,同时创建InputManager(C++)对象,并使用 inputflinger 注册到 ServiceManager 中。

整个InputManager核心的组件包括:

  • InputDispatcher:负责事件分发。
  • InputClassifier:负责事件分类。
  • InputReader:负责读取输入事件。
  • EventHub:负责监听输入设备的添加,和采集输入事件。
2. EventHub 的事件读取
  • EventHub::getEvents 通过 epoll_wait 等待触摸事件的到来。
  • scanDevicesLocked 扫描 /dev/input 目录下的设备,并通过 openDeviceLocked 打开设备节点。
  • 获取设备属性后,创建一个 EventHub::Device 对象。如果是键盘设备,会加载额外的配置文件。
  • 设备节点会被注册到 epoll 中。
3. InputReader 的工作机制
  • InputReader 创建时,会初始化一个 EventHub,内部使用 inotify 和 epoll 监听输入设备的设备节点。
  • 然后创建和启动2个线程:InputReadThreadInputDispatchThread
5. 设备事件处理
  • 根据设备类型,为其添加相应的 Mapper,例如 MultiTouchInputMapper,用于将原始触摸数据转换为 input_event
  • device->process 方法处理原始事件 (RawEvent),内部调用 mapper->process 进行进一步处理。
6. MultiTouchInputMapper 的处理流程
  • 在 sync 阶段,MultiTouchInputMapper 会将所有槽位的数据转换为 RowPointerData
  • 通过 processRawTouch 和 cookPointerData 对数据进行矫正。
  • 最后调用 dispatchTouches 分发触摸事件。
7. 事件分发
  • getListener()->notifyMotion 将数据存储到 mArgsQueue 中,直到调用 flush 方法。
  • 最终,事件会传递到 InputDispatcher::notifyMotion 进行进一步处理。

InputDispatcher 事件分发流程

1. InputDispatcher 的核心职责
  • 接收来自 InputReader 的输入事件。
  • 根据事件类型和目标窗口,将事件分发给对应的 Activity 或 Window
2. 事件分发的主要步骤
  1. 事件接收

    • InputDispatcher 通过 notifyMotion 方法接收来自 InputReader 的触摸事件。
    • 事件被封装为 MotionEntry,并放入待处理队列中。
  2. 事件分类

    • 根据事件的坐标和当前窗口的布局,确定事件的目标窗口。
    • 通过 findTouchedWindow 方法找到最上层的、可接收触摸事件的窗口。
  3. 事件分发

    • 将事件封装为 InputMessage,并通过 InputChannel 发送给目标窗口。
    • 目标窗口的 ViewRootImpl 通过 InputEventReceiver 接收事件。
  4. 事件处理

    • 目标窗口的 ViewRootImpl 将事件传递给 DecorView,再由 DecorView 分发给具体的 View
    • View 通过 onTouchEvent 方法处理触摸事件。
  5. 事件反馈

    • 目标窗口处理完事件后,会通过 InputChannel 向 InputDispatcher 发送反馈。
    • InputDispatcher 根据反馈决定是否需要继续分发后续事件。
3. 事件分发的优化
  • 批处理:多个连续的事件可能会被合并为一个批次,减少跨进程通信的开销。
  • 焦点管理InputDispatcher 会跟踪当前焦点窗口,确保按键事件分发给正确的窗口。
  • 超时机制:如果目标窗口未及时处理事件,InputDispatcher 会触发超时机制,避免事件堆积。
    private void startOtherServices() {
            inputManager = new InputManagerService(context);

            Slog.i(TAG, "Window Manager");
            wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
            ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

            mActivityManagerService.setWindowManager(wm);

            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
            inputManager.start();
     }
public class InputManagerService extends IInputManager.Stub
        implements Watchdog.Monitor {
        
    public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

        LocalServices.addService(InputManagerInternal.class, new LocalService())
    }
    
    public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(mPtr);
        ......
    }
static jlong nativeInit(JNIEnv* env, jclass clazz,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    ......
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}
InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

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

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();

        // Run a dispatch loop if there are no pending commands.
        // The dispatch loop might enqueue commands to run afterwards.
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.
        // If any commands were run then force the next poll to wake up immediately.
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}
void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    ......

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

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
            processEventsLocked(mEventBuffer, count);
        }

        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }

        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // Flush queued events out to the listener.
    // This must happen outside of the lock because the listener could potentially call
    // back into the InputReader's methods, such as getScanCodeState, or become blocked
    // on another thread similarly waiting to acquire the InputReader lock thereby
    // resulting in a deadlock.  This situation is actually quite plausible because the
    // listener is actually the input dispatcher, which calls into the window manager,
    // which occasionally calls into the input reader.
    mQueuedListener->flush();
}