原文链接: Android TV按键焦点原理浅谈
本篇主要阅读 Android 源码讲解 TV 的按键事件分发原理和焦点查找原理,源码基于 Android90 ,首先思考几个问题:
带着这些问题,我们一起来撸 Android 源码吧!了解了系统是如何处理的有便于我们解决 TV 上一些按键和焦点的问题。
首先我们看下按键事件的入口 ViewRootImpl 类中的 ViewPostImeInputStage 内部类:
可以看到注释1,2,3,4分别判断不同事件执行不同方法,本篇主要讨论的TV焦点事件,主要看下 processKeyEvent 方法:
可以看到在该方法中执行了 mViewdispatchKeyEvent 方法,这里的 View 其实是 DecorView ,接着看下该方法:
上面首先判断了如果是第一次按下则处理panel的快捷键,如果处理了则不往下走,否则继续判断当窗口未销毁且回调非空则回调处理,如果处理了则不往下走,否则让 PhoneWindow 对应的 onKeyDown , onKeyUp 方法来处理。
接下来我们按照这个派发顺序依次来看看相关方法的实现,这里先看看 Activity 的 dispatchKeyEvent 实现:
我们看第1点 superDispatchKeyEvent 方法,可以看到该方法为一个抽象方法,而它的实现是实现它的子类 PhoneWindow :
该方法又回调用 DecorView 中的 superDispatchKeyEvent 方法:
此时,再来看下 ViewGroup 的 dispatchKeyEvent 方法:
接着看下 View 的 dispatchKeyEvent 方法:
该方法主要是判断如果有给 View 设置 OnKeyListener 事件且 View 为可用状态,则优先处理监听事件,其次调用 KeyEvent 的 dispatch 方法,接下来我们看下该方法:
该方法主要处理了按下、d起事件,其中按下如果 mRepeatCount 重复次数大于0判断为长按,则执行长按事件。
我们继续看下 View 的 onKeyDown 方法:
按下事件判断了如果为确认相关的按键才到下一步处理,判断点击或长按条件满足,执行按下 View 正中心坐标,然后执行 checkForLongClick 检查长按方法,看下该方法如下:
我们经常会遇到电视按遥控器时长按会执行一次 onKeyDown 、 onKeyUp ,之后才是一直 onKeyDown ,松开后才执行 onKeyUp ,原因就在于这个检查长按方法是延迟的。 delayOffset 传进来的是0,所以延迟时间为 ViewConfigurationgetLongPressTimeout() ,即该类中定义的 DEFAULT_LONG_PRESS_TIMEOUT 常量。
同样的如果是触摸屏,可以看下 View 类中的 onTouchEvent 方法在按下 *** 作的时候会开启 CheckForTap 线程检查是否是长按,该线程同样是延迟的,时间为 ViewConfigurationgetTapTimeout() ,即该类中的 TAP_TIMEOUT 常量,知道了这个你就知道如果写脚本或插件模拟长按应该间隔多长时间了,是不是一下你的模拟长按插件速度又可以更加准确快速的实现了。
不同版本系统定义的延迟时间有可能不一样,比如Google API 28 的 DEFAULT_LONG_PRESS_TIMEOUT 是500, TAP_TIMEOUT 是100,而 API 30 的 DEFAULT_LONG_PRESS_TIMEOUT 是400, TAP_TIMEOUT 也是100。
接下来再看下 Activity 的 onKeyDown :
回到 Decorview 中的 dispatchKeyEvent 方法看看 PhoneWindow 的 onKeyDown 方法:
onKeyUp 方法也可以自己再看下,以上就是浅谈按键事件的分发流程了。
总结:
上面讲解了按键事件分发流程,当上面分发完所有都没消费的时候,就会继续走 ViewRootImpl 的焦点导航流程,接下来看下 performFocusNavigation 方法:
首先我们看 mViewfindFocus() ,该方法实际是调用了 ViewGroup 的 findFocus 方法:
该方法很简单,就是向下递归查找在当前页面已经获取焦点的 View ,继续看 focusedfocusSearch(direction) 调用了 View 的 focusSearch 方法:
该方法向上递归查找,调用 ViewGroup 的 focusSearch 方法:
如果是根命名空间,则调用 FocusFinder 的 findNextFocus 方法查找焦点,否则继续往上查找。继续看 FocusFinder 的 findNextFocus 方法:
可以看到该方法首先查找用户指定的下一个获取焦点的 view ,如果找到了直接返回该 view ,如果没找到继续下面先添加 effectiveRoot 下的所有 view 到 focusables 集合中去,然后调用 findNextFocus 方法查找系统可获取下一个焦点的最近 view 。
我们先看下 findNextUserSpecifiedFocus 方法的实现:
通过用户指定焦点方式不是本篇的重点,这里就不贴出内部细节源码了。该方法实际就是调用 View 的 findUserSetNextFocus 方法来查找用户设置的下一个可获取焦点的 view ,然后在 while 循环中判断如果找到的是可以获取焦点并且可见的并且不是 InTouchNode 模式,则返回该焦点,否则继续循环查找直到找了一个循环没有找到可以获取焦点的或者 userSetNextFocus 为 null 跳出循环返回 null 。
再来看下系统就近原则查找的 findNextFocus 方法:
该方法主要通过 findNextFocusInRelativeDirection 在相对方向上找下一个焦点,该方法内部逻辑比较简单,这里就不贴出来了,进去看下就知道其实就是先给 focusables 排序,然后从中找到 focused 在其中的后一个或前一个 view ,如果没找到并且 focusables 不为空则返回 focusables 的第一个。
接下来我们重点看下 findNextFocusInAbsoluteDirection 方法:
再看下 isBetterCandidate 方法,该方法很关键,内部包含一系列逻辑如何成为最佳候选者:
该方法英文注释很直观,就不中文翻译了,首先看下成为候选人的 isCandidate 方法:
该方法判断了目标Rect如果在源Rect的方向一侧且不在内部的话,则为候选者,如第一个 destRect 左侧应在 srcRect 左侧左边, destRect 右侧应在 srcRect 右侧左边,其他方向同理。
接下来看下 beamBeats 方法:
基于Android9x
Window和Session创建成功后,窗口的下一步流程为获取焦点
我们看下焦点获取过程,跟输入法相关的流程
两个Activity切换时,对应的状态变化过程为:
以下是Activity窗口初次获取焦点的流程
当两个activity 切换时,失去焦点的窗口调用过程如下:
对应的,获取焦点的额窗口的调用过程如下:
当B窗口的状态切换到RESUMED时,当窗口的focus可能变化时,会调用updateFocusedWindowLocked
在该方法中,判断,如果还没有执行startInputInner方法,则执行startInputInner方法,否则,直接执行startInputOrWindowGainedFocus方法
主要流程:
1:设置controlFlags的flag为CONTROL_WINDOW_FIRST
2:检查是否已经执行过startInputInner,没有的话执行startInputInner-->startInputOrWindowGainedFocus;否则,直接执行startInputOrWindowGainedFocus
两条路径,携带的startInputReason参数不一样
主要流程:
1:检查要启动和退出的ServedView是否为同一个,如果为同一个,则表示已经执行过startInputInner,则返回false,表示不再执行startInputInner
2:如果获取焦点的是EditorText,会创建跟IMS通信的mServedInputConnectionWrapper对象
主要流程:
1:创建EditorInfo对象tba,这个参数对TextView布局才有意义,它的初始化是在mServedView的onCreateInputConnection完成实例化的
2:根据EditorInfo创建一个InputConnection对象,输入法应用通过该对象,完成输入内容到输入框的传递;ACTIVITY获取焦点场景,该对象
为null,因为没有要输入的对象
startInputOrWindowGainedFocus携带的参数
startInputReason = 1
表示,该流程是窗口获取焦点过程
mClient
应用层创建的IInputMethodClient对象,为服务层提供应用层的各个回调方法
该方法跟应用进程首次创建时Session时,传递到IMMS的对象是同一个对象
windowGainingFocus:
应用层的ViewRootImpl$W对象
controlFlags |= CONTROL_START_INITIAL;
表示window窗口刚开始获取焦点
softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允许调整输入法窗口,避免被其他窗口遮挡
tba , EditorInfo对象
servedContext
null
missingMethodFlags
ic等于null的情况下,为0
当应用层传递的W对象windowToken不为null的时候,则创建windowGainedFocus对象,返回给app
结果返回后,会对IMM的对象进行赋值
如此,进入一个窗口,获取窗口焦点过程,窗口与输入法相关的流程,就结束了。
下一篇:输入法在输入框d出流程
Android输入法(3),d出流程
以上就是关于Android TV按键焦点原理浅谈全部的内容,包括:Android TV按键焦点原理浅谈、Android 输入法窗口焦点获取流程(2) ,输入法窗口和应用窗口绑定、等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)