Android裡,Activity按鍵事件相關的分發/處理函數有如下幾個:
1) public boolean dispatchKeyEvent(KeyEvent event);
2)public boolean onKeyDown(int keyCode, KeyEvent event);
3)public void onBackPressed(); //back鍵的處理函數
app開發者一般會override這幾個函數來做一些自定義處理,那麼這幾個函數的調用關系以及執行的先後順序是怎麼樣的呢? 今天抽時間看了一下Activity類的源碼,總算理清了一些頭緒,趕緊寫下來記錄已經跟大家share一下~!
首先說明一下這3個函數的執行順序就是我上面列出來的順序。具體每個函數是如何調用到的,下面我們通過源碼來做一下解釋和說明。假設使用者在某個Activity A上按了back鍵, 那麼肯定是A的dispatchKeyEvent()方法先被調用到, 這個函數的源碼如下:
/**
* Called to process key events. You can override this to intercept all
* key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
*
* @param event The key event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
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);
}
我們看到,menu鍵會在分發到A所在的window前被actionbar優先處理, window分發/處理按鍵的函數是superDispatchKeyEvent(), 這個函數是個abstract函數,需要結合具體的window實作源碼來分析(Activity的window貌似是一個叫PhoneWindow的類)。 最關鍵的是要看event的dispatch()函數了,這個函數的第一個參數Receiver是Callback接口類型, 這裡傳入的是this,也即目前的Activity。 這個函數裡會根據Action的值來執行Activity的onKeyDown()/onKeyUp()/onKeyMultiple()函數。 KeyEvent.dispatch()函數的源碼如下:
/**
* Deliver this key event to a {@link Callback} interface. If this is
* an ACTION_MULTIPLE event and it is not handled, then an attempt will
* be made to deliver a single normal event.
*
* @param receiver The Callback that will be given the event.
* @param state State information retained across events.
* @param target The target of the dispatch, for use in tracking.
*
* @return The return value from the Callback method that was called.
*/
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);
boolean res = receiver.onKeyDown(mKeyCode, this); //調用Activity的onKeyDown()
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);
}
return receiver.onKeyUp(mKeyCode, this); //調用Activity的onKeyUp()函數
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;
}
我們再來看一下Activity的onKeydown()函數裡都幹了些什麼? 對于back鍵來說, 在Android 2.0以前,會直接調用onBackPressed()函數;在Android 2.0之後,會調用event.startTracking(), 至于onBackPressed()函數,是在onKeyUp()裡調用的:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
。。。。
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}