美文网首页
Android Input(2)

Android Input(2)

作者: wbo4958 | 来源:发表于2018-07-01 21:44 被阅读58次

    https://www.jianshu.com/p/2bff4ecd86c9 已经大概说了下Android Input的整体流程,但是还有些小细节并没有详细说,而这篇文章主要讲了Input是如何加入输入设备,以及input是如何解析输入设备上报的事件的。

    一、input发现输入设备

    1.1 扫描输入设备

    EventHub::getEvents()在第一次进入的时候,会根据 mNeedToScanDevices 的值去决定是否去扫描设备,而 mNeedToScanDevices 默认是为true的

            if (mNeedToScanDevices) {
                mNeedToScanDevices = false;
                scanDevicesLocked();
                mNeedToSendFinishedDeviceScan = true;
            }
    

    scanDevicesLocked()这个函数完成两件事, 一个是去扫描 /dev/input 下所有的输入设备,另一个是创建一个 VIRTUAL KEYBOARD 设备,顾名思义,虚拟键盘。

    void EventHub::scanDevicesLocked() {
        status_t res = scanDirLocked(DEVICE_PATH);
        if(res < 0) {
            ALOGE("scan dir failed for %s\n", DEVICE_PATH);
        }
        if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
            createVirtualKeyboardLocked();
        }
    }
    

    scanDirLocked函数会通过openDeviceLocked打开 /dev/input 下的每一个输入设备来决定是否将该设备加入到input框架中去。

    如在 pixel 手机中, 有如下的输入设备

    sailfish:/ # ls /dev/input
    event0 event1 event2 event3 event4 event5 event6 mice
    

    openDeviceLocked()函数人主要功能有

    1. 是通过 ioctl 向驱动查询输入设备的相关信息
      如 device name, driver version, device identifier(vendor/version/bus ...) 等, 如果查询失败,则说明驱动有问题,input就不会将该输入设备加入到input框架当中。

    2. 如果驱动没问题,则通过loadConfigurationLocked 去查找并加载configure文件
      如果有configure文件,则通过 PropertyMap::load将configure文件里的内容加载进来

    3. 通过 ioctl 去查询该 输入设备上报什么事件, 如 key/ abs /rel ...等等事件

        ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
        ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
        ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
        ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
        ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
        ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
        ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
    
    1. 根据这些上报的事件类型,设备device的 classes, 如INPUT_DEVICE_CLASS_KEYBOARD, 并加载对应的 key layout map 和 key character map

    2. 最后再通过

    void EventHub::addDeviceLocked(Device* device) {
        mDevices.add(device->id, device);
        device->next = mOpeningDevices;
        mOpeningDevices = device;
    }
    

    加入到 mDevices 和 mOpeningDevices中。

    这里以pixel手机的触摸屏输入设备为例


    pixel手机的触摸屏 pixel手机的gpio键盘
    # cat gpio-keys.kl
    key 116   POWER
    key 115   VOLUME_UP
    key 114   VOLUME_DOWN
    

    1.2 EventHub上报InputReader关于输入设备接入

    EventHub发现了输入设备后,紧接着向InputReader上报关于设备加入了

            while (mOpeningDevices != NULL) {
                Device* device = mOpeningDevices;
                mOpeningDevices = device->next;
                event->when = now;
                event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
                event->type = DEVICE_ADDED;
                event += 1;
                mNeedToSendFinishedDeviceScan = true;
                if (--capacity == 0) {
                    break;
                }
            }
    

    1.1 小节在扫描设备完后将设备加入了 mClosingDevices, 此时Eventhub将mClosingDevices所构成的链表依次加入到 event (DEVICE_ADDED)

    InputReader在收到事件后

                case EventHubInterface::DEVICE_ADDED:
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
    

    调用 addDeviceLocked 生成 InputDevice 并加入到 InputReader里的 mDevices中

    input device

    二、Input 处理输入事件

    InputReader在收到输入设备的事件后,会调用 processEventsForDeviceLocked 去处理输入事件

    void InputReader::processEventsForDeviceLocked(int32_t deviceId,
            const RawEvent* rawEvents, size_t count) {
        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
        InputDevice* device = mDevices.valueAt(deviceIndex);
        device->process(rawEvents, count);
    }
    

    最终又会调用 InputDevice::process 去处理这些输入事件

    void InputDevice::process(const RawEvent* rawEvents, size_t count) {
        // Process all of the events in order for each mapper.
        // We cannot simply ask each mapper to process them in bulk because mappers may
        // have side-effects that must be interleaved.  For example, joystick movement events and
        // gamepad button presses are handled by different mappers but they should be dispatched
        // in the order received.
        size_t numMappers = mMappers.size();
        for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
            if (mDropUntilNextSync) {
              ...
            } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
              ...
            } else {
                for (size_t i = 0; i < numMappers; i++) {
                    InputMapper* mapper = mMappers[i];
                    mapper->process(rawEvent);
                }
            }
        }
    }
    

    2.1 以power键为例

    当按一次power键后,EventHub就获得了两个事件,如下log所示


    power_key.JPG

    可知处理 power 输入事件的InputDevice只有一个KeyboardMapper

    void KeyboardInputMapper::process(const RawEvent* rawEvent) {
        switch (rawEvent->type) {
        case EV_KEY: {
            int32_t scanCode = rawEvent->code;
            int32_t usageCode = mCurrentHidUsage;
            mCurrentHidUsage = 0;
    
            if (isKeyboardOrGamepadKey(scanCode)) {
                processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
            }
            break;
        }
        case EV_MSC: {
            if (rawEvent->code == MSC_SCAN) {
                mCurrentHidUsage = rawEvent->value;
            }
            break;
        }
        case EV_SYN: {
            if (rawEvent->code == SYN_REPORT) {
                mCurrentHidUsage = 0;
            }
        }
        }
    }
    
    相关的值

    第一个输入事件为 EV_KEY, 其对应的code = 116, value = 1, 表示按下
    第二个输入事件为 EV_SYN, 对应的code = 0


    image.png

    第一个输入事件为 EV_KEY, 其对应的code = 116, value = 0, 表示释放
    第二个输入事件为 EV_SYN, 对应的code = 0

    2.2 触屏事件

    ABS code
    • 单点触摸

    第一个 bunch

    第一个 bunch

    type = 3 对应的是 EV_ABS, 然后它的 code 分别为 0x39(ABS_MT_TRACKING_ID), 0x35(ABS_MT_POSITION_X), 0x36(ABS_MT_POSITION_Y), 0x3a(ABS_MT_PRESSURE)
    type = 0 对应的是 EV_SYN, 其实 EV_SYN 表示一组输入事件结束

    第二个 bunch

    第二个 bunch

    type = 3, code 分别是 0x31(ABS_MT_TOUCH_MINOR), 0x3a(ABS_MT_PRESSURE)

    第三个 bunch

    第三个 bunch
    type = 3, code 分别是 0x3a(ABS_MT_PRESSURE)

    最后 bunch

    最后 bunch
    type = 3, code 分别是 0x3a(ABS_MT_TRACKING_ID)

    从上面的连续的bunch, 可以看出,第一个bunch给出来触摸点的位置以及压力,后面的bunch只有压力的改变。也就是手指点击后并没有移动过,否则会有新的position上报

    现在来看下代码

    void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
        TouchInputMapper::process(rawEvent);
    
        mMultiTouchMotionAccumulator.process(rawEvent);
    }
    
    void TouchInputMapper::process(const RawEvent* rawEvent) {
        mCursorButtonAccumulator.process(rawEvent);
        mCursorScrollAccumulator.process(rawEvent);
        mTouchButtonAccumulator.process(rawEvent);
    
        if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
            sync(rawEvent->when);
        }
    }
    
    void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
        if (rawEvent->type == EV_ABS) {
            ...
            if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
              ...
            } else {
                Slot* slot = &mSlots[mCurrentSlot];
    
                switch (rawEvent->code) {
                case ABS_MT_POSITION_X:
                    slot->mInUse = true;
                    slot->mAbsMTPositionX = rawEvent->value;
                    break;
                case ABS_MT_POSITION_Y:
                    slot->mInUse = true;
                    slot->mAbsMTPositionY = rawEvent->value;
                    break;
                case ABS_MT_TOUCH_MAJOR:
                    slot->mInUse = true;
                    slot->mAbsMTTouchMajor = rawEvent->value;
                    break;
    ...
    }
    

    由代码可知 MultiTouchMotionAccumulator 就是将每个 bunch 的事件放到一个 Slot, 在这个单点触摸的例子中,每一个bunch, InputReader dispatch一次,下一个bunch会基于上一个bunch的值,如本例中第二个bunch,本来没有x, y的值,但是由于这次是继承上一次的x, y, 所以 x, y是上一次的值,改变的是仅仅是 pressure的值,其它值未变。

    最后经过一系列的运算,通过 dispatchMotion 将触屏事件发送出去

    void TouchInputMapper::process(const RawEvent* rawEvent) {
        ...
        //每一个bunch都以EV_SYN结束,然后sync
        if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
            sync(rawEvent->when);
        }    
    }
    
    sync -> syncTouch -> processRawTouches -> cookAndDispatch -> dispatchTouches -> dispatchMotion
    
    • 多点触摸

    第一个bunch

    多点触摸
    可见多点与单点触摸的区别在于,多点触摸一次会上报所有点的触摸事件,每个触摸事件之间由 ABS_MT_SLOT 分开,这样MultiTouchMotionAccumulator::process在处理的时候就会将多点触摸放到多个slot中,这样在 syncTouch时就会将多个slot,放到RawState.RawPointerData.Pointer中。 多点触摸

    相关文章

      网友评论

          本文标题:Android Input(2)

          本文链接:https://www.haomeiwen.com/subject/vcwtuftx.html