Android TV按键传递机制
Activity#dispatchKeyEvent()
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
- 分析源码可知,事件传递的优先顺序如下:ActionBar——>Window——>event.dispatch,下面按照这个顺序依次分析.
一、ActionBar
-
keyCode如果是KEYCODE_MENU,且actionBar存在,则交给了actionBar处理:使用其onMenuKeyEvent方法,对菜单按键进行响应;
-
如果actionBar不消费,就交给Window处理。
二、Window
1.Window#superDispatchKeyEvent()
public abstract boolean superDispatchKeyEvent(KeyEvent event);
2.PhoneWindow ->Window#superDispatchKeyEvent()
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
3.DecorView#superDispatchKeyEvent()
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
return super.dispatchKeyEvent(event);
}
-
DecorView在keyCode == KECODE_BACK时,检查是否有Menu弹出,有则毁MENU,防止内存泄漏。(mPrimaryActionMode是一种菜单方式,这里使用它销毁Menu)。
-
不是BACK按键时,return super.dispatchKeyEvent,即交给父类处理,因为DecorView继承于FrameLayout,所以super.dispatchKeyEvent其实就是ViewGroup.dispatchKeyEvent,key事件在View层级中的传递最终在这里进行。
4.ViewGroup#dispatchKeyEvent()
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
- mInputEventConsistencyVerifier主要在debug的时候使用。
- 如果ViewGroup自身已经获得焦点,就调用super.dispatchKeyEvent,即View.dispatchKeyEvent。
- 如果ViewGroup没有获取到焦点,而是ViewGroup中的某一个child获得了焦点,则把事件交给这个child,即上述代码中的mFocused,将key事件传递给焦点子View处理,也是View.dispatchKeyEvent。
- 如果都没有获取到焦点,则直接return false,不进行消费。那么最终就会执行到Activity中的event.dispatch()方法。
总结一下就是,只有当ViewGroup或其child获取到焦点,才有机会对key事件进行消费。如果都没有获取焦点则交给Activity中的event.dispatch()进行消费。
5.View#dispatchKeyEvent()(终于不是@override了)
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
- 注释的意思是将key事件派发到焦点路径上的某一个View,这个焦点路径遵循View树结构自顶向下的规则,如果当前View获得焦点,事件就派发给它自己,否则继续在焦点路径上往下派发。该方法也会触发KeyListener。
- 如果View设置了KeyListener,且该View是启用状态,则将事件交给它设置的KeyListener处理。
- 如果KeyListener不消费,或者未设置KeyListener,则调用event.dispatch方法。这个方法和Activity中是同一个方法。
- 再来回忆一下Activity中是如何调用该方法的,
event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
KeyEvent.dispath的第一个参数this,其实就是View和Activity都实现的接口KeyEvent.Callback。 - 查阅代码看一下这个接口里的内容:
public interface Callback {
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
- KeyEvent的Callback接口主要包含onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiPle方法,这就是平时开发中,接收key事件常常复写的那个onKeyDown。
- 接下来我们在event.dispatch方法看一下
6.KeyEvent#dispatch()
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
case ACTION_UP:
if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ ": " + this);
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
if (code != KeyEvent.KEYCODE_UNKNOWN) {
mAction = ACTION_DOWN;
mRepeatCount = 0;
boolean handled = receiver.onKeyDown(code, this);
if (handled) {
mAction = ACTION_UP;
receiver.onKeyUp(code, this);
}
mAction = ACTION_MULTIPLE;
mRepeatCount = count;
return handled;
}
return false;
}
return false;
}
- dispatch方法中,根据KeyEvent.action进行判断:ACTION_DOWN调用receiver.onKeyDownACTION_UP调用receiver.onKeyUpACTION_MULTIPLE调用receiver.onKeyMultiple。
- 其中的receiver就是在View和Activity中传入的this,那么我们需要在View和Activity看这个receiver中的具体实现了
7.View#onKeyDown()
/**
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if (event.getRepeatCount() == 0) {
// Long clickable items don't necessarily have to be clickable.
final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
|| (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
// For the purposes of menu anchoring and drawable hotspots,
// key events are considered to be at the center of the view.
final float x = getWidth() / 2f;
final float y = getHeight() / 2f;
if (clickable) {
setPressed(true, x, y);
}
checkForLongClick(0, x, y);
return true;
}
}
}
return false;
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
setPressed(false);
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!event.isCanceled()) {
return performClick();
}
}
}
}
return false;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
return false;
}
- View的onKeyLongPress和onKeyMultiPe方法都默认返回了false。
- View的onKeyDown和onKeyUp方法中默认使用isConfirmKey进行判断,查看上述源码可知,isConfirmKey只在ENTER或PPAD_CENTER时返回true,即遥控器上的确认键或OK键,所以onKeyDown和onKeyUp在默认情况下只消费ENTER和CENTER这种确认类型的按键。
- 如果View的onKeyDown、onKeyUp等KeyEvent.Callback的实现方法都不消费,表示Activity.dispatchKeyEvent将事件派发给Window没有被消费,事件继续往下传递,到了Activity.dispatchKeyEvent方法中的第三部分——交给Activity中的event.dispatch处理。
三、Activity中的event.dispatch
终于从Window中出来了,这时候前面两个都没有对按键进行消费,交给Activity来处理。
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
Window w = getWindow();
if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
} else if (keyCode == KeyEvent.KEYCODE_TAB) {
// Don't consume TAB here since it's used for navigation. Arrow keys
// aren't considered "typing keys" so they already won't get consumed.
return false;
} else {
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
boolean clearSpannable = false;
boolean handled;
if ((event.getRepeatCount() != 0) || event.isSystem()) {
clearSpannable = true;
handled = false;
} else {
handled = TextKeyListener.getInstance().onKeyDown(
null, mDefaultKeySsb, keyCode, event);
if (handled && mDefaultKeySsb.length() > 0) {
// something useable has been typed - dispatch it now.
final String str = mDefaultKeySsb.toString();
clearSpannable = true;
switch (mDefaultKeyMode) {
case DEFAULT_KEYS_DIALER:
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case DEFAULT_KEYS_SEARCH_LOCAL:
startSearch(str, false, null, false);
break;
case DEFAULT_KEYS_SEARCH_GLOBAL:
startSearch(str, false, null, true);
break;
}
}
}
if (clearSpannable) {
mDefaultKeySsb.clear();
mDefaultKeySsb.clearSpans();
Selection.setSelection(mDefaultKeySsb,0);
}
return handled;
}
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}
/**
* Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
* the event).
*/
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
return false;
}
- 当一个键被按下,并且没有被活动中的任何View处理时才会调用其中的方法。例如,当光标在TextView内时按下键不会触发事件(除非是指向另一个对象),因为TextView会自己处理。一般用于获取焦点的view不想自己处理按键事件时调用。
- 在Activity的dispatchKeyEvent中使用时需注意注意return super.dispatchKeyEvent,正因为key事件从dispatchKeyEvent分发到onKeyDown、onKeyUp等是由envet.dispatch实现的,所以如果用户在Activity中复写了dispatchKeyEvent方法,如果返回true或false,key事件都不会到Activity的onKeyDown或onKeyUp中,只有返回super.dispatchKeyEvent,执行到了event.dispatch,key事件才能够派发到Activity的onKeyDown或onKeyUp中。
四、总结
Key事件在Window传递的流程
1、 如果是BACK按键,先交给MENU处理,
2、如果不是BACK按键或MENU未处理,交给ViewGroup传递给它的焦点View:焦点View可以是ViewGroup本身或ViewGroup的child,如果焦点View设置了KeyListener,传递给KeyListener,如果没有,按KeyEvent.action类型传递给对应的onKeyXXX方法
3、 如果事件传递到了onKeyDown都没被处理,代表Window传递到它的View各个层级都没有消费该事件,回溯到Activity.dispatchKeyEvent继续往下传递,即执行event.dispatch()方法。
Key事件在Activity中传递的流程
1、key事件首先交给了actionBar处理
2、如果actionBar未处理,则交给window处理,window直接交给了DecorView.superDispatchKeyEvent,之后再传给ViewGroup的dispatchKeyEvent(),将事件传递给了获取焦点的View。
4、如果焦点view未处理,继续传递给View中的KeyEvent.dispatch,如果还未处理最终KeyEvent.dispatch将事件交给Activity的onKeyDown、onKeyUp、onKeyMultiPe或onKeyLongPress处理。
5、如果Activity的onKeyDown、onKeyUp、onKeyMultiPe或onKeyLongPress都没处理,即Activity本身及其ContentView都没有消费该事件,最终该事件就交给PhoneWindow或ViewRootImpl自动处理:自动查找焦点、系统音量调节等。