💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] 本篇文章主要分析应用层在接收到按键事件后的分发流程,对于 Framework 层获得按键事件、分发传递给应用层等相关知识,在后期 Framework 层源码学习时再做分析。 项目中只要在 Activity 中重写 dispatchKeyEvent()方法,就可以进行事件处理和拦截了,那我们从 Activity 类的 dispatchKeyEvent()方法看起: # 分发顺序 在分发的任何一步有分发者进行消费,即返回 true 时,事件停止传递。 Activity 的分发 * Activity 接收到事件,为 Menu 键事件就直接消费掉,否则分发给 PhoneWindow,PhoneWindow 直接把事件传递给其 DecorView * DecorView 对 Back 键判断决定是否消费,不消费则分发给 ViewGroup * ViewGroup 不消费时,事件传递给 KeyEvent ViewGroup 的分发 * ViewGroup 有焦点且大小确定时,首先自己处理(调用其父类 View 的 dispatchKeyEvent 方法) * Viewgroup 的子 View 有焦点且大小确定时,会向下分发给子 View * 当子 View 也有子 View 时,会层层向下分发,直到 View View 的分发 * View 首先把事件分发给 OnKeyListener 监听器,监听器不消费时,事件传给 KeyEvent KeyEvent 的分发 * KeyEvent 回调 Activity 的 onKeyDown、onKeyUp、onKeyLongPress 方法 * KeyEvent 回调 View 的 onKeyDown、onKeyUp、onKeyLongPress 方法 事件的回传 * 当 Activity 或 View 的 onKeyDown()、onKeyUp()方法也没有消费事件时,事件开始回传,首先给 KeyEvent 的 dispatch()方法 * 依次按 KeyEvent -> Activity -> DecorView -> PhoneWindow 或 KeyEvent -> View -> ViewGroup -> Activity -> DecorView -> PhoneWindow 的方向进行回传,此处与触摸事件的回传方式相似 # 源码分析 ## Activity 分发 ```java public boolean dispatchKeyEvent(KeyEvent event) { // 用户交互时会回调 onUserInteraction(); // 当为菜单键时,ActionBar 打开菜单并消费事件 final int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { return true; } else if (event.isCtrlPressed() && event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') { // Capture the Control-< and send focus to the ActionBar final int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { final ActionBar actionBar = getActionBar(); if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) { mEatKeyUpEvent = true; return true; } } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) { mEatKeyUpEvent = false; return true; } } // 获得当前 Activity 对应的 Window 对象 Window win = getWindow(); // 事件分发给 PhoneWindow 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); } ``` 可以看到,在具体事件处理前,首先会回调 onUserInteraction() 方法。注意,在按键按下、抬起时都会回调改方法,但触摸时,只有触摸按下时会回调,移动、抬起时不会回调。 接下来判断是不是 Menu 按键,是则进行事件处理。不是则把事件传递给 Activity 对应的 Window 对象,即 PhoneWindow,调用其 superDispatchKeyEvent(event) 方法,来看下这个方法: ## PhoneWindow 的分发 ```java public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); } ``` 可以看到,事件传递给了 PhoneWindow 的 DecorView 对象。来看看 DecorView 对象的处理: ```java public boolean superDispatchKeyEvent(KeyEvent event) { // 先判断是不是 Back 按键事件 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { final int action = event.getAction(); // Back cancels action modes first. // 消费事件 if (mActionMode != null) { if (action == KeyEvent.ACTION_UP) { mActionMode.finish(); } return true; } } // 传递事件给 ViewGroup return super.dispatchKeyEvent(event); } ``` 接下来看看 ViewGroup 的分发: ## ViewGroup 的分发 ```java public boolean dispatchKeyEvent(KeyEvent event) { // 进行一致性检查 if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 1); } // 当 ViewGroup 已获取焦点并且大小已确定时 if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { // 调用其父类即 View 的 dispatchKeyEvent()方法 if (super.dispatchKeyEvent(event)) { return true; } } // 当 ViewGroup 拥有子 View,并且子 View 已获取到焦点并且大小已确定,事件分发给该子 View else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) { // 当 mFocused 为 ViewGroup 时,事件会进行递归传递 if (mFocused.dispatchKeyEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 1); } return false; } ``` 当上面的 mFocused 为 View 时,事件会分发给 View,来看下代码: ## 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 SimplifiableIfStatement ListenerInfo li = mListenerInfo; // 事件首先分发给 View 的 mOnKeyListener 监听器 if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { return true; } // 分发给 KeyEvent if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) { return true; } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; } ``` 事件首先分发给 mOnKeyListener 监听器,再分发给 KeyEvent,接下来看看 KeyEvent 的事件分发: ## KeyEvent 的分发 ```java 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); // 调用 View 的 onKeyDown()方法 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); } // 回调 View 的 onKeyUp 方法 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 方法的第一个参数为 reveiver,可能为 Activity 对象,也可能为 View 对象,具体要看是在哪里传递过来的。 KeyEvent 的事件分发主要是根据事件类型来回调 View 的 onKeyDown(),onKeyUp(),onKeyLongPress()方法。 Activity 和 View 的 onKeyLongPress()方法都是默认返回 false: ```java public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } ``` 可以重写改方法进行一些长按操作。 接下来看看 View 的 onKeyDown(),onKeyUp(),onKeyLongPress()方法。 ## View 的 onKeyDown(),onKeyUp()方法 onKeyDown()方法 ```java public boolean onKeyDown(int keyCode, KeyEvent event) { // 按下按键,相当于点击拥有焦点的 View ,包括(KEYCODE_DPAD_CENTER,KEYCODE_ENTER, KEYCODE_SPACE,KEYCODE_NUMPAD_ENTER) if (KeyEvent.isConfirmKey(keyCode)) { // 如果 View 设置不可按状态,直接消费,相当于已经按下 if ((mViewFlags & ENABLED_MASK) == DISABLED) { return true; } // View 是可点击的或者是可长按的时 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; } ``` onKeyUp()方法 ```java 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) { // 移除长按事件,进行单击事件 removeLongPressCallback(); return performClick(); } } } return false; } ``` ## Activity 的 onKeyDown(),onKeyUp()方法 当 Activity 内部的 ViewGroup 不消费事件时,事件由 Activity 传递给 KeyEvent。KeyEvent 的 dispatch()方法调用的就是 Activity 的 onKeyDown() 和 onKeyUp()方法了,来看看: onKeyDown()方法 ```java public boolean onKeyDown(int keyCode, KeyEvent event) { // 事件为返回键时 if (keyCode == KeyEvent.KEYCODE_BACK) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { // Android 2.1 以上版本开始跟踪按键事件 event.startTracking(); } else { // 2.1 以下版本直接返回 onBackPressed(); } return true; } ... } ``` onKeyUp()方法 ```java public boolean onKeyUp(int keyCode, KeyEvent event) { // 大于 Android 2.1 版本时 if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { onBackPressed(); return true; } } return false; } ``` 可以看到,在大于 2.1 版本时,按下返回键到 onKeyUp()这里才被处理。我们也可以重写 onBackPressed()方法来实现自己想要的需求。 至此,应用层的按键事件分发流程分析也就完成了,Framework 层的分析将在后期进行。 参考文档: [Android 源码](http://androidxref.com/7.1.1_r6/) [按键事件传递流程(二)](http://andevele.com/2016/07/08/keypad_second/)