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; }