设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间07 现在时间07 缓存数据 没有什么事情是简单轻松的,唯有不断努力,才能更加顺利。

没有什么事情是简单轻松的,唯有不断努力,才能更加顺利。

查看: 1035|回复: 0

Input系统截断策略的分析与应用详解

[复制链接]

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:22
  • 打卡月天数:0
  • 打卡总奖励:250
  • 最近打卡:2025-04-09 23:11:29
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
396
主题
364
精华
0
金钱
1425
积分
806
注册时间
2023-2-10
最后登录
2025-4-9

发表于 2023-3-2 10:55:44 来自手机 | 显示全部楼层 |阅读模式
引言

上一篇文章 Input系统: 按键事件分发 分析了按键事件的分发过程,虽然分析的对象只是按键事件,但是也从整体上,描绘了事件分发的过程。其中比较有意思的一环是事件截断策略,本文就来分析它的原理以及应用。
其实这篇文章早已经写好,这几天在完善细节的时候,我突然发现了源码中的一个 bug,这让我开始对自己的分析产生质疑,最终我拿了一台公司的样机 debug,我发现这确实是源码的一个 bug。本文在分析的过程中,会逐步揭开这个 bug。

截断策略的原理

根据 Input系统: 按键事件分发 的分析,事件开始分发前,会执行截断策略,如下
  1. void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
  2.     // ...
  3.     uint32_t policyFlags = args->policyFlags;
  4.     int32_t flags = args->flags;
  5.     int32_t metaState = args->metaState;
  6.     // ...
  7.     // 根据事件参数创建 KeyEvent,截断策略需要使用它
  8.     KeyEvent event;
  9.     event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
  10.                      args->action, flags, keyCode, args->scanCode, metaState, repeatCount,
  11.                      args->downTime, args->eventTime);
  12.     android::base::Timer t;
  13.     // 1. 执行截断策略,执行的结果保存到参数 policyFlags
  14.     mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
  15.     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
  16.         ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
  17.               std::to_string(t.duration().count()).c_str());
  18.     }
  19.     bool needWake;
  20.     { // acquire lock
  21.         mLock.lock();
  22.         if (shouldSendKeyToInputFilterLocked(args)) {
  23.             // ...
  24.         }
  25.         // 2. 创建 KeyEntry , 并加入到 InputDispatcher 的收件箱中
  26.         std::unique_ptr<KeyEntry> newEntry =
  27.                 std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
  28.                                            args->displayId, policyFlags, args->action, flags,
  29.                                            keyCode, args->scanCode, metaState, repeatCount,
  30.                                            args->downTime);
  31.         needWake = enqueueInboundEventLocked(std::move(newEntry));
  32.         mLock.unlock();
  33.     } // release lock
  34.     // 3. 如果有必要,唤醒 InputDispatcher 线程
  35.     if (needWake) {
  36.         mLooper->wake();
  37.     }
  38. }
复制代码
根据 Input系统: InputManagerService的创建与启动 可知,底层的截断策略实现类是 NativeInputManager
  1. // com_android_server_input_InputManagerService.cpp
  2. void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
  3.         uint32_t& policyFlags) {
  4.     bool interactive = mInteractive.load();
  5.     if (interactive) {
  6.         policyFlags |= POLICY_FLAG_INTERACTIVE;
  7.     }
  8.     // 来自输入设备的按键事件是受信任的
  9.     // 拥有注入权限的app,注入的按键事件也是受信任的,例如 SystemUI 注入 HOME, BACK, RECENT 按键事件
  10.     if ((policyFlags & POLICY_FLAG_TRUSTED)) {
  11.         nsecs_t when = keyEvent->getEventTime();
  12.         JNIEnv* env = jniEnv();
  13.         // 包装成上层的 KeyEvent 对象
  14.         jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
  15.         jint wmActions;
  16.         if (keyEventObj) {
  17.             // 1. 调用上层 InputManagerService#interceptKeyBeforeQueueing() 来执行截断策略
  18.             wmActions = env->CallIntMethod(mServiceObj,
  19.                     gServiceClassInfo.interceptKeyBeforeQueueing,
  20.                     keyEventObj, policyFlags);
  21.             if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
  22.                 wmActions = 0;
  23.             }
  24.             android_view_KeyEvent_recycle(env, keyEventObj);
  25.             env->DeleteLocalRef(keyEventObj);
  26.         } else {
  27.             ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
  28.             wmActions = 0;
  29.         }
  30.         // 2. 处理截断策略的结果
  31.         // 实际上就是把上层截断策略的结果转化为底层的状态
  32.         handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
  33.     } else {
  34.         // ...
  35.     }
  36. }
  37. void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
  38.         uint32_t& policyFlags) {
  39.     // 其实就是根据截断策略的结果,决定是否在 policyFlags 添加 POLICY_FLAG_PASS_TO_USER 标志位
  40.     if (wmActions & WM_ACTION_PASS_TO_USER) {
  41.         policyFlags |= POLICY_FLAG_PASS_TO_USER;
  42.     } else {
  43. #if DEBUG_INPUT_DISPATCHER_POLICY
  44.         ALOGD("handleInterceptActions: Not passing key to user.");
  45. #endif
  46.     }
  47. }
复制代码
首先调用上层来执行截断策略,然后根据执行的结果,再决定是否在参数 policyFlags 添加 POLICY_FLAG_PASS_TO_USER 标志位。这个标志位,就决定了事件是否能传递给用户。
截断策略经过 InputManagerService,最终是由上层的 PhoneWindowManager 实现,如下
  1. // PhoneWindowManager.java
  2.     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
  3.         // ...
  4.         // Handle special keys.
  5.         switch (keyCode) {
  6.             // ...
  7.             case KeyEvent.KEYCODE_POWER: {
  8.                 // 返回的额结果去,去掉 ACTION_PASS_TO_USER 标志位
  9.                 result &= ~ACTION_PASS_TO_USER;
  10.                 isWakeKey = false; // wake-up will be handled separately
  11.                 if (down) {
  12.                     interceptPowerKeyDown(event, interactiveAndOn);
  13.                 } else {
  14.                     interceptPowerKeyUp(event, canceled);
  15.                 }
  16.                 break;
  17.             }
  18.             // ...
  19.         }
  20.         // ...
  21.         return result;
  22.     }
复制代码
这里以 power 按键为例,它的按键事件的截断策略的处理结果,是去掉了 ACTION_PASS_TO_USER 标志位,也即告诉底层不要把事件发送给用户,这就是为何窗口(例如 Activity)无法收到 power 按键事件的原因。
现在,如果项目的硬件上新增一个按键,并且不想这个按键事件被分发给用户,你会搞了吗?

截断策略的应用

根据 Input系统: 按键事件分发 可知,截断策略发生在事件分发之前,因此它能及时处理一些系统功能的事件,例如,power 按键亮/灭屏没有延时,挂断按键挂断电话也没有延时。
刚才,我们看到了截断策略的一个作用,阻塞事件发送给用户/窗口。然而,它还可以实现按键的手势,手势包括单击,多击,长按,组合键(例如截屏组合键)。
下面来分析截断策略是如何实现按键手势中的单击、多击、长按。至于组合键,由于涉及分发策略,留到下一篇文章分析。

初始化

按键的手势是用 SingleKeyGestureDetector 来管理的,它在 PhoneWindowManager 中的初始化如下
  1. // PhoneWindowManager.java
  2.     private void initSingleKeyGestureRules() {
  3.         mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
  4.         int powerKeyGestures = 0;
  5.         if (hasVeryLongPressOnPowerBehavior()) {
  6.             powerKeyGestures |= KEY_VERYLONGPRESS;
  7.         }
  8.         if (hasLongPressOnPowerBehavior()) {
  9.             powerKeyGestures |= KEY_LONGPRESS;
  10.         }
  11.         // 增加一个 power 按键手势的规则
  12.         mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures));
  13.         if (hasLongPressOnBackBehavior()) {
  14.             mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
  15.         }
  16.     }
复制代码
SingleKeyGestureDetector 根据配置,为 Power 键和 Back 键保存了规则(rule)。所谓的规则,就是如何实现单个按键的手势。
所有的规则的基类都是 SingleKeyGestureDetector.SingleKeyRule,它的使用方式用下面一段代码解释
  1. SingleKeyRule rule =
  2.     new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) {
  3.          int getMaxMultiPressCount() { // maximum multi press count. }
  4.          void onPress(long downTime) { // short press behavior. }
  5.          void onLongPress(long eventTime) { // long press behavior. }
  6.          void onVeryLongPress(long eventTime) { // very long press behavior. }
  7.          void onMultiPress(long downTime, int count) { // multi press behavior.  }
  8.      };
复制代码

  • getMaxMultiPressCount() 表示支持的按键的最大点击次数。如果返回1,表示只支持单击,如果返回3,表示支持双击和三击,and so on...
  • 单击按键会调用 onPress()
  • 多击按键会调用 onMultiPress(long downTime, int count),参数 count 表示多击的次数。
  • 长按按键会调用 onLongPress()
  • 长时间地长按按键,会调用 onVeryLongPress()

实现按键手势

截断策略在处理按键事件时,会处理按键手势,如下
  1. // PhoneWindowManager.java
  2.     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
  3.         // ...
  4.         // 一般来说,轨迹球设备产生的事件,会设置 KeyEvent.FLAG_FALLBACK
  5.         if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
  6.             // 处理按键手势
  7.             handleKeyGesture(event, interactiveAndOn);
  8.         }
  9.         // ...
  10.         return result;
  11.     }
  12.     private void handleKeyGesture(KeyEvent event, boolean interactive) {
  13.         // KeyCombinationManager 是用于实现组合按键功能,如果只按下单个按键,不会截断事件
  14.         if (mKeyCombinationManager.interceptKey(event, interactive)) {
  15.             // handled by combo keys manager.
  16.             mSingleKeyGestureDetector.reset();
  17.             return;
  18.         }
  19.         // GestureLauncherService 实现的双击打开 camera 功能
  20.         // 原理很简单,就是判断两次 power 键按下的时间间隔
  21.         if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
  22.             mPowerKeyHandled = handleCameraGesture(event, interactive);
  23.             if (mPowerKeyHandled) {
  24.                 // handled by camera gesture.
  25.                 mSingleKeyGestureDetector.reset();
  26.                 return;
  27.             }
  28.         }
  29.         // 实现按键手势
  30.         mSingleKeyGestureDetector.interceptKey(event, interactive);
  31.     }
复制代码
从这里可以看到,有三个类实现按键的手势,如下

  • KeyCombinationManager,它是用于实现组合按键的功能,例如,power 键 + 音量下键 实现的截屏功能。它的原理很简单,就是第一个按键按下后,在超时时间内等待第二个按键事件的到来。
  • GestureLauncherService,目前只实现了双击打开 Camera 功能,原理也很简单,当第一次按下 power 键,在规定时间内按下第二次 power 键,然后由 SystemUI 实现打开 Camera 功能。
  • SingleKeyGestureDetector,实现通用的按键的手势功能。
GestureLauncherService 在很多个 Android 版本中,都只实现了双击打开 Camera 的功能,它的功能明显与 SingleKeyGestureDetector 重合了。然而,更不幸的是,SingleKeyGestureDetector 实现的手势功能还有 bug,Google 的工程师是不是把这个按键手势功能给遗忘了?
KeyCombinationManager 会在下一篇文章中分析,GestureLauncherService 请大家自行分析,现在来看下 SingleKeyGestureDetector 是如何处理按键手势的
  1. // SingleKeyGestureDetector.java
  2.     void interceptKey(KeyEvent event, boolean interactive) {
  3.         if (event.getAction() == KeyEvent.ACTION_DOWN) {
  4.             if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
  5.                 // 记录按键按下时,是否是非交互状态
  6.                 // 一般来说,灭屏状态就是非交互状态
  7.                 mBeganFromNonInteractive = !interactive;
  8.             }
  9.             interceptKeyDown(event);
  10.         } else {
  11.             interceptKeyUp(event);
  12.         }
  13.     }
复制代码
SingleKeyGestureDetector 分别处理了按键的 DOWN 事件和 UP 事件,这两者合起来才实现了整个手势功能。
首先看下如何处理 DOWN 事件
  1. // SingleKeyGestureDetector.java
  2.     private void interceptKeyDown(KeyEvent event) {
  3.         final int keyCode = event.getKeyCode();
  4.         // 3. 收到同一个按键的长按事件,立即执行长按动作
  5.         if (mDownKeyCode == keyCode) {
  6.             if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0
  7.                     && mActiveRule.supportLongPress() && !mHandledByLongPress) {
  8.                 if (DEBUG) {
  9.                     Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode));
  10.                 }
  11.                 mHandledByLongPress = true;
  12.                 // 移除长按消息
  13.                 mHandler.removeMessages(MSG_KEY_LONG_PRESS);
  14.                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
  15.                 // 立即执行长按动作
  16.                 // 注意,是立即,因为系统已经表示这是一个长按动作
  17.                 mActiveRule.onLongPress(event.getEventTime());
  18.             }
  19.             return;
  20.         }
  21.         // 表示这里前一个按键按下还没有抬起前,又有另外一个按键按下
  22.         if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN
  23.                 || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) {
  24.             if (DEBUG) {
  25.                 Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode));
  26.             }
  27.             reset();
  28.         }
  29.         // 保存按下的按键 keycode
  30.         mDownKeyCode = keyCode;
  31.         // 1. 按下首次按下,寻找一个规则
  32.         if (mActiveRule == null) {
  33.             final int count = mRules.size();
  34.             for (int index = 0; index < count; index++) {
  35.                 final SingleKeyRule rule = mRules.get(index);
  36.                 // 找到为按键添加规则
  37.                 if (rule.shouldInterceptKey(keyCode)) {
  38.                     mActiveRule = rule;
  39.                     // 找到有效的 rule,就退出循环
  40.                     // 看来对于一个按键,只有最先添加的规则有效                    
  41.                     break;
  42.                 }
  43.             }
  44.         }
  45.         // 没有为按键事件找到一条规则,直接退出
  46.         if (mActiveRule == null) {
  47.             return;
  48.         }
  49.         final long eventTime = event.getEventTime();
  50.         // 2. 首次按下时,发送一个长按的延时消息,用于实现按键的长按功能
  51.         // mKeyPressCounter 记录的是按键按下的次数
  52.         if (mKeyPressCounter == 0) {
  53.             if (mActiveRule.supportLongPress()) {
  54.                 final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
  55.                         eventTime);
  56.                 msg.setAsynchronous(true);
  57.                 mHandler.sendMessageDelayed(msg, mLongPressTimeout);
  58.             }
  59.             if (mActiveRule.supportVeryLongPress()) {
  60.                 final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
  61.                         eventTime);
  62.                 msg.setAsynchronous(true);
  63.                 mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
  64.             }
  65.         }
  66.         // 4. 这里表示之前已经按键已经按下至少一次
  67.         else {
  68.             // 移除长按事件的延时消息
  69.             mHandler.removeMessages(MSG_KEY_LONG_PRESS);
  70.             mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
  71.             // 移除单击事件或多击事件的延时消息
  72.             mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
  73.             // Trigger multi press immediately when reach max count.( > 1)
  74.             // 达到最大点击次数,立即执行多击功能
  75.             // 注意,这段代码是一个 bug
  76.             if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) {
  77.                 if (DEBUG) {
  78.                     Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
  79.                             + " reach the max count " + mKeyPressCounter);
  80.                 }
  81.                 mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1);
  82.                 mKeyPressCounter = 0;
  83.             }
  84.         }
  85.     }
复制代码
SingleKeyGestureDetector 对按键 DOWN 事件的处理过程如下

  • 按键首次按下,为按键找到相应的规则,保存到 mActiveRule
  • 按键首次按下,会发送一个延时的长按消息,实现长按功能。当超时时,也就是按键按下没有抬起,并且系统也没有发送按键的长按事件,那么会执行 SingleKeyRule#onLongPress() 或/和 SingleKeyRule#onVeryLongPress()
  • 如果收到系统发送的按键的长按事件,那么移除长按消息,并立即SingleKeyRule#onLongPress()。为何要立即执行,而不是发送一个延时消息?因为系统已经表示这是一个长按事件,没有理由再使用一个延时来检测是否要触发长按。
  • 如果多次(至少超过1次)点击按键,那么移除长按、单击/多击消息,并在点击次数达到最大时,立即执行SingleKeyRule#onMultiPress()
注意,第4点中,当达到最大点击次数时,立即执行SingleKeyRule#onMultiPress(),并重置按键点击次数 mKeyPressCounter 为 0,这是一个 Bug,这段代码应该去掉。在后面的分析中,我将证明这会造成 bug。
SingleKeyGestureDetector 对按键按下事件的处理,确切来说只实现了长按的功能,而按键的单击功能以及多击功能,是在处理 UP 事件中实现的,如下
  1. // SingleKeyGestureDetector.java
  2.     private boolean interceptKeyUp(KeyEvent event) {
  3.         // 按键已抬起,就不应该触发长按事件,所以需要移除延时的长按消息
  4.         mHandler.removeMessages(MSG_KEY_LONG_PRESS);
  5.         mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
  6.         // 按键抬起,重置 mDownKeyCode
  7.         mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
  8.         // 没有有效规则,不处理按键的抬起事件
  9.         if (mActiveRule == null) {
  10.             return false;
  11.         }
  12.         // 如果已经触发长按,不处理按键的抬起事件
  13.         if (mHandledByLongPress) {
  14.             mHandledByLongPress = false;
  15.             mKeyPressCounter = 0;
  16.             return true;
  17.         }
  18.         final long downTime = event.getDownTime();
  19.         // 抬起按键的key code 要与规则的一样,否则无法触发规则
  20.         if (event.getKeyCode() == mActiveRule.mKeyCode) {
  21.             // 1. 规则只支持单击,那么发送消息,执行单击操作。
  22.             if (mActiveRule.getMaxMultiPressCount() == 1) {
  23.                 // 注意,第三个参数为 arg2,但并没有使用
  24.                 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
  25.                         1, downTime);
  26.                 msg.setAsynchronous(true);
  27.                 mHandler.sendMessage(msg);
  28.                 return true;
  29.             }
  30.             // 走到这里,表示规则支持多击功能,那么必须记录多击的次数,用于实现按键多击功能
  31.             mKeyPressCounter++;
  32.             // 2. 规则支持多击,发送一个延迟消息来实现单击或者多击功能
  33.             // 既然支持多击,那么肯定需要一个超时时间来检测是否有多击操作,所以这里要设置一个延时
  34.             // 注意,第三个参数为 arg2,但并没有使用
  35.             Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
  36.                     mKeyPressCounter, downTime);
  37.             msg.setAsynchronous(true);
  38.             mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
  39.             return true;
  40.         }
  41.         // 收到其他按键的 UP/CANCEL 事件,重置前一个按键的规则
  42.         reset();
  43.         return false;
  44.     }
复制代码
SingleKeyGestureDetector 处理 UP 事件分两种情况

  • 如果规则只支持单击,那么发送一个消息执行单击动作。这里我就有一个疑问了,为何不与前面一样,直接执行单击动作,反而还要多此一举地发送一个消息去执行单击动作?
  • 如果规则支持多击,那么首先把点击次数 mKeyPressCounter 加1,然后发送一个延时消息执行单击或者多击动作。为何要设置一个延时?对于单击操作,需要使用一个延时来检测没有再次点击的操作,对于多击操作,需要检测后面是否还有点击操作。
注意,以上两种情况下发送的消息,Message#arg2 的值其实是按键的点击次数。
现在来看下 MSG_KEY_DELAYED_PRESS 消息的处理流程。
  1. // SingleKeyGestureDetector.java
  2. private class KeyHandler extends Handler {
  3.     @Override
  4.     public void handleMessage(Message msg) {
  5.         if (mActiveRule == null) {
  6.             return;
  7.         }
  8.         final int keyCode = msg.arg1;
  9.         final long eventTime = (long) msg.obj;
  10.         switch(msg.what) {
  11.             // ...
  12.             case MSG_KEY_DELAYED_PRESS:
  13.                 // 虽然在发送消息的时候,使用了 Message#arg2 参数来表示按键的点击次数
  14.                 // 但是,这里仍然使用 mKeyPressCounter 来决定按键的点击次数
  15.                 if (mKeyPressCounter == 1) {
  16.                     // 当规则支持多击功能时,单击功能由这里实现
  17.                     mActiveRule.onPress(eventTime);
  18.                 } else {
  19.                     // 当规则只支持单击功能时,mKeyPressCounter 永远为 0,因此单击功能由这里实现
  20.                     // 当规则支持多击功能时,多击功能由这里实现
  21.                     mActiveRule.onMultiPress(eventTime, mKeyPressCounter);
  22.                 }
  23.                 reset();
  24.                 break;
  25.         }
  26.     }
  27. }
复制代码
这个消息的处理过程,从表面上看,如果是单击就执行 SingleKeyRule#onPress(),如果是多击就执行 SingleKeyRule#onMultiPress()
然而实际情况,并非如此。由于没有使用 Message#arg2,而是直接使用 mKeyPressCounter 作为按键点击次数,这就导致两个问题

  • 当规则只支持单击功能时,mKeyPressCounter 永远为0。于是单击按键时,调用 SingleKeyRule#onMultiPress(),而非 SingleKeyRule#onPress(),这岂不是笑话。
  • 当规则支持多击功能时,如果单击按键,会调用 SingleKeyRule#onPress(),如果多击按键,会调用 SingleKeyRule#onMultiPress() 这一切看起来没有问题,实则不然。对于多击操作,前面分析过,在处理 DOWN 事件时,当达到最大点击次数时,会调用SingleKeyRule#onMultiPress(),并把 mKeyPressCounter 重置为0。之后再处理 UP 事件时,mKeyPressCounter 加1后变为了1,然后发送消息去执行,最后,奇迹般地执行了一次 SingleKeyRule#onPress()。对于一个多击操作,居然执行了一次单击动作,这简直是国际笑话。
以上两点问题,我用样机进行验证过,确实存在。但是,系统很好地支持了双击 power 打开 Camera,以及单击 power 亮/灭屏。这又是怎么回事呢?

  • 双击 power 打开 Camera,是由 GestureLauncherService 实现的。
  • 系统默认配置的 power 规则,只支持单击。
既然 power 规则只支持单击,理论上应该调用 SingleKeyRule#onMultiPress(long downTime, int count),并且参数 count 为0,这岂不还是错的。确实如此,不过源码又巧合地用另外一个Bug避开了这一个Bug。接着往下看
首先看下 power 键的规则
  1. // PhoneWindowManager.java
  2.     private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
  3.         PowerKeyRule(int gestures) {
  4.             super(KEYCODE_POWER, gestures);
  5.         }
  6.         @Override
  7.         int getMaxMultiPressCount() {
  8.             // 默认配置返回1
  9.             return getMaxMultiPressPowerCount();
  10.         }
  11.         @Override
  12.         void onPress(long downTime) {
  13.             powerPress(downTime, 1 /*count*/,
  14.                     mSingleKeyGestureDetector.beganFromNonInteractive());
  15.         }
  16.         @Override
  17.         void onLongPress(long eventTime) {
  18.             if (mSingleKeyGestureDetector.beganFromNonInteractive()
  19.                     && !mSupportLongPressPowerWhenNonInteractive) {
  20.                 Slog.v(TAG, "Not support long press power when device is not interactive.");
  21.                 return;
  22.             }
  23.             powerLongPress(eventTime);
  24.         }
  25.         @Override
  26.         void onVeryLongPress(long eventTime) {
  27.             mActivityManagerInternal.prepareForPossibleShutdown();
  28.             powerVeryLongPress();
  29.         }
  30.         @Override
  31.         void onMultiPress(long downTime, int count) {
  32.             powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive());
  33.         }
  34.     }
复制代码
power 键规则,默认支持最大的点击数为1,这是在 config.xml 中进行配置的,这里不细讲。
power 键规则中,无论是单击还是多击,默认都调用同一个函数,如下
  1.     private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
  2.         if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
  3.             Slog.i(TAG, "Suppressed redundant power key press while "
  4.                     + "already in the process of turning the screen on.");
  5.             return;
  6.         }
  7.         final boolean interactive = Display.isOnState(mDefaultDisplay.getState());
  8.         Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
  9.                 + " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
  10.                 + " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior);
  11.         // 根据配置,决定power键的单击/多击行为
  12.         if (count == 2) {
  13.             powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
  14.         } else if (count == 3) {
  15.             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
  16.         }
  17.         // 注意,beganFromNonInteractive 表示是否在非交互状态下点击 power 键
  18.         // 这里判断条件的意思是,处于交互状态,并且不是非交互状态下点击power键
  19.         // 说简单点,这里只支持在亮屏状态下单击power键功能
  20.         else if (interactive && !beganFromNonInteractive) {
  21.             if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
  22.                 Slog.i(TAG, "Suppressing power key because the user is interacting with the "
  23.                         + "fingerprint sensor");
  24.                 return;
  25.             }
  26.             switch (mShortPressOnPowerBehavior) {
  27.                 case SHORT_PRESS_POWER_NOTHING:
  28.                     break;
  29.                 case SHORT_PRESS_POWER_GO_TO_SLEEP:
  30.                     // 灭屏,不过要先进入 doze 模式
  31.                     sleepDefaultDisplayFromPowerButton(eventTime, 0);
  32.                     break;
  33.                 case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
  34.                     // 跳过 doze 模式,直接进入 sleep 模式
  35.                     sleepDefaultDisplayFromPowerButton(eventTime,
  36.                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
  37.                     break;
  38.                 case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
  39.                     // 跳过 doze 模式,进入 sleep 模式,并返回 home
  40.                     if (sleepDefaultDisplayFromPowerButton(eventTime,
  41.                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) {
  42.                         launchHomeFromHotKey(DEFAULT_DISPLAY);
  43.                     }
  44.                     break;
  45.                 case SHORT_PRESS_POWER_GO_HOME:
  46.                     // 返回 home
  47.                     shortPressPowerGoHome();
  48.                     break;
  49.                 case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
  50.                     if (mDismissImeOnBackKeyPressed) {
  51.                         // 关闭输入法
  52.                         if (mInputMethodManagerInternal == null) {
  53.                             mInputMethodManagerInternal =
  54.                                     LocalServices.getService(InputMethodManagerInternal.class);
  55.                         }
  56.                         if (mInputMethodManagerInternal != null) {
  57.                             mInputMethodManagerInternal.hideCurrentInputMethod(
  58.                                     SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
  59.                         }
  60.                     } else {
  61.                         // 返回 home
  62.                         shortPressPowerGoHome();
  63.                     }
  64.                     break;
  65.                 }
  66.             }
  67.         }
  68.     }
复制代码
从整体看,如果参数 count 为2或者3,会执行多击的行为,否则,执行单击行为。
这个逻辑是不是有点奇怪呀,参数 count 不为2也不为3,难道就是一定为1吗?正是因为这个逻辑,才导致 count 为0时,也能执行单击动作,小朋友听了都直呼6。我想起了我一个前同事做的事,用一个 Bug 去解决另外一个 Bug。

power 键的亮屏与灭屏

好了,言归正传,power 键规则的单击行为,只包括了灭屏,并没有包含亮屏,这又是怎么回事呢?因为 power 键的亮屏,不是在规则中实现的,而是在截断策略中实现的,如下
  1.     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
  2.         // ...
  3.         if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
  4.             // 处理单个按键的手势
  5.             handleKeyGesture(event, interactiveAndOn);
  6.         }
  7.         // ...
  8.         switch (keyCode) {
  9.             // ...
  10.             case KeyEvent.KEYCODE_POWER: {
  11.                 // power 按键事件不分发给用户
  12.                 result &= ~ACTION_PASS_TO_USER;
  13.                 // 这里表示 power 键不是唤醒键,是不是很奇怪,因为系统默认绑定了 power 键亮屏功能
  14.                 isWakeKey = false; // wake-up will be handled separately
  15.                 if (down) {
  16.                     // power 亮屏在这里实现
  17.                     interceptPowerKeyDown(event, interactiveAndOn);
  18.                 } else {
  19.                     interceptPowerKeyUp(event, canceled);
  20.                 }
  21.                 break;
  22.             }
  23.             // ...
  24.         }
  25.         // ...
  26.         return result;
  27.     }
  28.     private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
  29.         // Hold a wake lock until the power key is released.
  30.         if (!mPowerKeyWakeLock.isHeld()) {
  31.             mPowerKeyWakeLock.acquire();
  32.         }
  33.         mWindowManagerFuncs.onPowerKeyDown(interactive);
  34.         // 设备处于响铃状态,就静音,处于通话状态,就挂电话
  35.         TelecomManager telecomManager = getTelecommService();
  36.         boolean hungUp = false;
  37.         if (telecomManager != null) {
  38.             if (telecomManager.isRinging()) {
  39.                 telecomManager.silenceRinger();
  40.             } else if ((mIncallPowerBehavior
  41.                     & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
  42.                     && telecomManager.isInCall() && interactive) {
  43.                 hungUp = telecomManager.endCall();
  44.             }
  45.         }
  46.         // 检测 PowerManagerService 是否正在使用 sensor 灭屏
  47.         final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
  48.         sendSystemKeyToStatusBarAsync(event.getKeyCode());
  49.         // mPowerKeyHandled 在长按,组合键,双击打开Camera情况下,会被设置为 true
  50.         mPowerKeyHandled = mPowerKeyHandled || hungUp
  51.                 || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
  52.         if (!mPowerKeyHandled) {
  53.             // power 事件没有被其它地方使用,那么在灭屏状态下执行亮屏
  54.             if (!interactive) {
  55.                 wakeUpFromPowerKey(event.getDownTime());
  56.             }
  57.         } else {
  58.             // power 事件被其它地方使用,那么重置规则
  59.             if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
  60.                 mSingleKeyGestureDetector.reset();
  61.             }
  62.         }
  63.     }
复制代码
截断策略处理 power 按键的 DOWN 事件过程如下

  • 如果 power 按键事件没有被其它地方使用,那么,在非交互状态下,一般指灭屏状态,会执行亮屏。
  • 如果 power 按键事件被其它地方使用,那么重置按键手势。
这里我又有一个疑问,为何要把 power 亮屏的代码的在这里单独处理?在创建规则时设置一个回调,是不是更好呢?

结束

我在支援公司的某个项目时,偶然发现有人在截断策略中,要实现一个新的双击power功能,以及添加三击power的功能,可谓是把源码修改得"鸡飞狗跳",我当时还嗤之以鼻,现在发现我错怪他了。但是呢,由于他不知道如何修复这个源码的 bug,所以他实现的过程还是非常丑陋。
最后,给看我文章的人一个小福利,你可以参考 Input系统: InputReader 处理按键事件 ,学会从底层映射一个按键到上层,然后根据本文,实现按键的手势,这绝壁是一个非常叼的事情。当然,如果手势中要包括多击功能,还得解决本文提出的 Bug,这个不难,小小思考下就可以了。
以上就是Input系统截断策略的分析与应用详解的详细内容,更多关于Input系统截断策略的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表