想写点什么东西放在这里,到后来发现没什么用,直接进入正题吧,本文主要是由于工作的遇到的一个问题,之后自己闲的蛋疼研究研究之后一发不可收拾,整篇文章从 Framework层开始一直到我们平时处理事件的View进行了整体的介绍,说是分析,其实就是梳理一遍事件的传递的流程,不要说我是标题党就好。由于时间及经验有限,文中可能存在错误与不足,欢迎大家指出,我会第一时间对文章进行修改纠正。
在说正文之前,必须要感谢几个人:以我遇到的时间排序。
- Android按键事件处理流程 -KeyEvent,作者tmp_zhao目前已经迁到简书。这篇文章介绍了事件,准确的说是按键事件(KeyEvent)从DecorView到View的整体流程。
- Android 中keyEvent的消息处理,作者是转载的,没有找到原作者。这篇文章介绍事件是怎么从连接层传到DecorView的,简单介绍,没有过多说明。
- Android源码解析(三十)-->触摸事件分发流程,作者一片枫叶_刘超,为我之前的猜想提供了充分有力的证据。
- Android中事件传递分析,作者zjutkz提供了从 Framework层到ViewRootImpl的证据,大神。
非常感谢以上四位大神的分享。
整体流程:系统核心->Native层->ViewRootImpl->DecorView->Activity->Window->DecorView->ViewGroup->View,无论是触摸事件还是点击时间都是一样的。
系统核心传到Framework的不去讨论了,我也不会,这篇文章是从ViewRootImpl开始说,关于怎么从Framework传到ViewRootImpl的,有兴趣的可以去看上面推荐的文章。
ViewRootImpl
首先从Framework中传递过来的事件会到InputEventReceiver的onInputEvent方法,在ViewRootImpl中的它的实现类是:
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
@Override
public void onBatchedInputEventPending() {
if (mUnbufferedInputDispatch) {
super.onBatchedInputEventPending();
} else {
scheduleConsumeBatchedInput();
}
}
@Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
}
}
当一个事件产生,通过一系列的传递,最终会到WindowInputEventReceiver的onInputEvent方法中进行排队处理,之后会调到ViewRootImpl的enqueueInputEvent方法:
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
<------Look here------>
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
不考虑延迟的情况,直接看doProcessInputEvents():
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
long eventTime = q.mEvent.getEventTimeNano();
long oldestEventTime = eventTime;
if (q.mEvent instanceof MotionEvent) {
MotionEvent me = (MotionEvent)q.mEvent;
if (me.getHistorySize() > 0) {
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
上面的代码中最重要的就是deliverInputEvent(q)这句,其他的都是一些排列啊,获得事件触发的事件啊,之类额,不是很懂,也不过介绍了。继续往下看:
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
<------------重点----------------->
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
在这个方法中会创建一个InputStage对象,有兴趣的可以去看一下具体这个类是怎么写的,蛮有意思的,这里就不过多介绍了,只说两个方法,一个是onProcess这个是处理event的方法,另一个是forward,让下一个InputStage去处理。我们可以在ViewRootImpl的setView方法中找到上面出现的三种InputStage:
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
重点是在ViewPostImeInputStage的onProcess方法中:
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
可以看到在这里对于InputEvent事件进行了分别的处理,KeyEvent事件调用java processKeyEvent(q)
,MotionEvent事件调用java processPointerEvent
。我们目前只关注KeyEvent,其实两条路目前是一样的。
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
//省略一部分代码
}
这里的mView,其实就是DecorView。事件就从连接层传到了视图层(这么说可能不太准确,个人理解吧)。
我们来总结一下在ViewRootImpl中都做了哪些事情:
- 在WindowInputEventReceiver这个接口中接受从Framework层传递过来的输入事件。
- 使用QueuedInputEvent对所有的事件进行排队,然后根据不同的条件,状态把事件分发下去。
- 在处理事件的时候使用InputStage能够实现对事件的按照顺序处理。
- 在ViewPostImeInputStage对事件进行分发,根据事件类别分发到DecorView中。
以上就是在ViewRootImpl中进行的处理。
DecorView
接着上文,我们继续往下分析,这里只针对KeyEvent。先来看看java dispatchKeyEvent(p)
这个方法:
使用的是API24的版本的源码,跟之前版本的可能会有所区别,不过大体上是一样的。
<!--DecorView.java-->
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
/// 1. 第一次down事件的时候,处理panel的快捷键
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
/// 2. 这里是我们本文的重点,当window没destroy且其Callback非空的话,交给其Callback处理
/// 其中 mFeatureId < 0 表示是Application的DecorView
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();// Activity、Dialog都是Callback接口的实现
final boolean handled = cb != null && mFeatureId < 0 ?
cb.dispatchKeyEvent(event)//Activity,Dialog。
: super.dispatchKeyEvent(event);//否则直接派发到ViewGroup#dispatchKeyEvent(View层次结构)
if (handled) {
return true;
}
}
/// 3. 这是key事件的最后一步,如果到这一步还没处理掉,则派发到PhoneWindow对应的onKeyDown, onKeyUp方法
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
代码上都有相应的注释,简单解释一下就是如果当前的DecorView是Application的DecorView的话,并且存在Activity或者Dialog,那么会执行Activity或Dialog的java dispatchKeyEvent()
方法,否则的话就直接执行ViewGroup的java dispatchKeyEvent()
方法。并且如果,没有消耗掉这个事件的话就回去去执行PhoneWindow的onKeyDown或onKeyUp方法。
接下来按照正常的逻辑会调用Activity的java dispatchKeyEvent()
方法:
<!-- Activity -- >
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
//处理ActionBar和Menu 略过
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;
}
}
//重点在这里,调用Window的superDispatchKeyEvent(event)方法
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
//如果View层次没有处理的话,就交给KeyEvent本身的dispatch方法,Activity的各种回调方法会被触发
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
在Activity中,先进行判断,之后调用Window的java superDispatchKeyEvent(event)
,将事件向下派发到View层次,如果View层次没有处理的话,会调用Activity中KeyEvent的各种回调方法。沿着主路线继续看:
<!-- Window.java -->
/**
* Used by custom windows, such as Dialog, to pass the key press event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchKeyEvent(KeyEvent event);
<!-- PhoneWindow.java -->
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
<!-- DecorView.java -- >
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
//如果有设置ActionMode,则优先调用ActionMode
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
//继续下发至View层。
return super.dispatchKeyEvent(event);
}
通过一系列的调用最终调用到java super.dispatchKeyEvent(event);
,因为DecorView的父类是FrameLayout;所以事件从这里开始就正式进入View层次了。
我们来总结一下在DecorView中都做了哪些事情:
- 在
java DecorView.dispatchKeyEvent(ev)
方法中接受从ViewRootImpl传过来的事件,判断,分发至Activity或View处理,如果都没有处理的话,则执行PhoneWindow的相关方法。 - 在
java Activity.dispatchKeyEvent(ev)
方法中调用Window的处理方法,如果没有被处理的话,会触发Activity实现KeyEvent.Callback的相关方法。 - 通过Window->PhoneWindow->DecorView,事件再次传递到
java DecorView.superDispatchKeyEvent(ev)
方法中,直接调用父类的java dispatchKeyEvent(event)
事件被传递到View层。
在DecorView层接收事件,再通过DecorView层将事件发下去。
View
相对于MotionEvent,KeyEvent的最大的不同点就是在View层的处理,其实以上的步骤同样适用于MotionEvent。有兴趣的童鞋可以从ViewRootImpl中的InputStage找到线索。
继续我们的主题,当事件传递到View层之后首先被触发的应该是java ViewGroup.dispatchKeyEvent(ev)
方法:
<!--ViewGroup.java -- >
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
//KeyEvent一致性检测,忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
//如果此ViewGroup是focused或者具体的大小被设置了(有边界),则交给它处理,即调用View的实现
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//否则,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
这里我们可以看出对KeyEvent来说在View层次结构中,如果ViewGroup条件满足则会优先处理事件而不是先派发给其孩子view,这第一点是和MotionEvent最大的不同,在处理MotionEvent的时候ViewGroup是默认不处理的,只有当子View不处理的时候才会交给ViewGroup处理。接下来看看java View.dispatchKeyEvent(ev)
:
<! -- 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;
//调用onKeyListener,如果它非空且view是ENABLED状态,监听器优先触发
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
//调用KeyEvent.dispatch方法,并将view对象本身作为参数传递进去,view的各种callback方法在这里被触发
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
这里可以看到处理的逻辑还是蛮清晰的,对ViewGroup进行进行Flag的判断,决定是自己处理还是向下派发,如果View是可用的并且有对应的监听器,则触发监听器,否则触发View实现的KeyEvent.Callback的回调。其中在View处理的时候有一个 mAttachInfo.mKeyDispatchState变量,这个类是KeyEvent中的DispatcherState,静态类,作用是在KeyEvent.dispatch()中处理并追踪事件。这里不过多介绍了,详情可以参考这篇文章。
关于ViewGroup的flag可以参考这篇文章
我们来总结一下在View中都做了哪些事情:
- 在ViewGroup中判断Flag来决定是否处理事件,不处理则交给子View处理;
- 在View中先判断有没有监听器,有则触发,没有则触发KeyEvent.dispatch()回调;
到这里关于事件的派发流程其实就算是告一段落了,从ViewRootImpl到DecorView再到View层。接下来还有一些要说的那就是回调,也就是返回路线。
返回路线
通过上面的内容我们了解到了事件从ViewRootImpl传递到View层,但是如果到View层没有被消耗处理的话,那这个事件接下来应该怎么派发?
对于MotionEvent会传递到父布局,最终传递到Activity。那对于KeyEvent是怎么处理这种情况的那?
三个主要的方法:
- View中的KeyEvent.dispatch()方法;
- Activity中的KeyEvent.dispatch()方法;
- DecorView中PhoneWindow的onKeyDown和onKeyUp;
先来看看KeyEvent.dispatch()方法都做了哪些事情:
<!--KeyEvent.java -- >
/**
* 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);
// 回调Callback接口的onKeyDown方法,View和Activity都是此接口的实现者
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);
}
//回调Callback接口的onKeyUp方法,View和Activity都是此接口的实现者
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;
}
简单说一下这段代码,在dispatch方法中对于keyevent的action进行了判断,分别调用Callback接口的实现类相对应的方法。其他的一下是之前说过的KeyEvent中的DispatcherState这个类对于事件的处理,本人也不是很清楚,简单来说就是对事件进行一些标记,跟踪之类的。
分别看一下View和Activity对于KeyEvent.Callback的实现方法:
<!-- View.java -- >
/**
* Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
* KeyEvent.Callback.onKeyDown()}: perform press of the view
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
* is released, if the view is enabled and clickable.
* <p>
* Key presses in software keyboards will generally NOT trigger this
* listener, although some may elect to do so in some situations. Do not
* rely on this to catch software key presses.
*
* @param keyCode a key code that represents the button pressed, from
* {@link android.view.KeyEvent}
* @param event the KeyEvent object that defines the button action
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {// 只处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER这2个按键
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;// 针对disabled View直接返回true表示处理过了
}
// Long clickable items don't necessarily have to be clickable.
if (((mViewFlags & CLICKABLE) == CLICKABLE
|| (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
&& (event.getRepeatCount() == 0)) { // clickable或者long_clickable且是第一次down事件
// 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);// 标记pressed,你可能设置了View不同的background,这时候就会有所体现(比如高亮效果)
checkForLongClick(0, x, y);// 启动View的long click检测
return true;// 到达这一步就表示KeyEvent被处理掉了
}
}
return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { // 必须得是LONG_CLICKABLE的View
mHasPerformedLongPress = false;// 设置初始值
if (mPendingCheckForLongPress == null) {// 只非空的时候才new一个
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,/ post一个Runnable,注意延迟是个差值,而不是delayOffset
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {// 当时间到了,此Runnable没被移除掉的话,并且这些条件都满足的时候,
if (performLongClick(mX, mY)) {// 客户端定义的onLongClickListener监听器被触发
mHasPerformedLongPress = true;
}
}
}
public void setAnchor(float x, float y) {
mX = x;
mY = y;
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
/**performLongClick()最终会调到这个方法
* Calls this view's OnLongClickListener, if it is defined. Invokes the
* context menu if the OnLongClickListener did not consume the event,
* optionally anchoring it to an (x,y) coordinate.
*
* @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
* to disable anchoring
* @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
* to disable anchoring
* @return {@code true} if one of the above receivers consumed the event,
* {@code false} otherwise
*/
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {// 优先触发监听器
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {// 如果还没处理,显示ContextMenu如果定义了的话
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;// 返回处理结果
}
/**
* Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
* KeyEvent.Callback.onKeyUp()}: perform clicking of the view
* when {@link KeyEvent#KEYCODE_DPAD_CENTER}, {@link KeyEvent#KEYCODE_ENTER}
* or {@link KeyEvent#KEYCODE_SPACE} is released.
* <p>Key presses in software keyboards will generally NOT trigger this listener,
* although some may elect to do so in some situations. Do not rely on this to
* catch software key presses.
*
* @param keyCode A key code that represents the button pressed, from
* {@link android.view.KeyEvent}.
* @param event The KeyEvent object that defines the button action.
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {// 同onKeyDown,默认也只处理confirm key
if ((mViewFlags & ENABLED_MASK) == DISABLED) {// 同样的逻辑,如果是DISABLED view,直接返回true表示处理过了
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
setPressed(false); // 重置pressed状态
if (!mHasPerformedLongPress) {// 长按没发生的话,
// This is a tap, so remove the longpress check
removeLongPressCallback();// 当up事件发生的时候,移除这些已经没用的callback
return performClick();// 调用单击onClick监听器
}
}
}
return false;// 其他所有的Key默认不处理
}
/**
* Sets the pressed state for this view.
*
* @see #isClickable()
* @see #setClickable(boolean)
*
* @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
* the View's internal state from a previously set "pressed" state.
*/
public void setPressed(boolean pressed) {
final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
if (pressed) {
mPrivateFlags |= PFLAG_PRESSED;
} else {
mPrivateFlags &= ~PFLAG_PRESSED;
}
if (needsRefresh) {
refreshDrawableState();// 这行代码会刷新View的显示状态
}
dispatchSetPressed(pressed);
}
接下来,看看Activity对应的onKeyDown,onKeyUp方法:
<! -- Activity.java -- >
/**
* Called when a key was pressed down and not handled by any of the views
* inside of the activity. So, for example, key presses while the cursor
* is inside a TextView will not trigger the event (unless it is a navigation
* to another object) because TextView handles its own key presses.
*
* <p>If the focused view didn't want this event, this method is called.
*
* <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
* by calling {@link #onBackPressed()}, though the behavior varies based
* on the application compatibility mode: for
* {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,
* it will set up the dispatch to call {@link #onKeyUp} where the action
* will be performed; for earlier applications, it will perform the
* action immediately in on-down, as those versions of the platform
* behaved.
*
* <p>Other additional default key handling may be performed
* if configured with {@link #setDefaultKeyMode}.
*
* @return Return <code>true</code> to prevent this event from being propagated
* further, or <code>false</code> to indicate that you have not handled
* this event and it should continue to be propagated.
* @see #onKeyUp
* @see android.view.KeyEvent
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
event.startTracking(); // 标记追踪这个key event
} else {
onBackPressed();// 直接调用onBackPressed
}
return true; // 返回true表示被activity处理掉了
}
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
Window w = getWindow();
if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
} else {
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
boolean clearSpannable = false;
boolean handled;
if ((event.getRepeatCount() != 0) || event.isSystem()) {
clearSpannable = true;
handled = false;
} else {
handled = TextKeyListener.getInstance().onKeyDown(
null, mDefaultKeySsb, keyCode, event);
if (handled && mDefaultKeySsb.length() > 0) {
// something useable has been typed - dispatch it now.
final String str = mDefaultKeySsb.toString();
clearSpannable = true;
switch (mDefaultKeyMode) {
case DEFAULT_KEYS_DIALER:
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case DEFAULT_KEYS_SEARCH_LOCAL:
startSearch(str, false, null, false);
break;
case DEFAULT_KEYS_SEARCH_GLOBAL:
startSearch(str, false, null, true);
break;
}
}
}
if (clearSpannable) {
mDefaultKeySsb.clear();
mDefaultKeySsb.clearSpans();
Selection.setSelection(mDefaultKeySsb,0);
}
return handled;
}
}
/**
* Called when a key was released and not handled by any of the views
* inside of the activity. So, for example, key presses while the cursor
* is inside a TextView will not trigger the event (unless it is a navigation
* to another object) because TextView handles its own key presses.
*
* <p>The default implementation handles KEYCODE_BACK to stop the activity
* and go back.
*
* @return Return <code>true</code> to prevent this event from being propagated
* further, or <code>false</code> to indicate that you have not handled
* this event and it should continue to be propagated.
* @see #onKeyDown
* @see KeyEvent
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {// 同onKeyDown,2.0之后的版本
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();// 在这种情况下执行onBackPressed表示处理掉了
return true;
}
}
return false;
}
还有的就是在DecorView中最下面的一句PhoneWindow的回调方法:
<! -- PhoneWindow.java -- >
/**
* A key was pressed down and not handled by anything else in the window.
*
* @see #onKeyUp
* @see android.view.KeyEvent
*/
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
/* ****************************************************************************
* HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
*
* If your key handling must happen before the app gets a crack at the event,
* it goes in PhoneWindowManager.
*
* If your key handling should happen in all windows, and does not depend on
* the state of the current application, other than that the current
* application can override the behavior by handling the event itself, it
* should go in PhoneFallbackEventHandler.
*
* Only if your handling depends on the window, and the fact that it has
* a DecorView, should it go here.
* ****************************************************************************/
final KeyEvent.DispatcherState dispatcher =
mDecor != null ? mDecor.getKeyDispatcherState() : null;
//Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
// + " flags=0x" + Integer.toHexString(event.getFlags()));
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
int direction = 0;
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
direction = AudioManager.ADJUST_RAISE;
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
direction = AudioManager.ADJUST_LOWER;
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
direction = AudioManager.ADJUST_TOGGLE_MUTE;
break;
}
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, direction,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
| AudioManager.FLAG_FROM_KEY);
}
return true;
}
// These are all the recognized media key codes in
// KeyEvent.isMediaKey()
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if (mMediaController != null) {
if (mMediaController.dispatchMediaButtonEvent(event)) {
return true;
}
}
return false;
}
case KeyEvent.KEYCODE_MENU: {
onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
return true;
}
case KeyEvent.KEYCODE_BACK: {
if (event.getRepeatCount() > 0) break;
if (featureId < 0) break;
// Currently don't do anything with long press.
if (dispatcher != null) {
dispatcher.startTracking(event, this);
}
return true;
}
}
return false;
}
/**
* A key was released and not handled by anything else in the window.
*
* @see #onKeyDown
* @see android.view.KeyEvent
*/
protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
final KeyEvent.DispatcherState dispatcher =
mDecor != null ? mDecor.getKeyDispatcherState() : null;
if (dispatcher != null) {
dispatcher.handleUpEvent(event);
}
//Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
// + " flags=0x" + Integer.toHexString(event.getFlags()));
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN: {
final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
| AudioManager.FLAG_FROM_KEY;
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
mMediaController.adjustVolume(0, flags);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, 0, flags);
}
return true;
}
case KeyEvent.KEYCODE_VOLUME_MUTE: {
// Similar code is in PhoneFallbackEventHandler in case the window
// doesn't have one of these. In this case, we execute it here and
// eat the event instead, because we have mVolumeControlStreamType
// and they don't.
getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
return true;
}
// These are all the recognized media key codes in
// KeyEvent.isMediaKey()
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if (mMediaController != null) {
if (mMediaController.dispatchMediaButtonEvent(event)) {
return true;
}
}
return false;
}
case KeyEvent.KEYCODE_MENU: {
onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
event);
return true;
}
case KeyEvent.KEYCODE_BACK: {
if (featureId < 0) break;
if (event.isTracking() && !event.isCanceled()) {
if (featureId == FEATURE_OPTIONS_PANEL) {
PanelFeatureState st = getPanelState(featureId, false);
if (st != null && st.isInExpandedMode) {
// If the user is in an expanded menu and hits back, it
// should go back to the icon menu
reopenMenu(true);
return true;
}
}
closePanel(featureId);
return true;
}
break;
}
case KeyEvent.KEYCODE_SEARCH: {
/*
* Do this in onKeyUp since the Search key is also used for
* chording quick launch shortcuts.
*/
if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
break;
}
if (event.isTracking() && !event.isCanceled()) {
launchDefaultSearch(event);
}
return true;
}
case KeyEvent.KEYCODE_WINDOW: {
if (mSupportsPictureInPicture && !event.isCanceled()) {
getWindowControllerCallback().enterPictureInPictureModeIfPossible();
}
return true;
}
}
return false;
}
至此所有按键事件的处理就分析完毕了。最后我们来个整体的总结一下。
- View的各种KeyEvent.Callback接口早于Activity的对应接口被调用;
- 整个处理环节中只要有一处表明处理掉了,则处理结束,不在往下传递;
- 各种Callback接口的处理优先级低于监听器,也就是说各种onXXXListener的方法优先被调用。
总结
梳理一遍整体流程:
- ViewRootImpl中的WindowInputEventReceiver接受时间,排列分发。
- ViewRootImpl中的InputStage对事件进行顺序处理。
- 通过ViewPostImeInputStage将事件传递到DecorView中。
- DecorView进行判断当前状态,将事件传递给Activity或ViewGroup。
- Activity将事件传递给Window再传递给DecorView,然后传递到View层。
- ViewGroup先判断是否消耗,消耗则事件终止,不消耗则传递给包括的子View。
- View先判断是否有监听器,优先触发监听,没有则触发View实现KeyEvent.Callback方法。
- 如果View监听器和回调都返回false,事件往回传递至Activity。
- 执行Activity实现KeyEvent.Callback的相关方法。
- 如果Activity中仍返回false,不消耗,则继续往回返,传递至DecorView。
- 执行PhoneWindow的onKeyDown和onKeyUp。
整体的流程应该就是这样,如果有不对的地方欢迎批评指正,我会第一时间更改纠正