本文主要是介绍Android8.0 按键事件处理流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- Android8.0 按键事件处理流程
- 输入事件认识
- 按键事件流入
- 按键事件派发
- 1. DecorView.dispatchKeyEvent方法
- 2. Activity的dispatchKeyEvent
- 3. ViewGroup的dispatchKeyEvent
- 4. View的dispatchKeyEvent
- 总结
Android8.0 按键事件处理流程
此处记录按键事件从Framework到应用层的传递流程。WMS中接收到消息后,会调用ViewRootImpl中的dispatchInputEvent方法,
附上核心流程图
输入事件认识
Android所有输入事件都会封装为InputEvent事件然后进行分发,InputEvent又分为两种类型,实体按键事件(KeyEvent),触摸事件(MotionEvent)。这些事件流入到上层之后才会进行分别进行处理。
下面源码分析
按键事件流入
InputEvent就包含了KeyEvent,接下来就看该输入事件如何传递和分别处理的,首先是ViewRootImpl的dispatchInputEvent方法
frameworks/base/core/java/android/view/ViewRootImpl.java
public void dispatchInputEvent(InputEvent event) {dispatchInputEvent(event, null);
}public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {SomeArgs args = SomeArgs.obtain();args.arg1 = event;args.arg2 = receiver; // 此处receiver为nullMessage msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);msg.setAsynchronous(true);//发送MSG_DISPATCH_INPUT_EVENT消息mHandler.sendMessage(msg);
}
mHandler是其内部类ViewRootHandler,接收到消息如下
final class ViewRootHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {...case MSG_DISPATCH_INPUT_EVENT: {SomeArgs args = (SomeArgs)msg.obj;InputEvent event = (InputEvent)args.arg1;InputEventReceiver receiver = (InputEventReceiver)args.arg2;enqueueInputEvent(event, receiver, 0, true);args.recycle();} break;}
}
走enqueueInputEvent方法
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {adjustInputEventForCompatibility(event);// 1. 将输入事件event封装为QueuedInputEventQueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);// Always enqueue the input event in order, regardless of its time stamp.// We do this because the application or the IME may inject key events// in response to touch events and we want to ensure that the injected keys// are processed in the order they were received and we cannot trust that// the time stamp of injected events are monotonic.QueuedInputEvent last = mPendingInputEventTail;if (last == null) {mPendingInputEventHead = q;mPendingInputEventTail = q;} else {// 2. 追加新事件到mPendingInputEventTail上,形成事件链表last.mNext = q;mPendingInputEventTail = q;}mPendingInputEventCount += 1;if (processImmediately) {// 3. 处理输入事件doProcessInputEvents();} else {scheduleProcessInputEvents(); }
}void doProcessInputEvents() {// Deliver all pending input events in the queue.while (mPendingInputEventHead != null) {QueuedInputEvent q = mPendingInputEventHead;mPendingInputEventHead = q.mNext;if (mPendingInputEventHead == null) {mPendingInputEventTail = null;}q.mNext = null;...// 4. 分发QueuedInputEvent队列中的所有事件deliverInputEvent(q); }...
}
InputEvent事件会形成一个事件链表,最后循环分发链表队列中的事件
private void deliverInputEvent(QueuedInputEvent q) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);}// 关于InputStage下面详解,它表示了输入事件的一个分发阶段,eg: ime之前处理,ime处理,ime之后处理等InputStage stage;if (q.shouldSendToSynthesizer()) {stage = mSyntheticInputStage;} else {// 是否跳过键盘消息(IME),如果true,返回EarlyPostImeInputStage对象,否则返回NativePreImeInputStage对象stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;}if (stage != null) {stage.deliver(q);} else {finishInputEvent(q);}
}
NativePreImeInputStage是在ViewRootImpl中的setView()创建,而setview方法是在完成view绘制时调用的。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;...mSyntheticInputStage = new SyntheticInputStage();InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,"aq:native-post-ime:" + counterSuffix);InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);InputStage imeStage = new ImeInputStage(earlyPostImeStage,"aq:ime:" + counterSuffix);InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,"aq:native-pre-ime:" + counterSuffix);mFirstInputStage = nativePreImeStage;mFirstPostImeInputStage = earlyPostImeStage;}}
}
先接着看deliverInputEvent方法中的stage.deliver(q);
abstract class InputStage {/*** Delivers an event to be processed.*/public final void deliver(QueuedInputEvent q) {/// M: [ANR] Add for monitoring stage status. {ViewDebugManager.getInstance().debugInputStageDeliverd(this,System.currentTimeMillis());/// }// 当前事件还没有处理,因此不包含FLAG_FINISHED标致if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {forward(q);// 一般不会丢弃输入事件} else if (shouldDropInputEvent(q)) {finish(q, false);} else {ViewDebugManager.getInstance().debugInputDispatchState(q.mEvent, this.toString());// 1. 执行apply方法,传入onProcess的返回结果,该方法由子类重写apply(q, onProcess(q));}}.../*** Applies a result code from {@link #onProcess} to the specified event.*/protected void apply(QueuedInputEvent q, int result) {if (result == FORWARD) {forward(q);} else if (result == FINISH_HANDLED) {finish(q, true);} else if (result == FINISH_NOT_HANDLED) {finish(q, false);} else {throw new IllegalArgumentException("Invalid result: " + result);}}/*** Called when an event is ready to be processed.* @return A result code indicating how the event was handled.*/protected int onProcess(QueuedInputEvent q) {return FORWARD;}...
}
上面提到的所有InputState都继承自InputStage
如果跳过IME消息,则inputStage为NativePreImeInputStage,其是由ViewPreImeInputStage,ImeInputStage,EarlyPostImeInputStage,NativePostImeInputStage,ViewPostImeInputStage,SyntheticInputStage作为嵌套参数构成的,所以调用NativePreImeInputStage的deliver(q),会依次调用到每个InputState的子类的onProcess()方法
这里先介绍下InputStage
InputStage
输入事件的传递过程如下,每个前面处理事件的阶段都有拦截传递的能力。
- NativePreImeInputStage 分发早于IME的InputEvent事件到NativeActivity中去处理, NativeActivity和普通acitivty的功能区别不大,只是很多代码都在native层去实现,这样执行效率更高,并且NativeActivity在游戏开发中很实用。 不支持触摸事件。
- ViewPreImeInputStage 分发早于IME的InputEvent到View框架处理,会调用acitivity的所有view的onkeyPreIme方法,这样就给View在输入法处理key事件之前先得到消息并处理的机会。 不支持触摸事件
- ImeInputStage 分发InputEvent到IME处理 ImeInputStage的onProcess方法会调用InputMethodManager的dispatchInputEvent方法处理消息。 不支持触摸事件。
- EarlyPostImeInputStage 输入法之后输入事件就会流到该阶段,此时 屏幕上有焦点的View会高亮显示,用来提示用户焦点所在。支持触摸事件。
- NativePostImeInputStage 分发InputEvent事件到NativeActivity,为了让IME处理完消息后能先于普通的Activity处理消息。此时支持触摸事件。
- ViewPostImeInputStage 分发InputEvent事件到View框架,支持触摸事件。
- SyntheticInputStage 未处理的InputEvent都会传到这个阶段,例如手机上的虚拟按键消息
我们所关心的View框架的输入事件,故着重分析ViewPostImeInputStage
/*** Delivers post-ime input events to the view hierarchy.*/
final class ViewPostImeInputStage extends InputStage {...@Overrideprotected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) {// 处理按键事件return processKeyEvent(q); } else {// else中处理触摸事件,触摸事件又会根据不同类型的触摸做不同的处理,例如鼠标触摸,轨迹球(Android很早的一种交互方式,现在没有手机用)触摸,一般的手指触摸final int source = q.mEvent.getSource();if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {return processPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {return processTrackballEvent(q);} else {return processGenericMotionEvent(q);}}}
}
此处我们分析按键事件
private int processKeyEvent(QueuedInputEvent q) {// 转为KeyEvent事件final KeyEvent event = (KeyEvent)q.mEvent; // Deliver the key to the view hierarchy.// 1. 先由DecorView进行按键事件派发if (mView.dispatchKeyEvent(event)) {return FINISH_HANDLED;}...int groupNavigationDirection = 0; // 根据TAB和SHIFT键的按下来判断焦点方向为向前还是向后if (event.getAction() == KeyEvent.ACTION_DOWN&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {groupNavigationDirection = View.FOCUS_FORWARD;} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {groupNavigationDirection = View.FOCUS_BACKWARD;}}// If a modifier is held, try to interpret the key as a shortcut.if (event.getAction() == KeyEvent.ACTION_DOWN&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())&& event.getRepeatCount() == 0&& !KeyEvent.isModifierKey(event.getKeyCode())&& groupNavigationDirection == 0) {// 交由DecorView处理快捷键分发if (mView.dispatchKeyShortcutEvent(event)) {return FINISH_HANDLED;}...}...// Handle automatic focus changes.if (event.getAction() == KeyEvent.ACTION_DOWN) {if (groupNavigationDirection != 0) {if (performKeyboardGroupNavigation(groupNavigationDirection)) {return FINISH_HANDLED;}} else {// 2. 处理键盘的上下左右的焦点查找if (performFocusNavigation(event)) {return FINISH_HANDLED;}}}return FORWARD;
}
注释1处,mView处理按键事件, mView具体指的是? 如果是Activity和Dialog,mView就是DecorView,是所有view的根;如果是Toast,mView是id为com.android.internal.R.id.message,这点在Toast.makeText方法中可以看出。此处只分析Activity。
本文主要分析View框架的按键事件派发
按键事件派发
1. DecorView.dispatchKeyEvent方法
DecorView.java
@Override
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 keyif ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {return true;}}}if (!mWindow.isDestroyed()) {// cb是Activiy或者Dialog,我们只分析Activity // mFeatureId在installDecor是构造的DecorView传入的为-1,故调用Activity的dispatchKeyEventfinal 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);
}
2. Activity的dispatchKeyEvent
Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {onUserInteraction();// Let action bars open menus in response to the menu key prioritized over// the window handling itfinal int keyCode = event.getKeyCode();// 如果按键是menu事件,则先回调Actionbar的onMenuKeyEvent()事件处理,如果返回没有处理才会继续往下走if (keyCode == KeyEvent.KEYCODE_MENU &&mActionBar != null && mActionBar.onMenuKeyEvent(event)) {return true;}Window win = getWindow();// 1. 调用Phonewindow的superDispatchKeyEvent,最终会调用到DecorView的dispatchKeyEvent方法中if (win.superDispatchKeyEvent(event)) {return true;}View decor = mDecor;if (decor == null) decor = win.getDecorView();// 2. 如果Phonewindow 分发后返回false,则交由KeyEvent派发事件,调用Activity的onKeyDown/Up()方法return event.dispatch(this, decor != null? decor.getKeyDispatcherState() : null, this);
}
继续分析注释1,看如何派发输入事件到ViewGroup
PhoneWindow.java
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {// mDecor是DecorViewreturn mDecor.superDispatchKeyEvent(event);
}
发现又进入到DecorView,但这次调用的方法是superDispatchKeyEvent
DecorView.java
public boolean superDispatchKeyEvent(KeyEvent event) {// Give priority to closing action modes if applicable.// 对BACK按键做处理,如果存在ActionMode则先退出ActionMode,ActionMode相当于一个临时的ActionBar,具体使用还是google吧,此处不是重点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;}}// 1. DecorView父类是FrameLayout,但其没有实现dispatchKeyEvent方法,故该处调用的是ViewGroup的方法return super.dispatchKeyEvent(event);
}
注释1会调用其父类ViewGroup的dispatchKeyEvent()方法
3. ViewGroup的dispatchKeyEvent
@Override
public boolean dispatchKeyEvent(KeyEvent event) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onKeyEvent(event, 1);}// 1. 如果viewgroup获得焦点且边界确定,则调用父view的也就是View的dispatchKeyEventif ((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) {// 2. 交由获取焦点的子view进行按键事件的派发if (mFocused.dispatchKeyEvent(event)) {return true;}}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);}return false;
}
注释2中mFocused存在于每个ViewGroup,其标识了ViewGroup的直接子View是否拥有或者包含焦点,通过mFocused.dispatchKeyEvent即可递归调用找到最终获取焦点的View,然后调用该View的dispatchKeyEvent()方法,如注释1.
4. View的dispatchKeyEvent
View.java
public boolean dispatchKeyEvent(KeyEvent event) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onKeyEvent(event, 0);}// Give any attached key listener a first crack at the event.//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;// 1. 当对view设置了OnKeyListener,且该view处于enabled状态,则调用OnKeyListener的onKey()方法if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {return true;}// 2. KeyEvent派发事件,receiver为view,会回调View的onKeyDown/Up()方法if (event.dispatch(this, mAttachInfo != null? mAttachInfo.mKeyDispatchState : null, this)) {return true;}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}return false;
}
注释1中对View是否设置了OnKeyListener做了判断,如果设置,则先回调onKey()方法
注释2,在onKey()返回false情况下会通过KeyEvent的dispatch方法调用View的onKeyDown/Up()方法。
KeyEvent的dispatch(),该方法在View#dispatchKeyEvent()方法中,如果onKey()返回false会调用,参数传入的是View;该方法也会在Activity#dispatchKeyEvent()方法中win.superDispatchKeyEvent()方法返回false后进行调用,只不过参数传入的是Activity,最终会调用Activity或者View的onKeyDown/Up()方法。
KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,Object target) {switch (mAction) {case ACTION_DOWN: {mFlags &= ~FLAG_START_TRACKING; // 1. 执行Activity或者View的onKeyDown()方法boolean res = receiver.onKeyDown(mKeyCode, this); if (state != null) {if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {state.startTracking(this, target);} else if (isLongPress() && state.isTracking(this)) {try {if (receiver.onKeyLongPress(mKeyCode, this)) {state.performedLongPress(this);res = true;}} catch (AbstractMethodError e) {}}}return res;}case ACTION_UP:if (state != null) {state.handleUpEvent(this);}// 2. 执行Activity或者View的onKeyUp()方法return receiver.onKeyUp(mKeyCode, this); ...}return false;
}
receiver可能是Activity对象,也可能是view对象,具体情况具体分析
View对象的onKeyDown(),onKeyUp()
public boolean onKeyDown(int keyCode, KeyEvent event) {// isConfirmKey中会对keycode判断是否是KEYCODE_DPAD_CENTER,KEYCODE_ENTER,KEYCODE_SPACE,KEYCODE_NUMPAD_ENTER几个表示确定的键,也就是可以触发点击作用的键if (KeyEvent.isConfirmKey(keyCode)) {// 如果view处于DISABLED状态,则直接返回trueif ((mViewFlags & ENABLED_MASK) == DISABLED) {return true;}if (event.getRepeatCount() == 0) {// Long clickable items don't necessarily have to be clickable.// View的CLICKABLE 和 LONG_CLICABLE是独立的,互不影响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) {// 设置按下状态,比如更换view颜色,切换图片等setPressed(true, x, y);}// 该方法做的事情是发送一个可以判断长按时间的演示runnable,时间一到则执行长按操作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();if (!event.isCanceled()) {// 回调OnClickListener的onClick()方法return performClick();}}}}return false;
}
如果View设置了onClickListener()方法,则按下enter键后抬起时会执行onClick()方法
如果View的onKeyDown/Up()也返回false,则dispatchKeyEvent()方法会一直返回至Activity中,交由其onKeyDown/Up()处理
Activity对象的onKeyDown(),onKeyUp()
public boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK) {if (getApplicationInfo().targetSdkVersion>= Build.VERSION_CODES.ECLAIR) {// >= android2.1则跟踪按键传递过程event.startTracking();} else {// android2.1之前按下返回键直接返回onBackPressed();}return true;}...return handled;}
}public boolean onKeyUp(int keyCode, KeyEvent event) {// 如果android版本>= 2.1 回调onBackPressed()方法退出Activityif (getApplicationInfo().targetSdkVersion>= Build.VERSION_CODES.ECLAIR) {if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()&& !event.isCanceled()) {onBackPressed();return true;}}return false;
}
如果Activity里面的任何view、布局都没有处理按键,就会传递到Activity的onKeyDown,onKeyUp。比如,当在EditText中输入文字时,Activity的onKeyDown,onKeyUp不会接收到按键事件,因为EditText有自己的处理按键事件的方法,如果此时把焦点从EditText移走,onKeyDown,onKeyUp就会接收到按键事件。
Activity中onKeyDown/Up()也不做处理返回false时,事件一路返回至DecorView的dispatchKeyEvent()方法中,此时继续调用PhoneWindow的onKeyDown/Up()方法
PhoneWindow的onKeyDown/Up()
PhoneWindow.java
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {final KeyEvent.DispatcherState dispatcher =mDecor != null ? mDecor.getKeyDispatcherState() : null;switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_MUTE: {// If we have a session send it the volume command, otherwise// use the suggested stream.if (mMediaController != null) {int direction = 0;switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:direction = AudioManager.ADJUST_RAISE;break;case KeyEvent.KEYCODE_VOLUME_DOWN:direction = AudioManager.ADJUST_LOWER;break;case KeyEvent.KEYCODE_VOLUME_MUTE:direction = AudioManager.ADJUST_TOGGLE_MUTE;break;}mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);} else {MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(event, mVolumeControlStreamType, false);}return true;}// These are all the recognized media key codes in// KeyEvent.isMediaKey()case KeyEvent.KEYCODE_MEDIA_PLAY:case KeyEvent.KEYCODE_MEDIA_PAUSE:case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:case KeyEvent.KEYCODE_MUTE:case KeyEvent.KEYCODE_HEADSETHOOK:case KeyEvent.KEYCODE_MEDIA_STOP:case KeyEvent.KEYCODE_MEDIA_NEXT:case KeyEvent.KEYCODE_MEDIA_PREVIOUS:case KeyEvent.KEYCODE_MEDIA_REWIND:case KeyEvent.KEYCODE_MEDIA_RECORD:case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {if (mMediaController != null) {if (mMediaController.dispatchMediaButtonEvent(event)) {return true;}}return false;}case KeyEvent.KEYCODE_MENU: {onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);return true;}case KeyEvent.KEYCODE_BACK: {if (event.getRepeatCount() > 0) break;if (featureId < 0) break;// Currently don't do anything with long press.if (dispatcher != null) {dispatcher.startTracking(event, this);}return true;}}return false;
}
onKeyDown/onKeyUp方法主要针对当前获得焦点的窗口对一些特殊按键进行处理,包括音量+/-,多媒体控制按键,MENU,BACK
注意:PhoneFallbackEventHandler中也是对特殊按键进行处理,但是那是针对所有所有的窗口,包括当前获得焦点的窗口,而PhoneWindow只针对当前获得焦点的窗口
总结
按键事件从Framework层到View框架整体流程如流程图,我们主要关心是Activity,ViewGroup和View的事件派发。
Activity可以通过dispatchKeyEvent()将KeyEvent派发给ViewGroup直到找到获取焦点的View(当然可能就是ViewGroup获取焦点),获取焦点的View先去判断OnKeyListener存在与否,存在回调onKey(),如果不存在或者返回false,则回调其onKeyDown/Up()方法,onClick()方法在onKeyUp()方法中会进行回调,此时如果还是返回false,则Activity中的onKeyDown/Up()方法得以调用,最后没有处理则交给PhoneWindow的onKeyDown/Up()。我们也可以通过重写对应方法来达到事件消费,也就是不继续走剩余事件传递流程。
这篇关于Android8.0 按键事件处理流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!