Android Framework 输入子系统 (09)InputStage解读

系列文章解读&说明:

Android Framework 输入子系统 的 分析主要分为以下部分:

(01)核心机制 inotify和epoll

(02)核心机制 双向通信(socketpair+binder)

(03)输入系统框架

(04)InputReader解读

(05)InputDispatcher解读

(06)Global Key 一键启动 应用程序案例

(07)APP建立联系

(08)View基础(activity window decor view)

(09)InputStage解读

(10)Input命令解读

(11)sendevent与getevent命令解读

本模块分享的内容:InputStage解读

本章关键点总结 & 说明:

以上是迭代导图,主要关注➕ InputStage部分即可,同时上图是总图,局部显示的有点小,局部截图,如下所示:

本章节的思维导图放大后如上所示,这里主要研究InputStage的处理流程。

1 InputStage之前的处理流程

按键事件的理论处理流程如下:

  1. activity 把事件发送给window,如果window不能处理,activity来兜底处理;
  2. window把事件发给DecorView,如果DecorView不能处理,window来兜底处理;
  3. DecorView能够处理则发送给输入焦点

对于java层的按键分发从ViewRootImpl.java的WindowInputEventReceiver中的onInputEvent开始,代码如下:

final class WindowInputEventReceiver extends InputEventReceiver {
    //...
    @Override
    public void onInputEvent(InputEvent event) {
        enqueueInputEvent(event, this, 0, true);
    }
    //...
}

这里继续分析enqueueInputEvent,代码实现如下:

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;

    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}

因为这里传递进来的值processImmediately 为true,因此继续分析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;
        deliverInputEvent(q);//关键点
    }

    if (mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = false;
        mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
    }
}

在deliverInputEvent函数中做按键的实际分发,代码如下:

private void deliverInputEvent(QueuedInputEvent q) {
    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的deliver方法进行分发,InputStage代表了输入事件的处理阶段,使用责任链模式 设计模式。

2 InputStage说明

这里使用一张图来描述InputStage的处理流程,如下所示:

接下来对责任链模式的各个InputStage进行说明,整体流程如上所说,简要归纳如下:

  1. 输入法之前的处理
  2. 输入法处理
  3. 输入法之后处理
  4. 综合处理

这里对各个InputStage进行简要说明:

InputStage 说明
NativePreImeInputStage 分发早于IME的InputEvent到NativeActivity中去处理, NativeActivity和普通acitivty的功能一致,不过是在native层实现,这样执行效率会更高,同时NativeActivity在游戏开发中很实用(不支持触摸事件)。
ViewPreIMEInputStage 分发早于IME的InputEvent到View框架处理,会调用view(输入焦点)的onkeyPreIme方法,同时会给View在输入法处理key事件之前先得到消息并优先处理,View系列控件可以直接复写onKeyPreIme( 不支持触摸事件)。
ImeInputStage 分发InputEvent到IME处理调用ImeInputStage的onProcess,InputMethodManager的dispatchInputEvent方法处理消息(不支持触摸事件)。
EarlyPostImeInputStage 与touchmode相关,比如你的手机有方向键,按方向键会退出touchmode,这个事件被消费,有可能会有view的背景变化,但不确定(支持触摸事件)。
NativePostImeInputStage 分发InputEvent事件到NativeActivity,IME处理完消息后能先于普通Activity处理消息(此时支持触摸事件)。
ViewPostImeInputStage 分发InputEvent事件到View框架,view的事件分发(支持触摸事件)。最终会调用到输入焦点的3个方法:使用setKeyListener注册的监听器的onKey,之后是onKeyDown和onKeyUp,或者调用activity的onKeyDown和onKeyUp方法,也就是兜底处理无人处理的key事件
SyntheticInputStage 未处理 InputEvent最后处理。

InputStage将输入事件的处理分成若干个阶段(Stage), 如果当前有输入法窗口,则事件处理从 NativePreIme 开始,否则从EarlyPostIme 开始,事件依次经过每个Stage,如果该事件没有被标识为 “Finished”, 该Stage就会处理它,然后返回处理结果Forward 或 Finish,Forward 运行下一个Stage继续处理,而Finished事件将会简单的Forward到下一级,直到最后一级 SyntheticInputStage。

3 ViewPostImeInputStage分析

从ViewPostImeInputStage.processKeyEvent 开始分析,代码实现如下:

final class ViewPostImeInputStage extends InputStage {
	//...
    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            handleDispatchDoneAnimating();
            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);
            }
        }
    }
}

这里继续分析ViewPostImeInputStage的processKeyEvent,代码实现如下:

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;

    if (event.getAction() != KeyEvent.ACTION_UP) {
        // If delivering a new key event, make sure the window is
        // now allowed to start updating.
        handleDispatchDoneAnimating();
    }

    // Deliver the key to the view hierarchy.
	//这里 mView是PhoneWindow.DecorView对象
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }

    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    // If the Control modifier is held, try to interpret the key as a shortcut.
    if (event.getAction() == KeyEvent.ACTION_DOWN
            && event.isCtrlPressed()
            && event.getRepeatCount() == 0
            && !KeyEvent.isModifierKey(event.getKeyCode())) {
        if (mView.dispatchKeyShortcutEvent(event)) {
            return FINISH_HANDLED;
        }
        if (shouldDropInputEvent(q)) {
            return FINISH_NOT_HANDLED;
        }
    }

    // Apply the fallback event policy.
    if (mFallbackEventHandler.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    // Handle automatic focus changes.
	//四向键,或者是TAB键的处理
    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;
}

以上处理主要做2件事:

  1. 调用PhoneWindow.DecorView的dispatchKeyEvent函数,按键从根节点开始自上而下分发。 
  2. 判断按键是否是四向键,或者是TAB键,如果是则需要移动焦点

接下来主要针对事件派发进行继续分析,关注DecorView的dispatchKeyEvent,代码实现如下:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    final int keyCode = event.getKeyCode();
    final int action = event.getAction();
    final boolean isDown = action == KeyEvent.ACTION_DOWN;
    //...
    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);
}

说明:这里Window.setCallback函数将activty以this的方式注册进PhoneWindow中,所以cb就是activty,在PhoneWindow初始化时会生成DecorView对象,该函数中传入的mFeatureId是-1,所以此处会调用Activity的dispatchKeyEvent函数,开始在View中分发按键。因此这里继续分析Activity.dispatchKeyEvent,代码实现如下:

public boolean dispatchKeyEvent(KeyEvent event) {
    // 调用自定义 onUserInteraction
    onUserInteraction();
    if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
            mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
        return true;
    }
    // 实际调用的是DecorView的superDispatchKeyEvent
    //从DecorView开始从顶层View往子视图传递
    Window win = getWindow();
    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的实现,代码如下:

    @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }

继续分析Decor的superDispatchKeyEvent实现,代码如下:

public boolean superDispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        final int action = event.getAction();
        if (mActionMode != null) {
            if (action == KeyEvent.ACTION_UP) {
                mActionMode.finish();
            }
            return true;
        }
    }
    return super.dispatchKeyEvent(event);
}

这里进入到View的层次结构,接着会调用ViewGroup.dispatchKeyEvent方法,代码实现如下:

public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
        }
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}

这里处理逻辑是:如果ViewGroup是focused并且具体的大小被设置了(有边界)则交给它处理,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理。这里可以看出如果ViewGroup满足条件,则优先处理事件而不发给子视图去处理。下面看下View的dispatchKeyEvent实现,代码如下:

public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 0);
    }
	
    ListenerInfo li = mListenerInfo;
    //调用onKeyListener
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }
    //调用View的onKeyUp/onKeyDown
    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

这里如果注册了OnKeyListener,并且View属于Enable状态,则触发调用OnKeyListener,然后直接返回true;如果没有,则调用view的onKeyUp/onKeyDown方法并返回true。接下来分析View的onKeyUp/onKeyDown方法

onKeyUp代码如下所示:

public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (KeyEvent.isConfirmKey(keyCode)) {
        if ((mViewFlags & ENABLED_MASK) == DISABLED) {
            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;
}

onKeyDown代码如下所示:

public boolean onKeyDown(int keyCode, KeyEvent event) {
    boolean result = false;

    if (KeyEvent.isConfirmKey(keyCode)) {
        if ((mViewFlags & ENABLED_MASK) == DISABLED) {
            return true;
        }
        // Long clickable items don't necessarily have to be clickable
        if (((mViewFlags & CLICKABLE) == CLICKABLE ||
                (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
                (event.getRepeatCount() == 0)) { //第一次down事件
            setPressed(true); //标记pressed,你可能设置了View不同的background
            checkForLongClick(0);
            return true;
        }
    }
    return result;
}

注意:View层面如果不能处理,则会调用Activity的回调方法来处理,实际上 Activity的onKeyDown和onKeyUp无法收到KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件,因此当View收到这样的消息后也处理不了回优先Disable掉且返回true

最后分析 Activity.onKeyDown/onKeyUp

onKeyUp代码如下所示:

public boolean onKeyUp(int keyCode, KeyEvent event) {
    //如果是back键并且正在追踪该Event,则调用onBackPressed
    if (getApplicationInfo().targetSdkVersion
            >= Build.VERSION_CODES.ECLAIR) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                && !event.isCanceled()) {
            onBackPressed();
            return true;
        }
    }
    return false;
}

onKeyDown代码如下所示:

public boolean onKeyDown(int keyCode, KeyEvent event)  {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        //如果是back键则启动追踪
        if (getApplicationInfo().targetSdkVersion
                >= Build.VERSION_CODES.ECLAIR) {
            event.startTracking();
        } else {
            onBackPressed();
        }
        return true;
    }
	//...
}

整个流程总结如下:

=>Activity的dispatchKeyEvent 
==>onUserInteraction
==>PhoneWindow的superDispatchKeyEvent 
===>DecorView的superDispatchKeyEvent,DecorView的superDispatchKeyEvent再调用父类的dispatchKeyEvent 
===>DecorView是窗口中的顶级视图,按键从DecorView开始往子节点分发,实际调用ViewGroup的dispatchKeyEvent
===>ViewGroup中会先判断是否可以处理KeyEvent;如果可以则调用父类(View)的dispatchKeyEvent,如果当前的ViewGroup不满足条件,则调用mFocused的dispatchKeyEvent,这里的mFocused是焦点子视图,也可以是含有焦点子视图的ViewGroup,因此这里可能会发生递归调用。 
====> 在View的dispatchKeyEvent中会先调用onKey函数,即会调用各个View注册的View.OnKeyListener对象的接口 
====> 在View的dispatchKeyEvent中接着调用KeyEvent的dispatch函数,因为View实现了Window.Callback函数,因此会调用View的onKeyDown/onKeyUp/onKeyMultiple函数
==>调用KeyEvent的dispatch函数,因为Activity实现了Window.Callback函数,因此会调用Activity的onKeyDown/onKeyUp/onKeyMultiple函数 
=>如果整个View层次都没有返回true,则调用PhoneWindow的onKeyDown/onKeyUp函数

至此,整个InputStage的分析到此结束。最后用一张图说明下onKeyUp/onKeyDown涉及的各个类之间的关系,如下所示:

这样整个View与Activity以及和KeyEvent.Callback之间的关系,以及View系统之间的关系就很清楚了。

猜你喜欢

转载自blog.csdn.net/vviccc/article/details/93377708
今日推荐