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,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值