Android Input系统4 InputDispatcher线程

一 InputDispatcher 起点

上篇文章 Android Input系统3 InputReader线程,我们介绍了 InputReader 利用 EventHub 获取数据后生成 EventEntry 事件,然后加入到 InputDispatcher 的 mInboundQueue 队列,再唤醒 InputDispatcher 线程。本文将介绍 InputDispatcher,同样从 threadLoop 为起点开始分析。

1.1 threadLoop

先来回顾一下 InputDispatcher 对象的初始化过程:
InputDispatcher.cpp

InputDispatcher::InputDispatcher(
        const sp<InputDispatcherPolicyInterface>& policy) :
    mPolicy(policy),
    mPendingEvent(nullptr), mLastDropReason(DROP_REASON_NOT_DROPPED),
    mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
    mNextUnblockedEvent(nullptr),
    mDispatchEnabled(false), mDispatchFrozen(false),
    mInputFilterEnabled(false),
    mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
    // 创建 Looper 对象
    mLooper = new Looper(false);
    ......
    // 获取分发超时参数
    // policy 是 NativeInputManager,最终回调到 IMS
    policy->getDispatcherConfiguration(&mConfig);
}

该方法主要工作:

  • 创建属于自己线程的 Looper 对象
  • 超时参数来自于 IMS,参数默认值 keyRepeatTimeout = 500,keyRepeatDelay = 50。

InputDispatcher.cpp

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

整个过程就是不断循环地调用 InputDispatcher 的 dispatchOnce() 来分发事件。

1.2 dispatchOnce

InputDispatcher.cpp

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    {
        ......
        if (!haveCommandsLocked()) {
            // 当 mCommandQueue 不为空时处理
            // 通过 dispatchOnceInnerLocked() 进行输入事件的派发
            // 其中的传出参数 nextWakeupTime 决定了下次派发线程循环的执行时间
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    }

    nsecs_t currentTime = now(); // 获取当前时间
    // 计算 InputDispatcherThread 线程的睡眠时间
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis); // 进入 epoll_wait
}

haveCommandsLocked() 用于检查 InputDispatcher 的缓存队列 mCommandQueue 中是否有等待处理的命令,如果没有就会执行 dispatchOnceInnerLocked 函数,用来对输入事件进行分发。如果有命令需要处理,则跳过事件分发进行命令的处理操作。

bool InputDispatcher::haveCommandsLocked() const {
    return !mCommandQueue.empty();
}

mCommandQueue 是一个存储 CommandEntry 结构体的队列,CommandEntry 是用来描述一条指令,是调度当前 input 事件的指令,构造 CommandEntry 时需要向其构造函数传递一个 Command 对象,这个 Command 其实是个函数指针:

typedef std::function<void(InputDispatcher&, CommandEntry*)> Command;
struct CommandEntry {
    explicit CommandEntry(Command command);
	......
}

如果 mCommandQueue 不为空,就会调用 runCommandsLockedInterruptible 函数,循环遍历执行 mCommandQueue 中的所有指令:

bool InputDispatcher::runCommandsLockedInterruptible() {
    if (mCommandQueue.empty()) {
        return false;
    }
    do {
        std::unique_ptr<CommandEntry> commandEntry =
                std::move(mCommandQueue.front());
        mCommandQueue.pop_front();
        Command command = commandEntry->command;
        // commands are implicitly 'LockedInterruptible'
        command(*this, commandEntry.get()); 

        commandEntry->connection.clear();
    } while (!mCommandQueue.empty());
    return true;
}

即循环取出队列中的元素 CommandEntry,并执行其 command 函数,command 函数就是构造 CommandEntry 时传入的函数指针,至于 CommandEntry 是什么时候和如何构造的,我们下面会分析。

如果 mCommandQueue 队列为空,就会调用 dispatchOnceInnerLocked 函数,进行事件分发。

事件分发完毕后,最后调用 Looper 的 pollOnce 函数使 InputDispatcherThread 进入睡眠状态,并将它的最长的睡眠的时间设置为 timeoutMillis。

当有输入事件产生时,InputReader 就会将睡眠状态的 InputDispatcherThread 唤醒,然后会重新开始调用 InputDispatcher 的 dispatchOnce 方法,执行对输入事件的分发。就这样一直循环下去。

线程执行 Looper->pollOnce,进入 epoll_wait 等待状态后,当发生以下任一情况则退出休眠状态:

  • timeout:到达 nextWakeupTime 时间,超时唤醒
  • wake: 主动调用 Looper 的 wake() 方法(有输入事件注入派发队列中时)
  • epol_wait() 监听的 fd 有 epoll_event 发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)

熟悉 Looper,handler 机制的同学应该很清楚这个函数的作用,它会让当前线程进入睡眠状态,核心原理是采用 epoll 机制对指定 fd 进行监听,对于 InputDispatcherThread 线程来说,主要监听了两个 fd,一个是 Looper 创建时就会默认添加到监听的 mWakeEventFd,另一个是后面要分析的 InputChannel 机制的 fd。关于 Looper 机制,同学们可以参考 Android消息机制2 (Native层)

dispatchOnce 小结:

派发线程的一次循环包括如下三项工作:

  • 进行一次事件派发。事件的派发工作仅当命令队列中没有命令时才会进行。派发工作会设置 nextWakeupTime,指明随后休眠时间的长短
  • 执行命令列表中的命令。所谓的命令,不过是一个符合 command 签名的回调函数,可以通过 InputDispatcher::postCommandLocked() 函数将其追加到命令列表中
  • 陷入休眠状态

可见派发线程的线程循环是比较清晰的,不过读者可能对 nextWakeupTime 的存在意义有一些费解。它对派发线程的执行过程有着举足亲重的作用。假如,当派发队列中最后一个事件派发完成后,nextWakeupTime 将被设置为 LONG_LONG_MAX,使之在新的输入事件或命令到来前休眠以节约资源。另外,有时因为窗口尚未准备好接受事件(如已经有一个事件发送给窗口,但此窗口尚未对其进行反馈),则可以放弃此事件的派发并设置 nextWakeupTime 为一个合理的时间点,以便在下次循环时再尝试派发。

二 InputDispatcher事件分发

接下来看输入事件分发的核心函数 dispatchOnceInnerLocked。

2.1 dispatchOnceInnerLocked

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now(); // 当前时间,也是后面 ANR 计时的起点

    if (!mDispatchEnabled) { // 默认值为 false
        resetKeyRepeatLocked(); // 重置上一个事件
    }
    // 分发被冻结, WMS 冻屏时会设置这个值,例如转屏
    if (mDispatchFrozen) { // 默认值为 false
        return; // 如果 InputDispatcher 被冻结,则不进行派发操作,直接返回
    }

    // 对于特殊按键 HOME, END_CALL, APP_SWITCH 增加了 0.5s 超时时间
    // 超时之后会丢弃其他即将处理的按键事件。即 isAppSwitchDue 为 true 的情况
    // 目的是优化 app 切换延迟,当切换超时,则抢占分发,丢弃其他所有即将要处理的事件
    // 如果 isAppSwitchDue 为 true,说明没有及时响应 HOME 键等操作
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    // 这样当 InputDispatcher 处理完分发事件后,会第一时间处理窗口切换操作
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }
    // 如果还没有待分发的事件,去 mInboundQueue 中取出一个事件
    if (!mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
            if (isAppSwitchDue) {
       // The inbound queue is empty so the app switch key we were waiting
                // for will never arrive.  Stop waiting for it.
                resetPendingAppSwitchLocked(false);
                isAppSwitchDue = false;
            }
            if (!mPendingEvent) {
            // 如果 mInboundQueue 为空,并且没有待分发的事件,就 return
                return;
            }
        } else {
            // 取队列 mInboundQueue 头部的 EventEntry 赋值给 mPendingEvent 
            mPendingEvent = mInboundQueue.dequeueAtHead();
        }
        ......
        resetANRTimeoutsLocked(); // 重置 ANR 信息
    }

    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED; // 默认值为不丢弃
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
    // 当前事件不包含 POLICY_FLAG_PASS_TO_USER 的标志,则不合法,需要丢弃
        dropReason = DROP_REASON_POLICY;
    } else if (!mDispatchEnabled) {
    // 当前事件被禁止分发,需要丢弃
        dropReason = DROP_REASON_DISABLED;
    }
    ......

    switch (mPendingEvent->type) {// 判断事件的类型,有 key,motion 等
      ......
      case EventEntry::TYPE_KEY: {
          KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
          if (isAppSwitchDue) {
              if (isAppSwitchKeyEventLocked(typedEntry)) {
                  // 重置 mAppSwitchDueTime
                  resetPendingAppSwitchLocked(true);
                  isAppSwitchDue = false;
              } else if (dropReason == DROP_REASON_NOT_DROPPED) {
                  // 没有及时响应窗口切换操作
                  // 由于 APP 切换需要丢弃非 HOME,ENDCALL,APP_SWITCH 之外的事件
                  dropReason = DROP_REASON_APP_SWITCH;
              }
          }
          if (dropReason == DROP_REASON_NOT_DROPPED
                  && isStaleEventLocked(currentTime, typedEntry)) {
              // 当前处理事件的时间减去事件产生的时间大于 10s 也需要丢弃
              dropReason = DROP_REASON_STALE;// 事件过期
          }
          if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
              dropReason = DROP_REASON_BLOCKED;// 阻碍其他窗口获取事件
          }
          // 进一步分发按键事件,结果赋值给 done
          // 无论是成功派发还是事件被丢弃,都返回 true,否则返回false
          // 以便在下次循环时再次尝试此事件的派发
          done = dispatchKeyLocked(currentTime, typedEntry,
                  &dropReason, nextWakeupTime);
          break;
      }
      ......
    }
    ......
    // 如果事件派发完成,则准备派发下一个事件
    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            // 最后事件还是被丢弃
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        releasePendingEventLocked(); // 释放 pending 事件 mPendingEvent 等
        // 立即开始下一次循环。如果此时派发队列为空,
        // 下次循环调用此函数时会保持 nextWakeupTime 为 
        // LONG_LONG_MAX 并直接返回,使得派发线程进入无限期休眠
        *nextWakeupTime = LONG_LONG_MIN; // 强制立刻执行轮询
    }
}

在 enqueueInboundEventLocked() 的过程中已设置 mAppSwitchDueTime 等于 eventTime 加上 500ms:

mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;

dispatchOnceInnerLocked 主要功能小结:

1.如果派发队列 mInboundQueue 为空,则会使派发线程陷入无限期休眠状态

2.如果 mPendingEvent 为空,则从派发队列中取出一个并保存在 mPendingEvent 中

3.事件有可能因为某些原因被丢弃,被丢弃的原因保存在 dropReason 中

4.不同类型的事件使用不同的派发函数进行实际的派发动作。如 Motion 事件使用 dispatchMotionLocked() 函数进行派发,Key 事件使用 dispatchKeyLocked 函数进行派发

5.派发一个事件至少需要一次线程循环才能完成。是否在下次循环继续尝试此事件的派发由派发函数的返回值决定

6.事件的派发是串行的,在排队首的事件完成派发或被丢弃之前,不会对后续的事件进行派发。理解 InputDispatcher 的这一特点非常重要

派发一个事件至少需要一次线程循环才能完成的原因是:

事件的目标窗口有可能正在处理先前的一个输入事件,在窗口完成先前事件的处理并给予反馈之前,InputDispatcher 不会再向此窗口派发新事件。

其他说明:

1.mDispatchFrozen 用于决定是否冻结事件分发工作,如果冻结则不再往下执行。InputDispatcher 有三种状态,分别是正常状态、冻结状态和禁用状态,可以通过 InputDispatcher 的 setInputDispatchMode 函数来设置

2.当特定输入事件(HOME,END_CALL,APP_SWITCH)分发的时间点距离该事件加入 mInboundQueue 的时间超过 500ms,则认为 app 切换过期,即 isAppSwitchDue=true

3.执行完成 (done) 后的处理工作

  • 根据 dropReason ( 默认 NOT_DROPPED 不丢弃) 来决定是否丢失事件
  • 释放当前正在处理的事件(即 mPendingEvent) releasePendingEventLocked

另外,读者可能对此函数最后设置 nextWakeupTime 为 LONG_LONG_MIN 使派发线程立即进行下一次循环有一些疑问。如果此时派发队列为空,为什么不设置其为 LONG_LONG_MAX 使其进入无限期休眠状态呢?在这里先给读者提个醒,当派发队列为空时,派发线程可能需要在下次循环中生成重复按键事件,因此不能直接进入休眠。后面的内容会讨论这个话题。

2.1.1 dropInboundEventLocked

void InputDispatcher::dropInboundEventLocked(EventEntry* entry,
    DropReason dropReason) {
    const char* reason;
    switch (dropReason) {
    case DROP_REASON_POLICY:
        reason = "inbound event was dropped because the policy consumed it";
        break;
    case DROP_REASON_DISABLED:
        if (mLastDropReason != DROP_REASON_DISABLED) {
            ALOGI("Dropped event because input dispatch is disabled.");
        }
        reason = "inbound event was dropped because input" + 
        " dispatch is disabled";
        break;
    case DROP_REASON_APP_SWITCH:
        ALOGI("Dropped event because of pending overdue app switch.");
        reason = "inbound event was dropped because of pending "+
        "overdue app switch";
        break;
    case DROP_REASON_BLOCKED:
        ALOGI("Dropped event because the current application is not "+
        "responding and the user "
         "has started interacting with a different application.");
        reason = "inbound event was dropped because the current"+
        " application is not responding "
        "and the user has started interacting with a different application";
        break;
    case DROP_REASON_STALE:
        ALOGI("Dropped event because it is stale.");
        reason = "inbound event was dropped because it is stale";
        break;
    default:
        return;
    }

    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
        CancelationOptions options(
        CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
        synthesizeCancelationEventsForAllConnectionsLocked(options);
        break;
    }
    ......
    }
}

前面的代码介绍了因为 DispatcherPolicy 认为事件不应派发给用户,以及 InputDispatcher 被停用而导致事件被丢弃。DropReason 枚举完整地描述了事件被丢弃的所有原因。

  • DROP_REASON_POLICY:某些输入事件具有系统级的功能,例如 HOME 键、电源键、电话接听/挂断键等被系统处理,因此 DispatcherPolicy 不希望这些事件被窗口所捕获。当 InputDispatcher 在将输入事件放入派发队列前,向 DispatcherPolicy 询问此事件的派发策略时,DispatcherPolicy 会将 POLICY_FLAG_PASS_TO_USER 策略去掉。没有这个派发策略的事件对象会被丢弃
  • DROP_REASON_APP_SWITCH:我们知道 InputDispatcher 的事件派发是串行的。因此在前一个事件的派发成功并得到目标窗口的反馈前,后续的输入事件都会被其阻塞。当某个窗口因程序缺陷而无法响应输入时,懊恼的用户可能会尝试使用 HOME 键退出这个程序。然而遗憾的是,由于派发的串行性,用户所按的 HOME 键在其之前的输入事件成功派发给无响应的窗口之前无法获得派发的机会,因此在 ANR 对话框弹出之前的 5 秒里,用户不得不面对无响应的应用程序欲哭无泪。为了解决这个问题,InputDispatcher 为 HOME 键设置了限时派发的要求。当 InputDispatcher 的 enqueueInboundEventLocked() 函数发现 HOME 键被加入派发队列后,便要求 HOME 键之前的所有输入事件在 0.5 秒(由 APP_SWITCH_TIMEOUT 常量定义)之前派发完毕,否则这些事件将都会被丢弃,使得 HOME 键至少能够在 0.5 秒内得到响应
  • DROP_REASON_BLOCKED:和 APP_SWITCH 原因类似,如果是因为一个窗口无法响应输入事件,用户可能希望在其他窗口上进行点击,以尝试是否能够得到响应。因为派发的串行性,这次尝试会以失败而告终。为此,当 enqueueInboundEventLocked() 发现有窗口正阻塞着派发的进行,并且新入队的触摸事件的目标是另外一个窗口,则将这个新事件保存到 mNextUnblockedEvent 中。随后的 dispatchOnceInnerLocked() 会将此事件之前的输入事件全部丢弃,使得用户在其他窗口上进行点击的尝试可以立即得到响应
  • DROP_REASON_DISABLED:因为 InputDispatcher 被禁用而使得事件被丢弃。setInputDispatchMode() 函数可以使得 InputDispatcher 在禁用、冻结与正常三种状态之间进行切换。禁用状态会使得所有事件被丢弃,冻结将会使得 dispatchOnceInnerLocked() 函数直接返回从而停止派发工作。InputDispatcher 的这三种状态的切换由 java 层的 IMS 提供接口,由 AMS 和 WMS 根据需要进行设置。例如,当手机进入休眠状态时,InputDispatcher 会被禁用,而屏幕旋转过程中,InputDispatcher 会被暂时冻结
  • DROP_REASON_STALE:在 dispatchOnceInnerLocked() 函数准备对事件进行派发时,会先检查一下事件所携带的事件戳与当前时间的差距。如果事件过于陈旧(10 秒以上,由常量 STALE_EVENT_TIMEOUT 所指定),则此事件需要被抛弃

当事件幸运地避开所有上述原因之后,才能由 InputDispatcher 尝试派发。对 Key 事件来说,下一步是 dispatchKeyLocked() 函数。在这个函数中,InputDispatcher 将为事件寻找合适的目标窗口。

关于 dispatchKeyLocked 分发事件:

1.不会执行 done 的情况:

  • 当前 Event 时间小于唤醒时间
  • 让 policy 有机会执行拦截操作
  • 调用 findFocusedWindowTargetsLocked 方法的返回结果是 INPUT_EVENT_INJECTION_PENDING,即 targets 没有处于 Ready 状态

2.会执行 done 的情况:

  • 该事件需要丢弃,即 dropReason != DROP_REASON_NOT_DROPPED
  • findFocusedWindowTargetsLocked 的返回结果不是 INPUT_EVENT_INJECTION_PENDING (没有正在处理的事件)

下面会以按键为例来展开说明 dispatchKeyLocked。

2.2 dispatchKeyLocked

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ......
    if (entry->interceptKeyResult ==
        KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
        // case1: 当前时间小于唤醒时间,则进入等待状态
        if (currentTime < entry->interceptKeyWakeupTime) {
            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                *nextWakeupTime = entry->interceptKeyWakeupTime;
            }
            return false; //直接返回 wait until next wakeup
        }
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
        entry->interceptKeyWakeupTime = 0;
    }

    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
        //case2: 让 policy 有机会执行拦截操作
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
     CommandEntry* commandEntry = postCommandLocked(
     & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);  
     sp<InputWindowHandle> focusedWindowHandle =
     getValueByKey(mFocusedWindowHandlesByDisplay, getTargetDisplayId(entry));
            if (mFocusedWindowHandle != nullptr) {
                commandEntry->inputChannel =
                    getInputChannelLocked(focusedWindowHandle->getToken());
            }
            commandEntry->keyEntry = entry;
            entry->refCount += 1;
            return false; //直接返回 wait for the command to run
        } else {
            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
        }
    } else if (entry->interceptKeyResult == 
              KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
        if (*dropReason == DROP_REASON_NOT_DROPPED) {
            *dropReason = DROP_REASON_POLICY;
        }
    }

    //case3: 如果需要丢弃该事件,则执行清理操作
    if (*dropReason != DROP_REASON_NOT_DROPPED) {
        setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
        ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
        mReporter->reportDroppedKey(entry->sequenceNum);
        return true; //直接返回
    }

    // 目标窗口信息列表会存储在 inputTargets 中
    Vector<InputTarget> inputTargets;
    // case4: 寻找焦点
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime);
    // 输入事件被挂起,说明找到了窗口并且窗口无响应
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false; //直接返回
    }
 
    setInjectionResultLocked(entry, injectionResult);
     // 输入事件没有分发成功,说明没有找到合适的窗口
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
        return true; // 直接返回
    }
    // Add monitor channels from event's or focused display.
    addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry));

    // 只有 injectionResult 是成功,才有机会执行分发事件
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

dispatchKeyLocked 函数,在以下场景中,有可能无法分发事件:

  • 当前时间小于唤醒时间 (nextWakeupTime) 的情况
  • policy 需要提前拦截事件的情况
  • 需要 drop 事件的情况
  • 寻找聚焦窗口失败的情况

如果成功跳过以上所有 case,则会继续调用 dispatchEventLocked,继续进行事件的分发。

2.2.1 doInterceptKeyBeforeDispatchingLockedInterruptible

接上节,我们重点来讨论第二种,也是最常见的定制需求,即:policy 需要提前拦截事件的情况(通过 PhoneWindowManager 来拦截):

dispatchKeyLocked 函数中对于输入事件,并不是直接拿到就处理,而是首先为其构造 CommandEntry,并添加到 mCommandQueue 队列,然后此函数就返回了,返回 false 表明此事件并未处理完成。

我们前面说过 mCommandQueue 这个队列,它会在执行 dispatchOnce 的时候,首先判断这个队列是否为空,如果非空的话,就会调用 runCommandsLockedInterruptible 函数来处理,方法是遍历 mCommandQueue 中的每一个 CommandEntry ,并执行其内部的 Command 函数,这个 command 函数就是构造 CommandEntry 时传入的函数指针:doInterceptKeyBeforeDispatchingLockedInterruptible,接下来会先调用这个函数判断按键事件是否被提前拦截:

void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
        CommandEntry* commandEntry) {
    // 将 KeyEntry 转换为 KeyEvent
    KeyEntry* entry = commandEntry->keyEntry;
    KeyEvent event = createKeyEvent(*entry);

    mLock.unlock();

    android::base::Timer t;
    sp<IBinder> token = commandEntry->inputChannel != nullptr
            ? commandEntry->inputChannel->getConnectionToken()
            : nullptr;
    // 这里会通过 JNI 调到 java 层 PhoneWindowManager 中去提前处理按键
    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(token, &event,
            entry->policyFlags);
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
        ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
              std::to_string(t.duration().count()).c_str());
    }

    mLock.lock();

    if (delay < 0) {
        // 按键将被拦截,不会发到应用窗口
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
    } else if (!delay) {
        // 按键会接着立即执行
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
    } else {
       //按键延时执行
        entry->interceptKeyResult = 
                KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
        entry->interceptKeyWakeupTime = now() + delay;
    }
    entry->release();
}

上面函数会将按键在发给应用窗口之前先将按键发给 java 层 PhoneWindowManager 的 interceptKeyBeforeDispatching 方法,这就给了我们提前拦截按键的机会,如果有需求禁用 HOME,RECENT,BACK 就可以到 interceptKeyBeforeDispatching 方法中添加相应 case,并返回 -1 就行了。

此函数有三个结果,1. 完全拦截按键,2. 按键立即执行,3. 按键延时执行。

按键的提前拦截执行完之后,再次循环执行到 dispatchKeyLocked 函数之时,就可以根据 PhoneWindowManager 提前拦截的结果进行下一步处理了,如果 entry->interceptKeyResult 不等于 INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER 和 INTERCEPT_KEY_RESULT_SKIP,就会接着调用 findFocusedWindowTargetsLocked 函数寻找按键分发的焦点窗口:

2.2.2 InputDispatcher::findFocusedWindowTargetsLocked

要理解 findFocusedWindowTargetsLocked,需要先明白 InputTarget 结构体。

struct InputTarget {
  enum {
    // 此标记表示事件正在交付给前台应用程序
    FLAG_FOREGROUND = 1 << 0,
    //此标记指示 MotionEvent 位于目标区域内
    FLAG_WINDOW_IS_OBSCURED = 1 << 1,
    ......
};
    // inputDispatcher 与目标窗口的通信管道
    sp<InputChannel> inputChannel; //1
    // 事件派发的标记
    int32_t flags;
    // 屏幕坐标系相对于目标窗口坐标系的偏移量
    float xOffset, yOffset;//2
    // 屏幕坐标系相对于目标窗口坐标系的缩放系数
    float scaleFactor;//3
    BitSet32 pointerIds;
}     

InputTarget 结构体可以说是 InputDispatcher 与目标窗口的转换器,存储了目标窗口的所有信息,其分为两大部分,一部分是枚举中存储的 InputDispatcher 与目标窗口交互的标记,另一部分是 InputDispatcher 与目标窗口交互参数,比如 flags,inputChannel 等。

inputChannel 本质上是一个 SocketPair,SocketPair 用于进程间双向通信,这非常适合 InputDispatcher 与目标窗口之间的通信,因为 InputDispatcher 不仅要将事件分发到目标窗口,同时 InputDispatcher 也需要得到目标窗口对事件的响应更新。其中的 xOffset 和 yOffset,是屏幕坐标系相对于目标窗口坐标系的偏移量,MotionEntry (MotionEvent) 中的存储的坐标是屏幕坐标系,因此就需要一些参数,来将屏幕坐标系转换为目标窗口的坐标系。

接下来看 findFocusedWindowTargetsLocked:

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets,
        nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    std::string reason;
    
    int32_t displayId = getTargetDisplayId(entry);
    // 首先找到焦点窗口,关于焦点窗口如何得来的,以后有时间单独分析
    sp<InputWindowHandle> focusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    // 找到焦点应用程序,由 java 层 InputMonitor 传下来
    sp<InputApplicationHandle> focusedApplicationHandle =
            getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);

   // If there is no currently focused window and no focused application
    // then drop the event.
    if (focusedWindowHandle == nullptr) {
        if (mFocusedApplicationHandle != nullptr) {
        // 可能发生 anr,使用 handleTargetsNotReadyLocked 判断
            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                    mFocusedApplicationHandle, nullptr, nextWakeupTime,
                    "Waiting because no window has focus but there is a "
                    "focused application that may eventually add a window "
                    "when it finishes starting up.");
            goto Unresponsive;
        }
    ALOGI("Dropping event because there is no focused" + 
    " window or focused application in display ");
        injectionResult = INPUT_EVENT_INJECTION_FAILED;
        goto Failed;
    }

    // 权限检查
    if (! checkInjectionPermission(focusedWindowHandle, 
        entry->injectionState)) {
        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
        goto Failed;
    }

    // 检测窗口是否为更多的输入操作而准备就绪
    // Check whether the window is ready for more input.
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            focusedWindowHandle, entry, "focused");
    if (!reason.empty()) {
        injectionResult = handleTargetsNotReadyLocked(currentTime, 
        entry, mFocusedApplicationHandle, focusedWindowHandle,
        nextWakeupTime, reason.string());
        goto Unresponsive;
    }
    // Success!  Output targets.
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    // 成功找到目标窗口,添加到目标窗口
    addWindowTargetLocked(focusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, 
            BitSet32(0), inputTargets);

    // Done.
Failed:
Unresponsive:
    // 统计等待时长信息,目前没有实现,这个方法还是很值得去改造
    nsecs_t timeSpentWaitingForApplication =
    getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatisticsLocked(currentTime, entry,
          injectionResult, timeSpentWaitingForApplication);
    return injectionResult;
}

findFocusedWindowTargetsLocked 的主要作用是对按键事件是否能够成功分发做出条件判断,我们总结一下此函数:

  • 首先会通过 mFocusedWindowHandlesByDisplay 和 mFocusedApplicationHandlesByDisplay 获得当前焦点窗口和焦点应用,提一点,焦点窗口和焦点应用并不一定是一样,从 java 层来说焦点窗口指的是 Winodw,焦点应用指的是 ActivityRecord,一般对于系统类型窗口以及子窗口来说焦点窗口和焦点应用就不一样,而对于 Activity 和 Dialog 则一致
  • 如果没有找到焦点窗口和焦点应用则直接返回失败,否则的话如果存在焦点窗口或焦点应用,这种情况是可能发生 ANR 的,则会保存 ANR timeout 时间和 ANR 可能发生的应用,ANR timeout 时间的计算规则是:从 dispatchOnceInnerLocked 开始计时 + 5s,超过这个时间输入事件还没处理完则会上报 ANR
  • 对于按键事件,需要等待之前的全部事件处理完成之后再进行,因为之前的某些事件可能导致焦点窗口的变化,假设用户触摸窗口中的按钮,然后立即按下“ A”。 如果该按钮导致出现弹出窗口,则我们要确保将“ A”键传递到新的弹出窗口。 这是因为用户通常会在键盘上打字时预料到即将发生的UI更改。要获得此行为,我们必须针对所有先前的输入事件序列化按键事件(注释翻译)
  • 最后则是按键事件成功发送了,调用 addWindowTargetLocked 函数将接收按键事件的目标窗口的一些信息保存到 inputTargets 集合中

2.2.3 handleTargetsNotReadyLocked

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
    const EventEntry* entry,
    const sp<InputApplicationHandle>& applicationHandle,
    const sp<InputWindowHandle>& windowHandle,
    nsecs_t* nextWakeupTime, const char* reason) {
    if (applicationHandle == nullptr && windowHandle == nullptr) {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
            mInputTargetWaitStartTime = currentTime; // 当前时间
            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();
        }
    } else {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
            nsecs_t timeout;
            if (windowHandle != nullptr) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != nullptr) {
                timeout = applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; // 5s
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            // 这里的 currentTime 是执行 dispatchOnceInnerLocked 方法的起点时间
            mInputTargetWaitStartTime = currentTime; 
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();

            if (windowHandle != nullptr) {
                mInputTargetWaitApplicationToken = windowHandle->getApplicationToken();
            }
            if (mInputTargetWaitApplicationToken == nullptr && applicationHandle != nullptr) {
                mInputTargetWaitApplicationToken = applicationHandle->getApplicationToken();
            }
        }
    }

    if (mInputTargetWaitTimeoutExpired) {
        return INPUT_EVENT_INJECTION_TIMED_OUT; // 等待超时已过期,则直接返回
    }

    // 当超时 5s,则进入 ANR 流程
    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);

        *nextWakeupTime = LONG_LONG_MIN; // 强制立刻执行轮询来执行 ANR 策略
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime; // 当触发超时则强制执行轮询
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

ANR 超时时间点为 mInputTargetWaitTimeoutTime,该值等于 currentTime + 5s,这里的 currentTime 是指执行 dispatchOnceInnerLocked 方法体的起点。

此处设置 mInputTargetWaitCause 等于 INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY (应用没有准备就绪),而前面 resetANRTimeoutsLocked() 过程是唯一用于重置该变量的地方。

可见,ANR 时间区间是从 dispatchOnceInnerLocked 方法体的起点,直到下次执行 handleTargetsNotReadyLocked() 方法的这段应用未准备就绪的时间段,该时间段是否超过 5s 来决定是否触发 ANR。

当前这次的事件 dispatch 过程中执行 findFocusedWindowTargetsLocked() 方法到下一次执行 resetANRTimeoutsLocked() 的时间区间。

handleTargetsNotReadyLocked() 的判断过程:

1.当 applicationHandle 和 windowHandle 同时为空,且 mInputTargetWaitCause 没有被标记为 NOT_READY(判断为系统没有 ready)

  • 设置等待理由 INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY
  • 设置超时等待时长为无限大
  • 设置 TimeoutExpired = false
  • 清空等待队列

2.当 applicationHandle 和 windowHandle 至少一个不为空,且 mInputTargetWaitCause 没有被标记为 NOT_READY

  • 设置等待理由 INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
  • 设置超时等待时长为 5s
  • 设置 TimeoutExpired = false
  • 清空等待队列

继续回到 findFocusedWindowTargetsLocked,查看 checkWindowReadyForMoreInputLocked

2.2.4 checkWindowReadyForMoreInputLocked

String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // 当窗口暂停的情况,则保持等待
    if (windowHandle->getInfo()->paused) {
        return String8::format("Waiting because the %s window is paused.", targetType);
    }

    // 当窗口连接未注册,则保持等待
    ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
    if (connectionIndex < 0) {
        return String8::format("Waiting because the %s window's input channel is not "
                "registered with the input dispatcher. The window may be in the process "
                "of being removed.", targetType);
    }

    // 当窗口连接已死亡,则保持等待
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    if (connection->status != Connection::STATUS_NORMAL) {
        return String8::format("Waiting because the %s window's input connection is %s."
                "The window may be in the process of being removed.", targetType,
                connection->getStatusLabel());
    }

    // 当窗口连接已满,则保持等待
    if (connection->inputPublisherBlocked) {
        return String8::format("Waiting because the %s window's input channel is full. "
                "Outbound queue length: %d. Wait queue length: %d.",
                targetType, connection->outboundQueue.count(), connection->waitQueue.count());
    }


    if (eventEntry->type == EventEntry::TYPE_KEY) {
        // 按键事件,输出队列或事件等待队列不为空
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return String8::format("Waiting to send key event because the %s window has not "
                    "finished processing all of the input events that were previously "
                    "delivered to it. Outbound queue length: %d. Wait queue length: %d.",
                    targetType, connection->outboundQueue.count(), connection->waitQueue.count());
        }
    } else {
        // 非按键事件,事件等待队列不为空且头事件分发超时 500ms
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return String8::format("Waiting to send non-key event because the %s window has not "
                    "finished processing certain input events that were delivered to it over "
                    "%0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms.",
                    targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
                    connection->waitQueue.count(),
                    (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
        }
    }
    return String8::empty();
}

继续回到 findFocusedWindowTargetsLocked,如果没有发生 ANR,则会通过 addWindowTargetLocked() 将该事件的目标窗口添加到 inputTargets 列表中。

我们来看 addWindowTargetLocked 函数:

2.2.5 addWindowTargetLocked

void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
        int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
    sp<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
    if (inputChannel == nullptr) {
        ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
        return;
    }

    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    // 构建 InputTarget 添加到 inputTargets 中
    InputTarget target;
    target.inputChannel = inputChannel;
    target.flags = targetFlags;
    target.xOffset = - windowInfo->frameLeft;
    target.yOffset = - windowInfo->frameTop;
    target.globalScaleFactor = windowInfo->globalScaleFactor;
    target.windowXScale = windowInfo->windowXScale;
    target.windowYScale = windowInfo->windowYScale;
    target.pointerIds = pointerIds;
    inputTargets.push_back(target);
}

构建一个 InputTarget 对象 target,并将当前焦点窗口 focusedWindowHandle 的所有信息添加到 target 中,然后把 target 添加到 inputTargets 中,需要重点关注 inputChannel。

至此,findFocusedWindowTargetsLocked 函数分析完毕,也就是检查是否发生 ANR,同时找到目标窗口后,把窗口信息用 InputTarget 来表示,并添加到 inputTargets 中。

接下来回到 dispatchKeyLocked 函数中,最后调用 dispatchEventLocked 继续来看事件的分发。

2.3 dispatchEventLocked

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    // 向 mCommandQueue 队列添加 doPokeUserActivityLockedInterruptible 命令
    pokeUserActivityLocked(eventEntry);

    for (const InputTarget& inputTarget : inputTargets) {
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            // 找到目标连接
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        }
    }
}

这里会遍历 inputTargets,并调用 prepareDispatchCycleLocked 函数继续事件的分发。

需要重点关注参数 connection,它是用来描述窗口的连接,每一个窗口对应一个连接,这个连接就是用来将输入事件传递到目标窗口,connection 是在 registerInputChannel 的时候创建的,可以参考 Android Input系统5 UI线程

其中 pokeUserActivityLocked(eventEntry) 方法,最终会调用到 Java 层的 PowerManagerService.java 中的 userActivityFromNative() 方法。 这也是 PMS 中唯一的 native call 方法。

2.3.1 getConnectionIndexLocked

ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
    if (inputChannel == nullptr) {
        return -1;
    }
    for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
        sp<Connection> connection = mConnectionsByFd.valueAt(i);
        if (connection->inputChannel->getToken() == inputChannel->getToken()) {
            return i;
        }
    }
    return -1;
}

根据 inputChannel 的 fd 从 mConnectionsByFd 队列中查询目标 connection。

2.4 prepareDispatchCycleLocked

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {

    if (connection->status != Connection::STATUS_NORMAL) {
        return; //当连接已破坏,则直接返回
    }
    ......
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

2.5 enqueueDispatchEntriesLocked

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
    bool wasEmpty = connection->outboundQueue.isEmpty();
    // 调用了六次 enqueueDispatchEntryLocked 函数,仅仅最后一个参数不同
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_IS);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

    if (wasEmpty && !connection->outboundQueue.isEmpty()) {
        // 当原先的 outbound 队列为空, 且当前 outbound 不为空的情况执行
        startDispatchCycleLocked(currentTime, connection);
    }
}

该方法主要功能:

  • 根据 dispatchMode 来分别执行 DispatchEntry 事件加入队列的操作
  • 当起初 connection.outboundQueue 等于空,经 enqueueDispatchEntryLocked 处理后,outboundQueue 不等于空情况下,则执行startDispatchCycleLocked() 方法

2.6 enqueueDispatchEntryLocked

void InputDispatcher::enqueueDispatchEntryLocked(
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
        int32_t dispatchMode) {
    int32_t inputTargetFlags = inputTarget->flags;
    if (!(inputTargetFlags & dispatchMode)) {
        return; // 分发模式不匹配,则直接返回
    }
    inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;

    // 将 EventEntry 转换为 DispatchEntry, 加入 connection 的 outbound 队列
    DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry,
            inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
            inputTarget->scaleFactor);

    switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
            dispatchEntry->resolvedAction = keyEntry->action;
            dispatchEntry->resolvedFlags = keyEntry->flags;

            if (!connection->inputState.trackKey(keyEntry,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
                delete dispatchEntry;
                return; // 忽略不连续的事件
            }
            break;
        }
        case EventEntry::TYPE_MOTION: {
            ......
        }
        ......
    }
    ......
    // 添加到 connection 的 outboundQueue 队尾
    // Enqueue the dispatch entry.
    connection->outboundQueue.enqueueAtTail(dispatchEntry);
}

enqueueDispatchEntryLocked 主要功能:

  • 根据 dispatchMode 来决定是否需要加入 outboundQueue 队列
  • 根据 EventEntry 来生成 DispatchEntry 事件
  • 将 dispatchEntry 加入到 connection 的 outboundQueue 队列

执行到这里,其实等于做了一次搬运的工作,将 InputDispatcher 中 mInboundQueue 中的事件取出后,找到目标 window 后,封装成 dispatchEntry 加入到目标窗口对应的 connection 中的 outboundQueue 队列。

2.7 startDispatchCycleLocked

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    // 当 Connection 状态正常,且 outboundQueue 不为空
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        dispatchEntry->deliveryTime = currentTime; // 设置 deliveryTime 时间

        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
          case EventEntry::TYPE_KEY: {
              KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);

              // 核心动作:发布 Key 事件
              status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
                      keyEntry->deviceId, keyEntry->source,
                      dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                      keyEntry->keyCode, keyEntry->scanCode,
                      keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                      keyEntry->eventTime);
              break;
          }
          ......
        }

        // Check the result.
        if (status) { // publishKeyEvent 失败情况
            if (status == WOULD_BLOCK) {
                if (connection->waitQueue.isEmpty()) {
                    // pipe 已满,但 waitQueue 为空. 不正常的行为
                    abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
                } else {
                    // Pipe is full and we are waiting for the app to finish process some events
                    // before sending more events to it.
                    // 处于阻塞状态
                    connection->inputPublisherBlocked = true;
                }
            } else {
                // 不不正常的行为
                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
            }
            return;
        }

        // 从 outboundQueue 中取出事件,重新放入 waitQueue 队列
        connection->outboundQueue.dequeue(dispatchEntry);
        connection->waitQueue.enqueueAtTail(dispatchEntry);
    }
}

startDispatchCycleLocked 说明:

1.startDispatchCycleLocked 触发时机:当起初 connection.outboundQueue 等于空,经 enqueueDispatchEntryLocked 处理后,outboundQueue 不等于空。

2.startDispatchCycleLocked 主要功能:将目标窗口 connection 的 outboundQueue 队列中的 dispatchEntry 依次取出来,对于按键类型的输入事件会调用 connection->inputPublisher 的 publishKeyEvent 进行分发,分发成功之后会将 outboundQueue 队列中的 dispatchEntry 移除,并转移到 waitQueue 中。

3.publishKeyEvent 执行结果 status 不等于 OK 的情况下:

  • WOULD_BLOCK,且 waitQueue 等于空,则调用 abortBrokenDispatchCycleLocked(),该方法最终会调用到 Java 层的IMS.notifyInputChannelBroken()
  • WOULD_BLOCK,且 waitQueue 不等于空,则处于阻塞状态,则设置 inputPublisherBlocked=true
  • 其他情况,则调用 abortBrokenDispatchCycleLocked

2.8 inputPublisher.publishKeyEvent

status_t InputPublisher::publishKeyEvent(...) {
    ......
    // 拿到输入事件的各种信息之后构造一个 InputMessage
    InputMessage msg;
    msg.header.type = InputMessage::TYPE_KEY;
    msg.body.key.seq = seq;
    msg.body.key.deviceId = deviceId;
    msg.body.key.source = source;
    msg.body.key.action = action;
    msg.body.key.flags = flags;
    msg.body.key.keyCode = keyCode;
    msg.body.key.scanCode = scanCode;
    msg.body.key.metaState = metaState;
    msg.body.key.repeatCount = repeatCount;
    msg.body.key.downTime = downTime;
    msg.body.key.eventTime = eventTime;
    // 通过 InputChannel 来发送消息
    return mChannel->sendMessage(&msg);
}

接下来看 InputChannel 的 sendMessage:

status_t InputChannel::sendMessage(const InputMessage* msg) {
    const size_t msgLength = msg->size();
    InputMessage cleanMsg;
    msg->getSanitizedCopy(&cleanMsg);
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite < 0) {
        //异常情况,发送失败
        return -error;
    }

    if (size_t(nWrite) != msgLength) {

        return DEAD_OBJECT;
    }

    return OK;
}

sendMessage 的核心就是向 mFd 写入数据,mFd 是什么呢?mFd 其实就是一对 socket 的其中一个,InputChannel 在构造的时候是一对,对应了一对 socket,一个代表 “client” 端,一个代表 “server” 端,“server” 端被注册到了 InputDispatcher,“client” 端返回给了 APP 进程,InputDispatcher 和 APP 进程都会对自己的 socket 一端进行监听,所以 APP 进程和 InputDispatcher 就这样完成了通信。

socket 通道是如何建立的呢? InputDispatcher 又是如何与前台的 window 通信的呢? 请参考 Android Input系统5 UI线程

三 总结

3.1 小结

InputDispatcher 对事件的分发流程是相当复杂的,我们仅对最简单的按键类型事件进行分析,同时分析过程省略了对细节的处理,我们做的是把握整体架构,现在就对这个过程做一个总结:

  • InputReader 线程,将驱动获取的原始输入事件封装为 NotifyKeyArgs 传递给了 InputDispatcher 线程,在放入 InputDispatcher 线程 mInboundQueue 队列之前会先将事件传递到 java 层的 PhoneWindowManager 中,没有被拦截的情况下才会将 NotifyKeyArgs 转换为 KeyEntry 并放入 mInboundQueue 队列,接着会唤醒 InputDispatcher 线程
  • InputDispatcher 线程启动后,如果没有事件待处理就会进入了 Looper 休眠状态,等待输入事件的发生,被唤醒之后调用函数 dispatchOnceInnerLocked 处理事件,此函数在一些条件判断之后,发现没有丢弃事件,则会进一步调用 dispatchKeyLocked 函数
  • dispatchKeyLocked 函数在分发之前,又会首先将按键事件传到 java 层的 PhoneWindowManager 的 interceptKeyBeforeDispatching 中,给个提前拦截的机会,如果没有被拦截则会通过 findFocusedWindowTargetsLocked 寻找目标焦点窗口
  • findFocusedWindowTargetsLocked 会从两个容器 mFocusedWindowHandlesByDisplay 和 mFocusedApplicationHandlesByDisplay 中获得当前的焦点窗口和焦点应用,并且会对可能出现 ANR 的情况进行 ANR timeout 即 ANR 发生窗口的标记
  • 如果 findFocusedWindowTargetsLocked 函数,返回结果为成功分发,则调用 dispatchEventLocked 函数继续分发输入事件,接着会将 KeyEntry 再转换为 DispatchEntry,并存入目标窗口连接 connection 的 outboundQueue 队列,然后调用 publishKeyEvent 继续分发
  • publishKeyEvent 函数中,构造了描述输入事件信息的 InputMessage,并通过 InputChannel 向 “server” 端 socket 写入数据以唤醒 APP 进程的 socket “client” 端,自此输入事件成功从 InputDispatcher 跨进程发送到了 APP 进程
  • 最后将 DispatchEntry 从目标窗口连接 connection 的 outboundQueue 队列中移除,并转移到目标窗口连接 connection 的 waitQueue 队列中

整个事件传输过程中,有三个重要队列:mInboundQueue,outboundQueue,waitQueue。
mInboundQueue 位于 InputDispatcher 线程,代表即将分发的输入事件,outboundQueue 位于目标窗口的 connection 中,代表即将要分发给目标窗口的输入事件,waitQueue 也位于目标窗口的 connection 中,表示等待目标窗口处理的输入事件。

3.2 流程图

在这里插入图片描述

3.3 核心方法

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值