android recent key长按事件弹起触发最近列表故障分析

天马行空的脑际回路,是否可以追溯?

代码阅读,请到此处http://androidxref.com 查看原生代码

问题描述
[Dialer]it will appear different behaviors after long press the menu to exit split screen 

 
操作步骤

1.Enter dialer
2.there is no recent items
3.long press the recent key to enter the split screen,then long press the recent again to exit the split,it will appear different behavior—-KO
Note:没有其他进程运行,进入dialer,长按menu键成功切换到多窗体,然后再长按menu键退出多窗体,有时会进入到recent列表
EXPECTED BEHAVIOUR:
long press the recent key,it will exit the split screen and enter dialer full screen

环境描述
 android7.0.1
 屏幕分辨率 720*1280
 手机:eng版本

分析
 逐步剖析代码,探寻根本原因

01
   套路,使用hierarchyviewer 工具,去找下虚拟按键三个按钮的元素信息,属于哪个类。

展看NavigationBar,查看界面信息:

找到细枝末节,发现每个元素,属于KeyButtonView.java

于是我们快马加鞭,来项目里面查找下

这里我们看到了代码属于packages\systemui下面,于是我们可以得出一个结论,三个虚拟按键的处理属于SystemUI进程,于是乎,我们调试SystemUI。
02
缓下,我们先要去看KeyButtonView.java是个什么内容。
  
主要看几个信息:
继承谁
构造方法
public方法
Override方法

此处为:继承自ImageView(于是我们知道它是类似ImageView的啦,那么我们基本可以从ImageView猜出KeyButtonView至少能做什么,至少ImageView一般都是要显示图的啦)基础的长按,短按消息都是可以支持的了。

构造方法


从此处得到信息:由自定义style,有关键的三个属性,我们此处关心前两个,看注释

keyCode 代表了键值,我们知道虚拟按键就是在模拟实体按键,因此也是需要键值。具体键值对应表,查看KeyEvent.java即可。
keyRepeat 是否可以多次响应,其实就是是否支持长按了。
构造方法一般主要是看下构造的时候都做了哪些事情,可以从变量初始化上做个了解。

public方法

主要就是看下它对外给出了哪些方法,可以让我们了解它能对外做出什么反应。

playSoundEffect 播放声音效果,主要就是按键时有个反馈
sendEvent 发送事件,这个关键方法,模拟了发送按键的动作
主要按键参数为,什么时间,按下还是弹起,什么按键,是否长按
onTouchEvent
关键的方法,重写了父类的方法。

我们需要慢慢来看这里的逻辑:

主要ontouch的事件为
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_CANCEL
MotionEvent.ACTION_UP

事件经过如下:
按下(ACTION_DOWN)后开始计时,如果一段时间ViewConfiguration.getLongPressTimeout()后,没有释放(ACTION_UP)
说明用户想长按,于是我们的postDelayed扔出了一个Runnable来进行长按处理。如果在ViewConfiguration.getLongPressTimeout()之内,用户释放(ACTION_UP)了,那就是个短按事件了。

我们先看下短按事件,在按下的时候,判断是否有键值mCode!=0,如果是,模拟按键,发送一个按键。
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
在我们释放的时候,doIt判断是否按下了,并且不是长按。

首先判断mCode!=0时,如果doIt是,代表我们需要处理这个短按弹起事件,如果短按弹起,sendEvent(KeyEvent.ACTION_UP, 0);
否则,发送sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
如果mCode==0时,如果doIt为真(代表我们是个短按弹起事件),调用了onclick方法。
我们再来看长按事件,在按下的时候,判断是否有键值mCode!=0,如果是,模拟按键,发送一个按键。
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); 同时启动了一个postDelay消息,如果时间到,系统会调用
mCheckLongPress运行起来,(我们要看长按,此时可以假设我们按下一直没有释放,时间到后触发此Runnable)
我们一睹mCheckLongPress芳容

如果按下了,长按了,调用performLongClick 触发onlongclick消息,否则如果支持长按,我们发送长按事件。
弹起时,在我们释放的时候,doIt判断是否按下了,并且不是长按。
首先判断mCode!=0时,如果doIt否,代表我们此时是长按弹起,如果
mCode有值,我们触发sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);(我们的问题点就在这里)

Override方法

主要是复写父类方法,需要自己处理一些变量,对于系统改变,做出正确的相应。
performAccessibilityActionInternal 这个是辅助功能,模拟按键动作。

03
好了,扯了这么多,我们再次回到出发点。我们看了这个KeyButtonView.java源码,再次需要回到使用它的地方,通过我们之前的搜索,以及视图信息,可以找到使用它的xml布局文件

我们可以看到它们的父类View是NavigationBarInflaterView.java,此处展开去讲,就有些说不完了。
我们说下NavigationBarInflaterView.java主要干了什么,主要完成虚拟按键view的加载,构造,初始化。
简单走一遍流程




它就是初始化view,做了各统一管理

我们加快些步伐,可以从视图看到最终为NavigationBarView.java–>NavigationBarInflaterView.java
于是我们再看下这个NavigationBarView.java

这里发现它在初始化里面,给back,home,recent按键附上了图片。
除此之外,没发现别的。于是我们需要项目去找,谁使用了NavigationBarView.java
主要在找什么呢?

我删掉了一部分,这里想强调的是,我们知道是哪个View了,想找它在哪里用,代码肯定是会有的,xml里面一般都会是在某个布局文件里面了。我们使用此方法,可以不用断点的方式,就可以将我们的目标缩进,能够追到真正需要的代码。
如上,我们被带到了PhoneStatusBar.java
04

神奇的PhoneStatusBar.java到来,我们看到了接近真相的地方(引用虚拟按键的布局地方)

好了,我们不进行更详细的追踪了,打住在这里。

inflateNavigationBarView 将NavigationBarView布局加载进来,于是我们可以去本文件搜索,哪里用了mNavigationBarView变量
满世界都是,我们找啊找,看到了这里:

到达目的地,我们找到了是如何将虚拟按键加入布局的了,也知道怎么显示出来了(mWindowManager.addView),如此我们明白了,虚拟按键通过NavigationBarView.java包裹了一个NavigationBarInflaterView.java  (navigation_bar.xml内容)实现,在PhoneStatusBar.java里面进行了加载,加载在了sysetmUI进程。
05

绕了一圈,主要就是带着走走流程,讲下一些在大脑的过程。我们现在回到我们的问题。

我们问题描述为:进入dialer,长按menu键成功切换到多窗体,然后再长按menu键退出多窗体,有时会进入到recent列表

主要关注点:view的设置回调函数,此处为KeyButtonView.java ,还有此处特殊,因为是自定义view(KeyButtonView),于是复写的方法onTouchEvent也变成了关注点。

于是我们梳理如下:

在phonestatusBar.java里面,给recentsButton注册了点击事件,touch事件(此处可忽略这个,主要是为了预加载最近列表做的准备动作,于本文分析无关)
长按事件。
这里有人有疑问了,我们看到的是个ButtonDispatcher 啊,哪里来的KeyButtonView,于是我们进入实现看下

NavigationBarView的构造里面,它的子view是NavigationBarInflaterView,它里面完成了初始化动作,具体为

inflateButton方法里面的

继续跟进:

所有流程扫过,我们再次整理
recent虚拟按键注册了
点击事件
touch事件(可以忽略)
长按事件
本身(keyButtonView.java)的onTouchEvent方法。
于是我们需要看,此时如果我们长按recent按键,弹起来的时候流程啦。
我们再次回到keyButtonView.java的onTouchEvent方法,来看

此处的信息为:
mCode=187(KEY_SERACH 也就是这里定义出来的recent的键值,具体在KeyEvent.java可以找到)
doIt 不为真
于是我们走到了流程
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
此时系统发出了一个按键弹起事件,此事件导致了进入了recent列表。(有时不会的原因是长按在触发分屏的时候就去释放,此时消息会被冲掉,因为随后的分屏逻辑会覆盖掉之前的recent列表)
此时我们要找的便是,sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);到底做了什么。
通过之前的讲解,我们知道sendEvent 目标就是想向系统扔出一个按键(此时为keycode= 187 action=ACTION_UP flag=KeyEvent.FLAG_CANCELED)
06

我们此时,进入一个系统处理key值的地方
PhoneWindowManager.java 的 interceptKeyBeforeDispatching方法
我们recent键值为KEYCODE_APP_SWITCH
我们看下case项:

这里为:down为按键按下 repeatCount 为是否是长按 keyguardOn 为是否锁屏下。这里我们看到,在我们弹起的时候,触发了toggleRecentApps 进入最近列表,引出此故障。

如果要修复,有两个思路:

A在此处对于弹起事件,加入一个处理,就是判断下是否为KeyEvent.FLAG_CANCELED,如果是,不做响应即可。
B在recent 的 KeyButtonView.java里面,up有段代码,判断了是否有mCode,如果有,则会在长按后在释放按键时触发send,而如果没有mCode,则没有动作,因此我们可以修改此处的code值。

结论:
系统设计时,对于是否有code的虚拟按键,定义了两组逻辑,引出此问题。

修复
我们采用B方案处理。
我们来到NavigationBarInflaterView.java 里面(加载了各个button虚拟按键),对于RECENT,使用的layout为:recent_apps.xml


修改此处的
systemui:keyCode=”187” 为
systemui:keyCode=”0”

验证OK,收工。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员入门进阶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值