[TOC]
本篇文章主要分析應(yīng)用層在接收到按鍵事件后的分發(fā)流程,對(duì)于 Framework 層獲得按鍵事件、分發(fā)傳遞給應(yīng)用層等相關(guān)知識(shí),在后期 Framework 層源碼學(xué)習(xí)時(shí)再做分析。
項(xiàng)目中只要在 Activity 中重寫 dispatchKeyEvent()方法,就可以進(jìn)行事件處理和攔截了,那我們從 Activity 類的 dispatchKeyEvent()方法看起:
# 分發(fā)順序
在分發(fā)的任何一步有分發(fā)者進(jìn)行消費(fèi),即返回 true 時(shí),事件停止傳遞。
Activity 的分發(fā)
* Activity 接收到事件,為 Menu 鍵事件就直接消費(fèi)掉,否則分發(fā)給 PhoneWindow,PhoneWindow 直接把事件傳遞給其 DecorView
* DecorView 對(duì) Back 鍵判斷決定是否消費(fèi),不消費(fèi)則分發(fā)給 ViewGroup
* ViewGroup 不消費(fèi)時(shí),事件傳遞給 KeyEvent
ViewGroup 的分發(fā)
* ViewGroup 有焦點(diǎn)且大小確定時(shí),首先自己處理(調(diào)用其父類 View 的 dispatchKeyEvent 方法)
* Viewgroup 的子 View 有焦點(diǎn)且大小確定時(shí),會(huì)向下分發(fā)給子 View
* 當(dāng)子 View 也有子 View 時(shí),會(huì)層層向下分發(fā),直到 View
View 的分發(fā)
* View 首先把事件分發(fā)給 OnKeyListener 監(jiān)聽器,監(jiān)聽器不消費(fèi)時(shí),事件傳給 KeyEvent
KeyEvent 的分發(fā)
* KeyEvent 回調(diào) Activity 的 onKeyDown、onKeyUp、onKeyLongPress 方法
* KeyEvent 回調(diào) View 的 onKeyDown、onKeyUp、onKeyLongPress 方法
事件的回傳
* 當(dāng) Activity 或 View 的 onKeyDown()、onKeyUp()方法也沒有消費(fèi)事件時(shí),事件開始回傳,首先給 KeyEvent 的 dispatch()方法
* 依次按
KeyEvent -> Activity -> DecorView -> PhoneWindow 或
KeyEvent -> View -> ViewGroup -> Activity -> DecorView -> PhoneWindow
的方向進(jìn)行回傳,此處與觸摸事件的回傳方式相似
# 源碼分析
## Activity 分發(fā)
```java
public boolean dispatchKeyEvent(KeyEvent event) {
// 用戶交互時(shí)會(huì)回調(diào)
onUserInteraction();
// 當(dāng)為菜單鍵時(shí),ActionBar 打開菜單并消費(fèi)事件
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;
}
}
// 獲得當(dāng)前 Activity 對(duì)應(yīng)的 Window 對(duì)象
Window win = getWindow();
// 事件分發(fā)給 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);
}
```
可以看到,在具體事件處理前,首先會(huì)回調(diào) onUserInteraction() 方法。注意,在按鍵按下、抬起時(shí)都會(huì)回調(diào)改方法,但觸摸時(shí),只有觸摸按下時(shí)會(huì)回調(diào),移動(dòng)、抬起時(shí)不會(huì)回調(diào)。
接下來(lái)判斷是不是 Menu 按鍵,是則進(jìn)行事件處理。不是則把事件傳遞給 Activity 對(duì)應(yīng)的 Window 對(duì)象,即 PhoneWindow,調(diào)用其 superDispatchKeyEvent(event) 方法,來(lái)看下這個(gè)方法:
## PhoneWindow 的分發(fā)
```java
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
```
可以看到,事件傳遞給了 PhoneWindow 的 DecorView 對(duì)象。來(lái)看看 DecorView 對(duì)象的處理:
```java
public boolean superDispatchKeyEvent(KeyEvent event) {
// 先判斷是不是 Back 按鍵事件
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
// 消費(fèi)事件
if (mActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mActionMode.finish();
}
return true;
}
}
// 傳遞事件給 ViewGroup
return super.dispatchKeyEvent(event);
}
```
接下來(lái)看看 ViewGroup 的分發(fā):
## ViewGroup 的分發(fā)
```java
public boolean dispatchKeyEvent(KeyEvent event) {
// 進(jìn)行一致性檢查
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
// 當(dāng) ViewGroup 已獲取焦點(diǎn)并且大小已確定時(shí)
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
// 調(diào)用其父類即 View 的 dispatchKeyEvent()方法
if (super.dispatchKeyEvent(event)) {
return true;
}
}
// 當(dāng) ViewGroup 擁有子 View,并且子 View 已獲取到焦點(diǎn)并且大小已確定,事件分發(fā)給該子 View
else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 當(dāng) mFocused 為 ViewGroup 時(shí),事件會(huì)進(jìn)行遞歸傳遞
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
```
當(dāng)上面的 mFocused 為 View 時(shí),事件會(huì)分發(fā)給 View,來(lái)看下代碼:
## View 的分發(fā)
```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;
// 事件首先分發(fā)給 View 的 mOnKeyListener 監(jiān)聽器
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
// 分發(fā)給 KeyEvent
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
```
事件首先分發(fā)給 mOnKeyListener 監(jiān)聽器,再分發(fā)給 KeyEvent,接下來(lái)看看 KeyEvent 的事件分發(fā):
## KeyEvent 的分發(fā)
```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);
// 調(diào)用 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 {
// 處理長(zhǎng)按事件
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);
}
// 回調(diào) 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 方法的第一個(gè)參數(shù)為 reveiver,可能為 Activity 對(duì)象,也可能為 View 對(duì)象,具體要看是在哪里傳遞過(guò)來(lái)的。
KeyEvent 的事件分發(fā)主要是根據(jù)事件類型來(lái)回調(diào) View 的 onKeyDown(),onKeyUp(),onKeyLongPress()方法。
Activity 和 View 的 onKeyLongPress()方法都是默認(rèn)返回 false:
```java
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
```
可以重寫改方法進(jìn)行一些長(zhǎng)按操作。
接下來(lái)看看 View 的 onKeyDown(),onKeyUp(),onKeyLongPress()方法。
## View 的 onKeyDown(),onKeyUp()方法
onKeyDown()方法
```java
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 按下按鍵,相當(dāng)于點(diǎn)擊擁有焦點(diǎn)的 View ,包括(KEYCODE_DPAD_CENTER,KEYCODE_ENTER,
KEYCODE_SPACE,KEYCODE_NUMPAD_ENTER)
if (KeyEvent.isConfirmKey(keyCode)) {
// 如果 View 設(shè)置不可按狀態(tài),直接消費(fèi),相當(dāng)于已經(jīng)按下
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
// View 是可點(diǎn)擊的或者是可長(zhǎng)按的時(shí)
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;
// 設(shè)置按下的狀態(tài)
setPressed(true, x, y);
// 檢查長(zhǎng)按,這里和觸摸事件分發(fā)的長(zhǎng)按事件判斷方式相同,可參考
checkForLongClick(0, x, y);
return true;
}
}
return false;
}
```
onKeyUp()方法
```java
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
// 消費(fèi)事件
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
// 去除按下狀態(tài)
setPressed(false);
if (!mHasPerformedLongPress) {
// 移除長(zhǎng)按事件,進(jìn)行單擊事件
removeLongPressCallback();
return performClick();
}
}
}
return false;
}
```
## Activity 的 onKeyDown(),onKeyUp()方法
當(dāng) Activity 內(nèi)部的 ViewGroup 不消費(fèi)事件時(shí),事件由 Activity 傳遞給 KeyEvent。KeyEvent 的 dispatch()方法調(diào)用的就是 Activity 的 onKeyDown() 和 onKeyUp()方法了,來(lái)看看:
onKeyDown()方法
```java
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 事件為返回鍵時(shí)
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 版本時(shí)
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}
```
可以看到,在大于 2.1 版本時(shí),按下返回鍵到 onKeyUp()這里才被處理。我們也可以重寫 onBackPressed()方法來(lái)實(shí)現(xiàn)自己想要的需求。
至此,應(yīng)用層的按鍵事件分發(fā)流程分析也就完成了,F(xiàn)ramework 層的分析將在后期進(jìn)行。
參考文檔:
[Android 源碼](http://androidxref.com/7.1.1_r6/)
[按鍵事件傳遞流程(二)](http://andevele.com/2016/07/08/keypad_second/)
- 導(dǎo)讀
- Java知識(shí)
- Java基本程序設(shè)計(jì)結(jié)構(gòu)
- 【基礎(chǔ)知識(shí)】Java基礎(chǔ)
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設(shè)計(jì)結(jié)構(gòu)
- 對(duì)象與類
- 【基礎(chǔ)知識(shí)】對(duì)象與類
- 【專題分析】Java類加載過(guò)程
- 【面試清單】對(duì)象與類
- 泛型
- 【基礎(chǔ)知識(shí)】泛型
- 【面試清單】泛型
- 集合
- 【基礎(chǔ)知識(shí)】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎(chǔ)知識(shí)】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關(guān)鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達(dá)式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識(shí)
- Activity
- 【基礎(chǔ)知識(shí)】Activity
- 【專題分析】運(yùn)行時(shí)權(quán)限
- 【專題分析】使用Intent打開三方應(yīng)用
- 【源碼分析】Activity的工作過(guò)程
- 【面試清單】Activity
- 架構(gòu)組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數(shù)據(jù)綁定
- 【面試清單】架構(gòu)組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點(diǎn)九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發(fā)機(jī)制
- 【源碼分析】按鍵事件分發(fā)機(jī)制
- 【源碼分析】Android窗口機(jī)制
- 【面試清單】界面
- 動(dòng)畫和過(guò)渡
- 【基礎(chǔ)知識(shí)】動(dòng)畫和過(guò)渡
- 【面試清單】動(dòng)畫和過(guò)渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺(tái)任務(wù)
- 應(yīng)用數(shù)據(jù)和文件
- 基于網(wǎng)絡(luò)的內(nèi)容
- 多線程與多進(jìn)程
- 【基礎(chǔ)知識(shí)】多線程與多進(jìn)程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進(jìn)程
- 應(yīng)用優(yōu)化
- 【專題分析】布局優(yōu)化
- 【專題分析】繪制優(yōu)化
- 【專題分析】?jī)?nèi)存優(yōu)化
- 【專題分析】啟動(dòng)優(yōu)化
- 【專題分析】電池優(yōu)化
- 【專題分析】包大小優(yōu)化
- 【面試清單】應(yīng)用優(yōu)化
- Android新特性
- 【專題分析】狀態(tài)欄、ActionBar和導(dǎo)航欄
- 【專題分析】應(yīng)用圖標(biāo)、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標(biāo)識(shí)符的最佳做法
- 開源庫(kù)源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識(shí)
- Flutter
- 原生開發(fā)與跨平臺(tái)開發(fā)
- 整體歸納
- 狀態(tài)及狀態(tài)管理
- 零碎知識(shí)點(diǎn)
- 添加Flutter到現(xiàn)有應(yīng)用
- Git知識(shí)
- Git命令
- .gitignore文件
- 設(shè)計(jì)模式
- 創(chuàng)建型模式
- 結(jié)構(gòu)型模式
- 行為型模式
- RxJava
- 基礎(chǔ)
- Linux知識(shí)
- 環(huán)境變量
- Linux命令
- ADB命令
- 算法
- 常見數(shù)據(jù)結(jié)構(gòu)及實(shí)現(xiàn)
- 數(shù)組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊(duì)列
- 算法時(shí)間復(fù)雜度
- 常見算法思想
- 其他技術(shù)
- 正則表達(dá)式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識(shí)
- 開發(fā)歸納
- Android零碎問題
- 其他零碎問題
- 開發(fā)思路
