Android按键分发流程之java层按键传递
Android输入子系统之java层按键传递
平台:Android6.0
Android开发中在自定义Activity以及View时经常会重写onKeyDown,onKeyUp,dispatchKeyEvent,同时View还有setOnKeyListener等,当一个按键事件发生时,这些方法将会被回调,但是到底哪个先回调,哪个后回调呢,一直不是特别清楚,只知道个大概,下面将详细讲述按键在java层的分发过程,其中重点关注按键事件在View层次中的分发
java层的按键分发从ViewRootImpl.java的WindowInputEventReceiver中的onInputEvent开始,从前面的应用程序注册消息监听过程分析和InputDispatcher分发键盘消息过程分析两篇文章的分析,InputDispatcher在处理按键事件时,会通过InputChannel::sendMessage函数将按键消息从server端写入,这里的InputChannel是当前获取焦点的窗口的InputChannel对的server端,这样应用程序端就可以收到该消息,然后调用NativeInputEventReceiver的handleEvent,最后调用到InputEventReceiver的onInputEvent函数(具体的可以看应用程序注册消息监听过程分析 的Step20-Step23)
Step 1. WindowInputEventReceiver.onInputEvent
该函数定义在frameworks/base/core/java/android/view/ViewRootImpl.java
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); } ...}
这里只列出部分代码,当一个按键按下时onInputEvent方法就会被回调,其中调用了ViewRootImpl::enqueueInputEvent(event, this, 0, true);
ViewRootImpl.enqueueInputEvent
该函数定义在frameworks/base/core/java/android/view/ViewRootImpl.java
void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { //从队列中获取一个QueuedInputEvent,这里的flags传入的是0 QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); ... if (processImmediately) { doProcessInputEvents();//这里传入的processImmediately是true,所以调用doProcessInputEvents } else { scheduleProcessInputEvents(); }}
从前面的参数可知,这里表示要立即处理,所以调用doProcessInputEvents函数.
ViewRootImpl.doProcessInputEvents
该函数定义在frameworks/base/core/java/android/view/ViewRootImpl.java
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; ... //分发按键 deliverInputEvent(q); }}
在deliverInputEvent函数中实际做按键的分发
ViewRootImpl.deliverInputEvent
该函数定义在frameworks/base/core/java/android/view/ViewRootImpl.java
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 { //选择责任链的模式的入口,如果InputEvent需要跳过IME处理,则从mFirstPostImeInputStage(EarlyPostImeInputStage)开始,否则从mFirstInputStage(NativePreImeInputStage)开始分发 stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; } if (stage != null) { stage.deliver(q); } else { finishInputEvent(q); }}
这里调用了InputStage的deliver方法分发,这里的InputStage代表了输入事件的处理阶段,是一种责任链模式(可以看我的另外一篇文章责任链模式)
在ViewRootImpl的setView函数中会构造一个如图所示的InputStage的链,按键会从入口阶段,进入责任链,顺序处理,入口阶段根据QueuedInputEvent的状态来决定。q.shouldSendToSynthesizer() 这里一般是false,因此主要看stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; 这里的shouldSkipIme其实是一个flag在构造QueuedInputEvent时传入的,从前面的onInputEvent调用的enqueueInputEvent(event, this, 0, true);可知,这里传入的flags是第三个参数0,那这里的shouldSkipIme就是false,那么按键会从mFirstPostImeInputStage 开始分发,就是图中的NativePreImeInputStage分发。
下面只从跟本文前面提到的Activity,View的按键分发流程相关的InputStage(ViewPostImeInputStage)开始分析
ViewPostImeInputStage.processKeyEvent
该函数定义在frameworks/base/core/java/android/view/ViewRootImpl.java
private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; ... // Deliver the key to the view hierarchy. // 调用成员变量mView的dispatchKeyEvent函数,这里mView是PhoneWindow.DecorView对象 if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; } ... // 如果按键是四向键或者是TAB键,则移动焦点 // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { int direction = 0; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: if (event.hasNoModifiers()) { direction = View.FOCUS_LEFT; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (event.hasNoModifiers()) { direction = View.FOCUS_RIGHT; } break; case KeyEvent.KEYCODE_DPAD_UP: if (event.hasNoModifiers()) { direction = View.FOCUS_UP; } break; case KeyEvent.KEYCODE_DPAD_DOWN: if (event.hasNoModifiers()) { direction = View.FOCUS_DOWN; } break; case KeyEvent.KEYCODE_TAB: if (event.hasNoModifiers()) { direction = View.FOCUS_FORWARD; } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { direction = View.FOCUS_BACKWARD; } break; } if (direction != 0) { View focused = mView.findFocus(); if (focused != null) { View v = focused.focusSearch(direction); if (v != null && v != focused) { // do the math the get the interesting rect // of previous focused into the coord system of // newly focused view focused.getFocusedRect(mTempRect); if (mView instanceof ViewGroup) { ((ViewGroup) mView).offsetDescendantRectToMyCoords( focused, mTempRect); ((ViewGroup) mView).offsetRectIntoDescendantCoords( v, mTempRect); } if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); return FINISH_HANDLED; } } // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { return FINISH_HANDLED; } } else { // find the best view to give focus to in this non-touch-mode with no-focus View v = focusSearch(null, direction); if (v != null && v.requestFocus(direction)) { return FINISH_HANDLED; } } } } return FORWARD;}
上述主要分两步:
第一步是调用PhoneWindow.DecorView的dispatchKeyEvent函数,DecorView是View层次结构的根节点,按键从根节点开始根据Focuse view的path自上而下的分发。
第二步是判断按键是否是四向键,或者是TAB键,如果是则需要移动焦点
public boolean dispatchKeyEvent(KeyEvent event) { ... if (!isDestroyed()) { final Callback cb = getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { return true; } } return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);}
主要的分发在下面开始,如果cb不为空并且mFeatureId小于0,则调用cb.dispatchKeyEvent开始分发,否则会调用DecorView的父类(View)的dispatchKeyEvent函数。cb是Window.Callback类型,Activity实现了Window.Callback接口,在attach函数中,会调用Window.setCallback函数将自己注册进PhoneWindow中,所以cb不为空。在PhoneWindow初始化时会调用installDecor函数生成DecorView对象,该函数中传入的mFeatureId是-1,所以mFeatureId也小于0。因此此处会调用Activity的dispatchKeyEvent函数,开始在View中分发按键。
下面来分析按键在View的层次结构中是如何分发的
接下来来看这里先看看Activity(Callback)的dispatchKeyEvent实现:
Activity.dispatchKeyEvent
该函数定义在frameworks/base/core/java/android/app/Activity.java
public boolean dispatchKeyEvent(KeyEvent event) { //调用自定义的onUserInteraction onUserInteraction(); Window win = getWindow(); //调用PhoneWindow的superDispatchKeyEvent,实际调用DecorView的superDispatchKeyEvent,从DecorView开始从顶层View往子视图传递 if (win.superDispatchKeyEvent(event)) { return true; } View decor = mDecor; if (decor == null) decor = win.getDecorView(); //到这里如果view层次结构没有返回true则交给KeyEvent本身的dispatch方法,Activity的onKeyDown/onKeyUp/onKeyMultiple就会被触发 return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);}
接着看下PhoneWindow的superDispatchKeyEvent
PhoneWindow.superDispatchKeyEvent
@Overridepublic boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event);} public boolean superDispatchKeyEvent(KeyEvent event) { // Give priority to closing action modes if applicable. 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的层次结构,调用ViewGroup.dispatchKeyEvent return super.dispatchKeyEvent(event);}
再看ViewGroup的dispatchKeyEvent函数
ViewGroup.dispatchKeyEvent
该函数定义在frameworks/base/core/java/android/view/ViewGroup.java
public boolean dispatchKeyEvent(KeyEvent event) { ... 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; } } ... return false;}
这里可以看出如果ViewGroup满足条件,则优先处理事件而不发给子视图去处理。
下面看下View的dispatchKeyEvent实现
View.dispatchKeyEvent
该函数定义在frameworks/base/core/java/android/view/View.java
public boolean dispatchKeyEvent(KeyEvent event) { ... // Give any attached key listener a first crack at the event. //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //调用onKeyListener,如果注册了OnKeyListener,并且View属于Enable状态,则触发 if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { return true; } //调用KeyEvent.dispatch方法,并将view作为参数传递进去,实际会回调View的onKeyUp/onKeyDown等方法 if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) { return true; } ... return false;}
View.onKeyDown/View.onKeyUp
该函数定义在frameworks/base/core/java/android/view/View.java
下面看下View的onKeyDown/onKeyUp
// frameworks/base/core/java/android/view/View.javapublic boolean onKeyDown(int keyCode, KeyEvent event) { boolean result = false; //处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER按键 if (KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { //disabled的view直接返回true,不再继续分发,即Activity的onKeyDown和onKeyUp无法收到KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件 return 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事件 setPressed(true);// 标记pressed,你可能设置了View不同的background,这时候就会有所体现(比如高亮效果) checkForLongClick(0); return true; } } return result;}public boolean onKeyUp(int keyCode, KeyEvent event) { //处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER按键 if (KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { //disabled的view直接返回true,不再继续分发,即Activity的onKeyDown和onKeyUp无法收到KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件 return true; } if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) { setPressed(false); if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); return performClick(); } } } return false;}
Activity.onKeyDown/onKeyUp
最后再分析一下Activity的onKeyDown/onKeyUp
// frameworks/base/core/java/android/app/Activity.javapublic boolean onKeyDown(int keyCode, KeyEvent event) { //如果是back键则启动追踪 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()) { //如果是back键并且正在追踪该Event,则调用onBackPressed onBackPressed(); return true; } } return false;}
总结
1. 调用Activity的dispatchKeyEvent
1.1. 调用onUserInteraction
1.2. 调用PhoneWindow的superDispatchKeyEvent
1.2.1.再然后调用DecorView的superDispatchKeyEvent,DecorView的superDispatchKeyEvent再调用父类的dispatchKeyEvent
1.2.2. DecorView是窗口中的顶级视图,按键从DecorView开始往子节点分发,因为DecorView继承自FrameLayout,FrameworkLayout继承自ViewGroup,所以实际调用ViewGroup的dispatchKeyEvent
1.2.3. ViewGroup中会先判断是否可以处理KeyEvent,如果可以则调用父类(View)的dispatchKeyEvent,如果当前的ViewGroup不满足条件,则调用mFocused的dispatchKeyEvent,这里的mFocused是焦点子视图,也可以是含有焦点子视图的ViewGroup,因此这里可能会发生递归调用。
1.2.3.1. 在View的dispatchKeyEvent中会先调用onKey函数,即会调用各个View注册的View.OnKeyListener对象的接口
1.2.3.2. 在View的dispatchKeyEvent中接着调用KeyEvent的dispatch函数,因为View实现了Window.Callback函数,因此会调用View的onKeyDown/onKeyUp/onKeyMultiple函数
1.3 调用KeyEvent的dispatch函数,因为Activity实现了Window.Callback函数,因此会调用Activity的onKeyDown/onKeyUp/onKeyMultiple函数
2. 如果整个View层次都没有返回true,则调用PhoneWindow的onKeyDown/onKeyUp函数
总结:
以一次KEYCODE_DPAD_DOWN按键为例,说明各个回调的顺序
Down事件
Activity::dispatchKeyEvent –> Activity::onUserInteraction –> View.OnKeyListener::onKey –> View.onKeyDown –> Activity::onKeyDown –> PhoneWindow.onKeyDown
Up事件
Activity::dispatchKeyEvent –> Activity::onUserInteraction –> View.OnKeyListener::onKey –> View.onKeyUp –> Activity::onKeyUp –> PhoneWindow.onKeyUp
View的KEYCODE_DPAD_CENTER KEYCODE_ENTER是在View的onKeyDown和onKeyUp中处理的,会处理一些高亮效果
一个Enable并且CLICKABLE的View响应KEYCODE_DPAD_CENTER KEYCODE_ENTER的DOWN事件后会return true,即Activity的onKeyDown不会回调,但是up事件没有return true,Activity的onKeyUp会回调
Activity的KEYCODE_BACK在Activity的onKeyDown启动追踪,在onKeyUp中实际调用onBackPressed函数处理返回键,所以长按back键是不会切换Activity的
参考:
http://www.cnblogs.com/xiaoweiz/p/3803301.html
更多相关文章
- C语言函数的递归(上)
- Android:Android系统启动(笔记)
- Android快速开发 动画系列(二) 之 overridePendingTransition(跳转
- android号码匹配位的修改
- 通过广播获取Android屏幕旋转事件
- Android中getWidth()和getMeasureWidth()的区别探究
- android 7.0 加入 android:directBootAware
- Android工程师面试准备知识点
- Android(安卓)ndk开发swig编译jni接口配置文件(二)