[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/)
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路