InputManagerService之事件分发(二)

通过第一篇文章的学习分析,知道触摸事件最终会走到InputDispatcher的notifyMotion方法。

InputDispatcher.cpp#
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {

    // Just enqueue a new motion event.
    //将事件进一步封装
   MotionEntry* newEntry = new MotionEntry(args->eventTime,
             args->deviceId, args->source, policyFlags,
             args->action, args->actionButton, args->flags,
             args->metaState, args->buttonState,
             args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
             args->displayId,
             args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
    //将事件插入队列
    needWake = enqueueInboundEventLocked(newEntry);
    
    if (needWake) {
        //如果需要就唤醒InputDispatcherThread
        mLooper->wake();
    }
}

在notifyMotion方法中,会把事件插入到队列中,然后如果需要就会唤醒InputDispatcherThread线程。

// --- InputDispatcherThread ---
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
        Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}

InputDispatcherThread::~InputDispatcherThread() {
}

bool InputDispatcherThread::threadLoop() {
    //调用InputDispatcher的dispatchOnce方法
    mDispatcher->dispatchOnce();
    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()) {
            //这里分发输入事件给window
            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);
    //休眠timeoutMillis时间
    mLooper->pollOnce(timeoutMillis);
}

这两个代码都是第一篇文章分析过的,这篇文章的重点是看事件是如何分发给具体的window的,也就是分析dispatchOnceInnerLocked方法。

InputDispatcher.cpp#
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
   
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
            // 事件队列(mInboundQueue)是空的并且没有待处理的事件(mPendingEvent)就直接返回
            if (!mPendingEvent) {
                return;
            }
        } else {
            //事件队列不为空,就取出队列开头的第一个事件,赋值给mPendingEvent,就是待处理的事件
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }
    }

    // Now we have an event to dispatch.
    // All events are eventually dequeued and processed this way, even if we intend to drop them.
    bool done = false;
    //事件被丢弃的原因,默认是DROP_REASON_NOT_DROPPED不丢弃
    DropReason dropReason = DROP_REASON_NOT_DROPPED;
   
    switch (mPendingEvent->type) {
     //....
     //其他的事件类型都不看了,这里只看触摸事件相关的逻辑
   	 case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        //继续分发事件
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
 	   }
    }

    if (done) {
		//释放本次分发的mPendingEvent对象
        releasePendingEventLocked();
        //把nextWakeupTime设置为LONG_LONG_MIN,会使得InputDispatcherThread被尽快的唤醒以便执行下   	  //一次的分发,nextWakeupTime就是阻塞的超时时间,
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

继续往下看dispatchMotionLocked方法

InputDispatcher.cpp#
bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // Preprocessing.
    if (! entry->dispatchInProgress) {
        //设置一个标记,分发中~
        entry->dispatchInProgress = true;
    }

    // Clean up if dropping the event.
    //如果事件是需要被丢弃的,直接返回true
    if (*dropReason != DROP_REASON_NOT_DROPPED) {
        setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
                ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
        return true;
    }

    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;

    // inputTargets存储所有的目标窗口信息
    Vector<InputTarget> inputTargets;

    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        // Pointer event.  (eg. touchscreen)
        //寻找合适的目标窗口以便处理点击事件,(比如触摸屏幕)
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        // Non touch event.  (eg. trackball)
        //寻找合适的目标窗口以便处理非触摸形式的事件,(比如轨迹球)
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
     //输入事件被挂起,说明找到了窗口并且窗口无响应,直接返回false
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }

    setInjectionResultLocked(entry, injectionResult);
     //返回值不为INPUT_EVENT_INJECTION_SUCCEEDED,表示无法找到合适窗口。例如窗口未获取焦点,或者点击      //位置没落在任何一个窗口内,直接丢弃
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
        //...	 
        return true;
    }
    //将所有找到的目标窗口都添加到inputTargets中
     addMonitoringTargetsLocked(inputTargets);

    // Dispatch the motion.
    //将事件分发给inputTargets中的窗口
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

那么是如何为一个事件找到合适的窗口的呢,这里以点击事件为例,分析findTouchedWindowTargetsLocked方法。

InputDispatcher.cpp#
int32_t InputDispatcher::findTouchedWindowTargetsLocked(const MotionEntry* entry, Vector<InputTarget>& inputTargets...) {
    
    if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
        /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
        // 从MotionEntry中获取事件坐标点
        int32_t pointerIndex =getMotionEventActionPointerIndex(action);
        int32_t x = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_X));
        int32_t y = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_Y));

        // Traverse windows from front to back to find touched window and outside targets.
        size_t numWindows = mWindowHandles.size();
         //遍历窗口信息找到合适的窗口。
        for (size_t i = 0; i < numWindows; i++) {
            //InputWindowHandle就是一个窗口对应的信息
            sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
            const InputWindowInfo* windowInfo = windowHandle->getInfo();
            //获取窗口的标记
            int32_t flags = windowInfo->layoutParamsFlags;
            //如果窗口可见
            if (windowInfo->visible) {
                  //如果窗口的标记是可触摸的会进入这个if分支
                if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    //窗口状态可触摸并且我们点击的位置落在这个窗口
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        //发现了,退出循环
                        newTouchedWindowHandle = windowHandle;
                        break; // found touched window, exit window loop
                    }
                }

            }
        }
        //如果没有找到合适的窗口
        if (newTouchedWindowHandle == NULL) {
          //尝试将指针指向找到的第一个前台窗口(如果有)。
            newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle();
            if (newTouchedWindowHandle == NULL) {
                injectionResult = INPUT_EVENT_INJECTION_FAILED;
                goto Failed;
            }
        }
         //把找到的窗口保存到TempTouchState中,以便后续处理
        mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
    } 


    //检查mTempTouchState中所有目标窗口是否准备好接受新的输入事件
    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
        const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
            // 检查窗口是否准备好接受更多输入
            String8 reason = checkWindowReadyForMoreInputLocked(currentTime,
                    touchedWindow.windowHandle, entry, "touched");
            if (!reason.isEmpty()) {
                // 如果窗口不能接收新事件,则将不能的原因赋值给injectionResult
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string());
                goto Unresponsive;
            }
        }
    }

    // Success!  Output targets.
    //走到这里就是找到了窗口!将injectionResult设置为INPUT_EVENT_INJECTION_SUCCEEDED表示成功
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;

    //为mTempTouchState中的每个窗口信息生成全局的InputTargets
    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
        const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                touchedWindow.pointerIds, inputTargets);
    }

    // Drop the outside or hover touch windows since we will not care about them
    // in the next iteration.
    mTempTouchState.filterNonAsIsTouchWindows();

Failed:
//...
Unresponsive:.
    //重置mTempTouchState
    mTempTouchState.reset();
    return injectionResult;
}

获取到所有的InputTargets后继续分析dispatchEventLocked方法==》

InputDispatcher.cpp#
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {

    pokeUserActivityLocked(eventEntry);

    for (size_t i = 0; i < inputTargets.size(); i++) {
        //遍历输入目标
        const InputTarget& inputTarget = inputTargets.itemAt(i);
		//获取连接索引
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            //Connection可以理解为InputDispatcher和目标窗口的连接,是用来跨进程通信的
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            //开始事件分发循环,事件最终会被分发到目标窗口
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } else {
        }
    }
}

继续分析~

InputDispatcher.cpp#
void InputDispatcher::prepareDispatchCycleLocked(...) {
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

void InputDispatcher::enqueueDispatchEntriesLocked(...) {
      startDispatchCycleLocked(currentTime, connection);
}


void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {

    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
   
        // Publish the event.
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {

        case EventEntry::TYPE_MOTION: {
            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);

            // Publish the motion event.
            //发送触摸事件
            status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
                    motionEntry->deviceId, motionEntry->source,
                    dispatchEntry->resolvedAction, motionEntry->actionButton,
                    dispatchEntry->resolvedFlags, motionEntry->edgeFlags,
                    motionEntry->metaState, motionEntry->buttonState,
                    xOffset, yOffset, motionEntry->xPrecision, motionEntry->yPrecision,
                    motionEntry->downTime, motionEntry->eventTime,
                    motionEntry->pointerCount, motionEntry->pointerProperties,
                    usingCoords);
            break;
        }

    }
}

最终通过 connection->inputPublisher.publishMotionEven方法将触摸事件跨进程分发到我们的app的目标窗口的根view。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值