Android 源码分析AccessibilityService拦截VR眼镜Key事件以及key事件在View体系的传递
2017-02-27 16:25
696 查看
上一篇《Android AccessibilityService拦截不到VR眼镜BACK键分析》我们拦截VR返回键出现了问题,这一篇我们从源码中进行分析。
《Android 源码分析鼠标事件传递》介绍了鼠标事件从底层到View的传递过程,那么我们直接从View的源码中分析
从底层传上来的keyEvent最终会被传递到view中,有keyListener的会先于onKeyDown调用,之后会调用到event.dispatch方法。
KeyEvent的dispatch方法最后还是会回调到callback,也就是View中,我们继续看View的OnKeyDown和onKeyUp。
在处理onKeyUp的时候,执行performClick。
在performClick时就发送了AccessibilityEvent事件,这样AccessibilityService就收到了。但是实际的情况是并没有收到,KeyEvent在onKeyDown一开始就不处理,因为不是ConfirmKey,什么是ConfirmKey,比如说键盘回车KEYCODE_ENTER,具体可以看KeyEvent源码,我们回到key事件源头DecorView。
mFeatureId < 0意味着这个window是Activity或者Dialog,先由cb处理,到Activity中
然后调用PhoneWindow的superDispatchKeyEvent
我们继续看ViewGroup的dispatchKeyEvent
ViewGroup中的子View如果有焦点的,比如EditText,就会处理这个Key事件。我们修改一下demo。
测试结果是EditText和Activity层收到Key事件,虚拟按键BACK在EditText的keyListener和MainActivity的onKeyDown中收到了,虚拟按键的BACK键Accessibility收到了。
但是VR眼镜的BACK键却始终都拦截不到,证明了虚拟按键BACK和VR眼镜的BACK键做了不同的处理。
AccessibilityService对于 Key事件的处理并不是走AccessibilityEvent这条路,AccessibilityEvent主要是跟View焦点,点击事件,窗口变化有关
《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:4 02-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:8194 02-28 11:48:10.106 24204-24204/? I/key: View onKey:4 02-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键做了不同的处理。
AccessibilityService对于 Key事件的处理并不是走AccessibilityEvent这条路,AccessibilityEvent主要是跟View焦点,点击事件,窗口变化有关
相关文章推荐
- Android View事件传递机制-源码分析
- Android View事件传递与源码分析
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
- Android View体系(五)从源码解析View的事件分发机制
- Android触摸屏ViewGroup事件派发机制详解与源码分析
- android 从源码分析ViewGroup事件分发
- android长按home键源码分析以及模拟长按home事件--弹出近期任务.
- Android事件传递机制以及ViewGroup的onInterceptTouchEvent的理解
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- Android应用开发原理之从View源码挖掘View中的事件传递机制
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- android源码分析之View的事件分发(上)
- Android事件传递机制 源码分析