上一篇《Android accessibilityService拦截不到VR眼镜BACK键分析》我们拦截VR返回键出现了问题,这一篇我们从源码中进行分析。
《Android 源码分析鼠标事件传递》介绍了鼠标事件从底层到View的传递过程,那么我们直接从View的源码中分析
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; }从底层传上来的keyEvent最终会被传递到view中,有keyListener的会先于onKeyDown调用,之后会调用到event.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; }KeyEvent的dispatch方法最后还是会回调到callback,也就是View中,我们继续看View的OnKeyDown和onKeyUp。 public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { return true; } // Long clickable items don't necessarily have to be clickable. if (((mViewFlags & CLICKABLE) == CLICKABLE || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && (event.getRepeatCount() == 0)) { // 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; setPressed(true, x, y); checkForLongClick(0, x, y); return true; } } 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(); return performClick(); } } } return false; }在处理onKeyUp的时候,执行performClick。
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }在performClick时就发送了AccessibilityEvent事件,这样AccessibilityService就收到了。但是实际的情况是并没有收到,KeyEvent在onKeyDown一开始就不处理,因为不是ConfirmKey,什么是ConfirmKey,比如说键盘回车KEYCODE_ENTER,具体可以看KeyEvent源码,我们回到key事件源头DecorView。 public boolean dispatchKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); final int action = event.getAction(); final boolean isDown = action == KeyEvent.ACTION_DOWN; if (isDown && (event.getRepeatCount() == 0)) { // First handle chording of panel key: if a panel key is held // but not released, try to execute a shortcut in it. if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { boolean handled = dispatchKeyShortcutEvent(event); if (handled) { return true; } } // If a panel is open, perform a shortcut on it without the // chorded panel key if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { return true; } } } if (!mWindow.isDestroyed()) { final Window.Callback cb = mWindow.getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { return true; } } return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); }mFeatureId < 0意味着这个window是Activity或者Dialog,先由cb处理,到Activity中
Window win = getWindow(); if (win.superDispatchKeyEvent(event)) { return true; }然后调用PhoneWindow的superDispatchKeyEvent
public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); } 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); }我们继续看ViewGroup的dispatchKeyEvent 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; }ViewGroup中的子View如果有焦点的,比如EditText,就会处理这个Key事件。我们修改一下demo。 @Override protected void onResume() { EditText hello = (EditText) findViewById(R.id.hello); hello.setClickable(true); hello.setFocusable(true); hello.requestFocus(); ((ViewGroup)hello.getParent()).setClickable(true); hello.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { Log.i(TAG, "View onKey:"+keyCode); if(keyCode == KeyEvent.KEYCODE_BACK){ Log.i(TAG, "View onKey KEYCODE_BACK:"); } return false; } }); ((ViewGroup)hello.getParent()).setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { Log.i(TAG, "ViewGroup onKey:"+keyCode); if(keyCode == KeyEvent.KEYCODE_BACK){ Log.i(TAG, "ViewGroup onKey KEYCODE_BACK:"); } return false; } }); super.onResume(); }02-28 11:48:09.906 24204-24204/? I/key: View onKey:402-28 11:48:09.906 24204-24204/? I/key: View onKey KEYCODE_BACK:02-28 11:48:09.906 24204-24204/? I/key: main KEYCODE_BACK source:819402-28 11:48:10.106 24204-24204/? I/key: View onKey:402-28 11:48:10.106 24204-24204/? I/key: View onKey KEYCODE_BACK:测试结果是EditText和Activity层收到Key事件,虚拟按键BACK在EditText的keyListener和MainActivity的onKeyDown中收到了,虚拟按键的BACK键Accessibility收到了。但是VR眼镜的BACK键却始终都拦截不到,证明了虚拟按键BACK和VR眼镜的BACK键做了不同的处理。
新闻热点
疑难解答