Android U Input系统:InputReader 处理触摸事件

1,891 阅读13分钟

slot 协议

对于触摸屏的 motion event,驱动可以使用 A 或 B 协议来上报事件数据。其中,B 协议,也称之为 slot 协议,也是最常用的协议。因此,本文使用 slot 协议来分析 motion event 的分发。

我们可以先通过执行命令 adb shell getevent -l ,来观察 slot 协议上报的数据。

当手指按下时,上报的数据如下

EV_ABS       ABS_MT_SLOT          00000000
EV_ABS       ABS_MT_TRACKING_ID   00000000            
EV_ABS       ABS_MT_POSITION_X    000002ea            
EV_ABS       ABS_MT_POSITION_Y    00000534

EV_SYN       SYN_REPORT           00000000 

每一行数据,由 type、code、value 组成。如果 type 为 EV_ABS,那这一行是 motion event 的某个数据。如果 type 为 EV_SYN,它代表一个同步事件,并非 motion event 数据,表示系统要同步前面上报的 motion event 数据。

motion event 数据中,code 值的意思如下

  1. ABS_MT_SLOT : 表示驱动使用哪个 slot 上报 motion event 数据。
  2. ABS_MT_TRACKING_ID : 代表手指 ID。
  3. ABS_MT_POSITION_X/ABS_MT_POSITION_Y : 代表 motion event 的 x/y 坐标。

当手指移动时,上报的数据如下

EV_ABS       ABS_MT_POSITION_X    000002ec            
EV_ABS       ABS_MT_POSITION_Y    00000526    

EV_SYN       SYN_REPORT           00000000 

移动事件的数据中,并没有看到 ABS_MT_SLOT 和 ABS_MT_TRACKING_ID 数据。当系统同步数据时,会沿用上一次事件的 ABS_MT_SLOT 和 ABS_MT_TRACKING_ID 数据。

当手指抬起时,驱动上报的数据如下

EV_ABS       ABS_MT_TRACKING_ID   ffffffff     

EV_SYN       SYN_REPORT           00000000    

抬起事件的数据中,也没有看到 ABS_MT_SLOT 数据,同样沿用上一次事件的数据。但是注意, ABS_MT_TRACKING_ID 的值为 ffffffff,十进制值是 -1,它是一个无效值,代表手指抬起。

InputReader 处理 motion event

Android U Input系统:InputReader 加工按键事件 分析了 InputReader 处理按键事件的流程,它会把事件交给输入设备 InputDevice 的 InputMapper 处理。对于 motion event 来说,同样适配这套处理流程。

当前,大部分触摸设备都支持多点触摸,因此,motion event 会交给 MultiTouchInputMapper 处理,如下

std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
    // 父类 TouchInputMapper 处理同步事件
    std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);

    // 使用累加器收集 motion event 数据
    mMultiTouchMotionAccumulator.process(rawEvent);
    return out;
}

MultiTouchInputMapper 先使用自己的累加器来收集 motion event 数据,如下

void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
    if (rawEvent->type == EV_ABS) {
        bool newSlot = false;
        if (mUsingSlotsProtocol) {
            // 获取 slot 数据
            // 注意,如果没有 slot 数据,那么就直接使用上一次数据的 slot 值,即 mCurrentSlot
            if (rawEvent->code == ABS_MT_SLOT) {
                mCurrentSlot = rawEvent->value;
                newSlot = true;
            }
        } else if (mCurrentSlot < 0) {
            // ...
        }

        if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
            // ...
        } else {
            // slot 值作为 index,从数组中获取 Slot 元素
            Slot& slot = mSlots[mCurrentSlot];
            
            // ...

            switch (rawEvent->code) {
                case ABS_MT_POSITION_X:
                    slot.mAbsMtPositionX = rawEvent->value;
                    warnIfNotInUse(*rawEvent, slot);
                    break;
                case ABS_MT_POSITION_Y:
                    slot.mAbsMtPositionY = rawEvent->value;
                    warnIfNotInUse(*rawEvent, slot);
                    break;
                
                // ...
                
                case ABS_MT_TRACKING_ID:
                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
                        // 手指 ID 大于 0,表示有效,因此标记 Slot 正在使用中
                        slot.mInUse = false;
                    } else {
                        // ...
                    }
                    break;
                
                // ...
            }
        }
    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
        // ...
    }
}

MultiTouchMotionAccumulator 把 motion event 数据,以 slot 值为索引,保存到数组 mSlots 对应的元素中。

MultiTouchInputMapper 利用累加器收集了 motion event 数据后,调用父类 TouchInputMapper 的方法来处理同步事件(type 为 EV_SYN,code 为 SYN_REPORT),如下

std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
    
    // ...

    std::list<NotifyArgs> out;

    // 处理同步事件
    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        out += sync(rawEvent->when, rawEvent->readTime);
    }
    return out;
}

std::list<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
    std::list<NotifyArgs> out;
    
    // ...
    
    // mRawStatesPending 末尾插入一个元素
    // Push a new state.
    mRawStatesPending.emplace_back();

    // 取出 mRawStatesPending 末尾元素
    RawState& next = mRawStatesPending.back();
    next.clear();
    next.when = when;
    next.readTime = readTime;

    // ...


    // 把子类收集的 motion event 数据,保存到 mRawStatesPending 末尾元素中
    // Sync touch
    syncTouch(when, &next);

    // The last RawState is the actually second to last, since we just added a new state
    const RawState& last =
            mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];

    // ...
    
    // 如果同步后的数据中没有 pointer id,那么沿用上一次事件的 pointer id
    // Assign pointer ids.
    if (!mHavePointerIds) {
        assignPointerIds(last, next);
    }

    ALOGD_IF(debugRawEvents(),
             "syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
             "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
             last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
             last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
             last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value,
             next.rawPointerData.canceledIdBits.value);

    // ...
    
    // 处理 mRawStatesPending 中同步过来的 motion event
    out += processRawTouches(/*timeout=*/false);
    return out;
}

TouchInputMapper 处理同步事件时,把实现类 MultitouchInputMapper 的累加器收集的 motion event 数据,同步到队列 mRawStatesPending 最后一个元素中,然后处理 mRawStatesPending。

MultiTouchInputMapper 同步数据

void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
    size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
    size_t outCount = 0;
    BitSet32 newPointerIdBits;

    // 注意,这里强制把 mHavePointerIds 设置为 true,因此当前事件本来就是 motion event,一定会有 pointer id
    mHavePointerIds = true;

    for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
        const MultiTouchMotionAccumulator::Slot& inSlot =
                mMultiTouchMotionAccumulator.getSlot(inIndex);

        // 填充 Slot 数据时,如果有 ABS_MT_TRACKING_ID 值(必须大于0),那么 Slot 就有效,否则无效
        if (!inSlot.isInUse()) {
            continue;
        }

        // ...

        RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
        outPointer.x = inSlot.getX();
        outPointer.y = inSlot.getY();
        
        // ...

        // 下面一段代码,主要是根据 tracking id,填充 pointer id 数据
        // Assign pointer id using tracking id if available.
        if (mHavePointerIds) {
            int32_t trackingId = inSlot.getTrackingId();
            int32_t id = -1;

            // 这一段代码,是为了获取 pointer id
            if (trackingId >= 0) { // 上报的数据中有 tracking id

                // 遍历 mPointerIdBits 保存的所有 pointer id,如果能从 mPointerTrackingIdMap 中
                // 根据 pointer id 匹配到 tracking id,那么就使用这个 pointer id 作为此次同步数据的 pointer id
                for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) {
                    uint32_t n = idBits.clearFirstMarkedBit();
                    if (mPointerTrackingIdMap[n] == trackingId) {
                        id = n;
                    }
                }

                // 没有从 mPointerTrackingIdMap 匹配到 pointer id,那么就生成一个 pointer id
                if (id < 0 && !mPointerIdBits.isFull()) {
                    id = mPointerIdBits.markFirstUnmarkedBit();
                    mPointerTrackingIdMap[id] = trackingId;
                }
            }

            if (id < 0) { // 没有获取到 pointer id,其实代表没有上报 tracking id
                mHavePointerIds = false;
                outState->rawPointerData.clearIdBits();
                newPointerIdBits.clear();
            } else { // 成功获取到 pointer id
                // 保存 pointer id
                outPointer.id = id;
                //  id -> index,index outState->rawPointerData.pointers 数组的索引,它保存了 motion event 的数据
                outState->rawPointerData.idToIndex[id] = outCount;
                outState->rawPointerData.markIdBit(id, isHovering);
                
                // newPointerIdBits 保存手指 id
                newPointerIdBits.markBit(id);
            }
        }
        outCount += 1;
    }
    
    // 保存手指数量
    outState->rawPointerData.pointerCount = outCount;
    
    // MultiTouchInputMapper::mPointerIdBits 保存所有 pointer id
    mPointerIdBits = newPointerIdBits;

    // 如果使用 slot 协议,实现为空
    mMultiTouchMotionAccumulator.finishSync();
}

所谓的同步数据,其实就是把 MultitouchInputMapper 累加器的 mSlots 数组中所有数据,全部同步到 RawState 中,当然还会额外生成并保存一些数据,例如,pointer id。

跟着注释,看下 RawState 如何保存数据的,如下

// TouchInputMapper.cpp

struct RawState {
    // ...

    // 同步的数据,主要保存这个结构体中
    RawPointerData rawPointerData{};

    // ...
};


/* Raw data for a collection of pointers including a pointer id mapping table. */
struct RawPointerData {
    // 保存一个手指的 motion event 数据
    struct Pointer {
        // 手指 id
        uint32_t id{0xFFFFFFFF};
        // x,y 坐标
        int32_t x{};
        int32_t y{};
        
        // ...
    };

    // 所有手指数量
    uint32_t pointerCount{};
    
    // 这个数组保存所有手指的 motion event 数据
    std::array<Pointer, MAX_POINTERS> pointers{};
    
    // ...
    
    // pointer id -> index
    // index 是 pointers 数组的索引
    IdToIndexArray idToIndex{};

    // ...
};

TouchInputMapper 处理同步后的数据

现在,TouchInputMapper::mRawStatesPending 保存了同步数据,现在来处理它

std::list<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
    std::list<NotifyArgs> out;
    if (mDeviceMode == DeviceMode::DISABLED) {
        // Do not process raw event while the device is disabled.
        return out;
    }

    // Drain any pending touch states. The invariant here is that the mCurrentRawState is always
    // valid and must go through the full cook and dispatch cycle. This ensures that anything
    // touching the current state will only observe the events that have been dispatched to the
    // rest of the pipeline.
    const size_t N = mRawStatesPending.size();
    size_t count;
    for (count = 0; count < N; count++) {
        const RawState& next = mRawStatesPending[count];

        // 正常来说,mRawStatesPending 只会用最后一个元素来保存同步的数据,因此只需要处理最后一个元素即可
        // 但是对于 stylus 设备来说,有时候不能立即处理 mRawStatesPending 中的最后一个元素
        // 因此,在下一次同步后,mRawStatesPending 可能出现两个元素或者更多
        // 因此,需要用一个 vector 来保存所有同步的数据
        // A failure to assign the stylus id means that we're waiting on stylus data
        // and so should defer the rest of the pipeline.
        if (assignExternalStylusId(next, timeout)) {
            break;
        }

        // All ready to go.
        clearStylusDataPendingFlags();

        // 保存当前正在处理的同步数据
        mCurrentRawState = next;

        if (mCurrentRawState.when < mLastRawState.when) {
            mCurrentRawState.when = mLastRawState.when;
            mCurrentRawState.readTime = mLastRawState.readTime;
        }
        
        // 加工和分发
        out += cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);
    }

    // 处理了多少数据,就移除多少个数据
    if (count != 0) {
        mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
    }

    if (mExternalStylusDataPending) {
        // ...
    }
    return out;
}


std::list<NotifyArgs> TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
    std::list<NotifyArgs> out;

    // mCurrentCookedState 保存加工后的事件的数据
    mCurrentCookedState.clear();

    // ...

    // Cook pointer data.  This call populates the mCurrentCookedState.cookedPointerData structure
    // with cooked pointer data that has the same ids and indices as the raw data.
    // The following code can use either the raw or cooked data, as needed.
    // 1. 加工 motion event
    cookPointerData();

    // ...

    if (mDeviceMode == DeviceMode::POINTER) {
        
    } else {
        if (!mCurrentMotionAborted) {
            // ...

            // 2. 分发 motion event
            out += dispatchTouches(when, readTime, policyFlags);

            // ...
        }

        // ...
    }

    // ...

    // Copy current touch to last touch in preparation for the next cycle.
    mLastRawState = mCurrentRawState;
    mLastCookedState = mCurrentCookedState;
    return out;
}

TouchInputMapper 处理同步的数据的过程,分为加工和分发。

加工

void TouchInputMapper::cookPointerData() {
    // mCurrentRawState 是当前正在处理的”同步后的事件”
    uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;

    // 加工后的事件,保存到 mCurrentCookedState
    mCurrentCookedState.cookedPointerData.clear();
    mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
    mCurrentCookedState.cookedPointerData.touchingIdBits =
            mCurrentRawState.rawPointerData.touchingIdBits;

    // ...

    // Walk through the the active pointers and map device coordinates onto
    // display coordinates and adjust for display orientation.
    for (uint32_t i = 0; i < currentPointerCount; i++) {
        const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];

        // ...

        vec2 transformed = {in.x, in.y};
        mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/);
        // 把触摸屏坐标,转换成显示屏坐标
        transformed = mRawToDisplay.transform(transformed);

        // Write output coords.
        PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
        out.clear();
        out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x);
        out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y);

        // ...

        uint32_t id = in.id;
        
        // ...

        // Write output properties.
        PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
        properties.clear();
        properties.id = id;
        properties.toolType = in.toolType;

        // 保存 pointer id -> index
        // index 是 mCurrentRawState.rawPointerData.pointers 数组的索引
        mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
        mCurrentCookedState.cookedPointerData.validIdBits.markBit(id);
    }
}

所谓加工数据,只是对同步数据中的一部分数据进行转换,例如,把触摸屏的坐标,转换为显示屏的坐标。

分发

std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime,
                                                        uint32_t policyFlags) {
    std::list<NotifyArgs> out;
    
    // 当前加工数据的所有手指id
    BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
    // 前一次加工数据的所有手指id
    BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
    
    // ...

    if (currentIdBits == lastIdBits) { // 前后两次手指id,没有改变
        if (!currentIdBits.isEmpty()) { // 当前数据有手指 id
            // 这种情况是 move event,创建一个 AMOTION_EVENT_ACTION_MOVE 事件
            // No pointer id changes so this is a move event.
            // The listener takes care of batching moves so we don't have to deal with that here.
            out.push_back(
                    dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE,
                                   0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                                   mCurrentCookedState.cookedPointerData.pointerProperties,
                                   mCurrentCookedState.cookedPointerData.pointerCoords,
                                   mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits,
                                   -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime,
                                   MotionClassification::NONE));
        }
    } else { // 前后两次数据的手指 id 改变了
    
        // 检测哪些手指抬起,哪些手指按下,哪些手指一动
        // There may be pointers going up and pointers going down and pointers moving
        // all at the same time.
        BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
        BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
        BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
        
        BitSet32 dispatchedIdBits(lastIdBits.value);

        // 检测是否有手指移动,如果有,返回 true
        // 例如,坐标改变,代表有手指移动
        // Update last coordinates of pointers that have moved so that we observe the new
        // pointer positions at the same time as other pointers that have just gone up.
        bool moveNeeded =
                updateMovedPointers(mCurrentCookedState.cookedPointerData.pointerProperties,
                                    mCurrentCookedState.cookedPointerData.pointerCoords,
                                    mCurrentCookedState.cookedPointerData.idToIndex,
                                    mLastCookedState.cookedPointerData.pointerProperties,
                                    mLastCookedState.cookedPointerData.pointerCoords,
                                    mLastCookedState.cookedPointerData.idToIndex, moveIdBits);
        
        // ...
        
        // 有手指抬起,那么为所有手指,都创建一个 AMOTION_EVENT_ACTION_POINTER_UP 事件
        while (!upIdBits.isEmpty()) {
            uint32_t upId = upIdBits.clearFirstMarkedBit();
            bool isCanceled = mCurrentCookedState.cookedPointerData.canceledIdBits.hasBit(upId);
            if (isCanceled) {
                ALOGI("Canceling pointer %d for the palm event was detected.", upId);
            }
            out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
                                         AMOTION_EVENT_ACTION_POINTER_UP, 0,
                                         isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState,
                                         buttonState, 0,
                                         mLastCookedState.cookedPointerData.pointerProperties,
                                         mLastCookedState.cookedPointerData.pointerCoords,
                                         mLastCookedState.cookedPointerData.idToIndex,
                                         dispatchedIdBits, upId, mOrientedXPrecision,
                                         mOrientedYPrecision, mDownTime,
                                         MotionClassification::NONE));
            dispatchedIdBits.clearBit(upId);
            mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
        }

        // 如果有手指移动,那么创建一个 AMOTION_EVENT_ACTION_MOVE 事件
        // Dispatch move events if any of the remaining pointers moved from their old locations.
        // Although applications receive new locations as part of individual pointer up
        // events, they do not generally handle them except when presented in a move event.
        if (moveNeeded && !moveIdBits.isEmpty()) {
            ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
            out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
                                         AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0,
                                         mCurrentCookedState.cookedPointerData.pointerProperties,
                                         mCurrentCookedState.cookedPointerData.pointerCoords,
                                         mCurrentCookedState.cookedPointerData.idToIndex,
                                         dispatchedIdBits, -1, mOrientedXPrecision,
                                         mOrientedYPrecision, mDownTime,
                                         MotionClassification::NONE));
        }

        // 为每一个按下的手指,创建一个 AMOTION_EVENT_ACTION_POINTER_DOWN 事件
        // Dispatch pointer down events using the new pointer locations.
        while (!downIdBits.isEmpty()) {
            uint32_t downId = downIdBits.clearFirstMarkedBit();
            dispatchedIdBits.markBit(downId);

            if (dispatchedIdBits.count() == 1) {
                // First pointer is going down.  Set down time.
                mDownTime = when;
            }

            out.push_back(
                    dispatchMotion(when, readTime, policyFlags, mSource,
                                   AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState,
                                   0, mCurrentCookedState.cookedPointerData.pointerProperties,
                                   mCurrentCookedState.cookedPointerData.pointerCoords,
                                   mCurrentCookedState.cookedPointerData.idToIndex,
                                   dispatchedIdBits, downId, mOrientedXPrecision,
                                   mOrientedYPrecision, mDownTime, MotionClassification::NONE));
        }
    }
    return out;
}

所谓的分发,其实就是对元数据进行解析,生成高级事件,例如 ACTION DOWN , ACTION MOVE, ACTION UP 等等。

那么,如何分辨应该创建哪种高级事件呢?原理是,利用前后两次加工后的数据的所有手指 ID!

如果前后两次的手指 ID 没有改变,那么代表手指发生移动,因此需要生成 move event。

如果前后两次手指 ID 有改变,那么必须检测到底哪些手指按下,哪些手指抬起,哪些手指移动,才能创建对应的高级事件。检测的标准是,前后两次手指ID,以一种恰当的方式做位操作,大家自己品。

不知道大家注意到没有,对于 move event,并没有分别为所有手指,都生成一个事件。而 down event,up event,是分别为每一个手指,单独生成一个事件。

那么,如何获取不同手指的触摸坐标呢?我曾经写过一篇多点触控的文章 多手指触控,其实也不是很难,当收到 ACTION_DOWN 、ACTION_POINTER_DOWN 事件时,获取不同手指 ID。当收到一个 MotionEvent.ACTION_MOVE 事件时,可以通过不同的手指 ID,分别获取对应的手指触摸坐标。

再回头看这里的代码,如果有多个手指移动,只创建了一个 move event,而这个事件包含了所有手指的触摸坐标数据。因此,可以通过不同手指 ID,通过一个 ACTION_MOVE 事件,能获取到所有手指的触摸坐标。

现在来看下创建高级事件的过程,这对于理解 app 开发的多点指触摸功能,非常关键,如下

NotifyMotionArgs TouchInputMapper::dispatchMotion(
        nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
        int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
        int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
        const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
        float yPrecision, nsecs_t downTime, MotionClassification classification) {
        
    // 这两个数组,用于复制所有手指的 motion event 数据
    PointerCoords pointerCoords[MAX_POINTERS];
    PointerProperties pointerProperties[MAX_POINTERS];
    
    uint32_t pointerCount = 0;

    // idBits 保存的是所有手指ID
    while (!idBits.isEmpty()) {
        uint32_t id = idBits.clearFirstMarkedBit();
        uint32_t index = idToIndex[id];
        
        // 复制对应手指的 motion event 数据
        pointerProperties[pointerCount].copyFrom(properties[index]);
        pointerCoords[pointerCount].copyFrom(coords[index]);

        // changedId 大于 0,表示只有 changeId 对应的手指需要生成高级事件,例如,down event,up event
        // changedId 等于 -1,表示需要为 idBits 保存的所有手指,生成高级事件,例如 move event
        // 所以,这里表示,如果只有一个手指需要生成高级事件,那么在 action 的 8 bit 位之前,保存数组 index
        // 这个数组指的是 pointerProperties 和 pointerCoords
        if (changedId >= 0 && id == uint32_t(changedId)) {
            action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
        }

        pointerCount += 1;
    }

    ALOG_ASSERT(pointerCount != 0);

    if (changedId >= 0 && pointerCount == 1) { // 单手指触摸,并且需要生成高级事件
        // Replace initial down and final up action.
        // We can compare the action without masking off the changed pointer index
        // because we know the index is 0.
        if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
            // AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN
            action = AMOTION_EVENT_ACTION_DOWN; 
        } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
            if ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) {
                action = AMOTION_EVENT_ACTION_CANCEL;
            } else {
                // AMOTION_EVENT_ACTION_POINTER_UP 转化为 AMOTION_EVENT_ACTION_UP
                action = AMOTION_EVENT_ACTION_UP;
            }
        } else {
            // Can't happen.
            ALOG_ASSERT(false);
        }
    }

    
    // ...

    // 3 创建 NotifyMotionArgs
    return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                            policyFlags, action, actionButton, flags, metaState, buttonState,
                            classification, edgeFlags, pointerCount, pointerProperties,
                            pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
                            downTime, std::move(frames));
}

如果直接解释这里的代码,你可能会感觉非常晦涩难懂。然而,如果我以 app 开发多点触控功能的知识要点为例,你可能就会轻易理解这里的代码。

在多点触控功能中

  1. 当第一个手指按下时,收到的 ACTION_DOWN 事件,那是因为这里针对单手指事件,把 AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN。
  2. 当最后一个手指抬起,收到的 ACTION_UP ,而非 ACTION_POINTER_UP,道理同上。
  3. 当手指按下时,可以通过 action 获取 index,因为这里在 action 中混入 index。然后可以通过 index 转化为 pointer id,是因为通过 index,可以从 pointerProperties 数组中获取元素 PointerProperties,而 PointerProperties::id 正好就是 pointer id。
  4. 在收到 ACTION_MOVE 事件时,可以根据不同的手指 ID,获取触摸事件的 x,y 坐标。是因为可以把 pointer id 转化为 index,在以 index 为索引,从 pointerCoords 数组中获取对应手指的 x,y 坐标。如何把 pointer id 转化为 index 呢?由于 PointerProperties::id 保存是就是 pointer id,因此可以遍历数组 pointerProperties(元素类型为 PointerProperties),匹配 pointer id 匹配成功,就可以获取遍历时使用的 index。