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的层次结构中是如何分发的
DecorView的按键分发

接下来来看这里先看看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

<!-- PhoneWindow.java -->
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
    return mDecor.superDispatchKeyEvent(event);
} 

<!-- PhoneWindow.DecorView -->
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.java
public 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.java
public 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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值