[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零碎問題
- 其他零碎問題
- 開發思路