Jimmy Chen

A Programmer

(转载)Input系统: 分发策略及其应用

Input系统: 按键事件分发 从整体上描绘了通用的事件分发过程,其中有两个比较的环节,一个是截断策略,一个是分发策略。Input系统:截断策略的分析与应用 分析了截断策略及其应用,本文来分析分发策略及其应用。

在正式开始分析前,读者务必仔细地阅读 Input系统: 按键事件分发 ,了解截断策略和分发策略的执行时机。否则,阅读本文没有意义,反而是浪费时间。

分发策略原理

根据 Input系统: 按键事件分发 可知,分发策略发生在事件分发的过程中,并且发生在事件分发循环前,如下

如代码所示,事件在分发给窗口前,会先执行分发策略。而执行分发策略的方式是创建一个命令 CommandEntry,然后保存到命令队列中。当命令被执行的时候,会执行 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible() 函数。

那么,为何要执行分发策略呢?有如下两点原因

  • 截断事件,给系统一个优先处理事件的机会。
  • 实现组合按键功能。

例如,导航栏上的 home, app switch 按键的功能就是在这里实现的,分发策略会截断它们。

Input系统:截断策略的分析与应用 可知,截断策略也可以截断事件,让系统优先处理事件。那么截断策略与分发策略有什么区别呢?

Input系统: 按键事件分发 可知,截断策略是处理一些系统级的事件,例如 power 键亮灭屏,这些事件的处理必须让用户感觉没有延时。假如 power 键的事件是在分发流程中处理的,那么必须等到 power 事件前面的所有事件都处理完毕,才能轮到 power 事件被处理,这就可能让用户感觉系统有点不流畅。

而分发策略处理一些优先级相对较低的系统事件,例如 home,app switch 事件。由于分发策略处于分发过程中,因此当一个 app 在发生 anr 期间,无论我们按多少次 home, app switch 按键,系统都会没有响应。

好,回归正题,如上面代码所示,为了执行分发策略,创建了一个命令,并保存到命令队列,然后就返回了。由 Input系统: 按键事件分发 可知,返回到了 InputDispatcher 的线程循环,如下

第1步,执行事件分发,不过事件为了执行分发策略,创建了一个命令并保存到命令队列中。

第2步,执行命令队列中的命令。根据前面创建命令时所分析的,会调用如下函数

果然,命令在执行时候,为事件 KeyEntry 查询了分发策略,并把分发策略的结果保存到 KeyEntry::interceptKeyResult。

注意,分发策略最终是由上层执行的,如果要截断事件,那么需要返回负值,如果不截断,返回0,如果暂时不知道如何处理事件,那么返回正值。

第2步执行完毕后,会立刻开始下一次的线程循环。如果要理解这一点,需要理解底层的消息机制,读者可能参考我写的 深入理解Native层的消息机制

在下一次线程循环时,执行第1步时,在事件分发给窗口前,需要根据分发策略的结果,对事件做进一步的处理,如下

对各种分发结果的处理如下

  1. INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER : 上层暂时不知道如何处理这个事件,所以告诉底层等一会再看看。底层收到这个结果,会让线程休眠指定时间。当时间到了后,会把重置分发策略结果为 INTERCEPT_KEY_RESULT_UNKNOWN,然后再次查询分发策略,此时分发策略会给出一个明确的结果,到底是截断还是不截断。
  2. INTERCEPT_KEY_RESULT_SKIP :上层截断了这个事件,因此让底层跳过这个事件,也就是不丢弃这个事件。
  3. INTERCEPT_KEY_RESULT_CONTINUE : 源码中没有明确处理这个结果,很简单嘛,那就是继续后面的事件分发流程。

那么,什么时候上层不知道如何处理一个事件呢?这是为了实现组合键的功能。

当第一个按键按下时,分发策略不知道用户到底会不会按下第二个按键,因此它会告诉底层再等等吧,底层因此休眠了。

如果在底层休眠期间,如果用户按下了第二个按键,那么成功触发组合键的功能,当底层醒来时,再次为第一个按键的事件查询分发策略,此时分发策略知道第一个按键的事件已经触发了组合键功能,因此告诉底层,第一个按键事件截断了,也就是被上层处理了,那么底层就不会分发这第一个按键的事件。

如果在底层休眠期间,如果没有用户按下了第二个按键。当底层醒来时,再次为第一个按键的事件查询分发策略,此时分发策略知道第一个按键事件没有触发组合键的功能,因此告诉底层这个事件不截断,继续分发处理吧。

下面以一个具体的组合键以例,来理解分发策略,因此读者务必仔细理解上面所分析的。

分发策略的应用 – 组合键

以手机上最常见的截断组合键为例,也就是 电源键 + 音量下键,来理解分发策略。但是,请读者务必,先仔细理解上面所分析的。

组合键的功能是由 KeyCombinationManager 管理,它在 PhoneWindowManager 的初始化如下

很简单,创建一个规则用于实现截屏,并保存到了 KeyCombinationManager#mRules 中。

当按下电源键,首先会经过截断策略处理,注意不是分发策略

第2步,截断策略会截断电源按键事件。

第1步,截断策略处理按键手势,这其中就包括组合键

现在来看下 KeyCombinationManager 如何处理截屏功能的第一个按键事件,也就是电源事件

KeyCombinationManager 处理组合键的第一个按键事件很简单,保存了按键按下的时间,并找到与这个按键相关的规则并保存。

由于电源按键事件被截断,当执行到分发策略时,如下

被截断策略截断的事件,不会经过分发策略的处理,并且直接被丢弃。这就是窗口为何收不到 power 按键事件的根本原因。

截断的第一个事件,电源事件,已经分析完毕。现在假设用户在很短的时间内,按键下了音量下键。经过截断策略时,仍然首先经过手势处理,此时 KeyCombinationManager 处理第二个按键的过程如下

根据代码可知,只有组合键的第二个按键在规定的时间内按下(150ms),才能触发规则。对于 电源键 + 音量下键,就是触发截屏。

截断策略在处理按键手势时,现在已经触发截屏,那么它是否截断音量下键呢?如果音量下键不用来挂断电话,那就不截断,这段代码请读者自行分析。

我们假设音量下键没有被截断策略截断,那么当它经过分发策略时,如何处理呢?如下

音量下键事件要执行分发策略,分发策略最终由上层的 PhoneWindowManager 实现,如下

由于已经触发了截屏功能,因此分发策略对音量下键的处理结果是 -1,也就是截断它。

底层收到这个截断信息时,就会丢弃音量下键这个事件,如下

由于音量下键事件被丢弃,因此窗口也收不到这个事件。其实,组合键功能只要触发,两个按键事件,窗口都收不到。

截屏功能不是只能通过 电源键 + 音量下键 触发,还可以通过 音量下键 + 电源键触发,但是分析过程却和上面不一样。如果音量下键先按,那么分发策略会返回一个稍后再试的结果,如果读者有兴趣,可以自行分析。

结束

通过学习本文,我们要达到学以致用的目的,其实最主要的,就是要学会如何自定义组合键。对于硬件上新增的按键事件,如果要截断,可以在截断策略,也可以在分发策略,根据自己所认为的重要性级别来决定。

写文章真是一个体力活,我一个星期发两篇文章,更是破天荒。另外,本文值反复研究,专家建议点赞,收藏,加关注,有事留言,溜了,溜了~

本文转自 https://juejin.cn/post/7195577258906878007,如有侵权,请联系删除。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注