美文网首页
Android8.0 按键事件处理流程(一)

Android8.0 按键事件处理流程(一)

作者: 小的橘子 | 来源:发表于2019-03-05 16:00 被阅读0次

    此处记录按键事件从Framework到应用层的传递流程。WMS中接收到消息后,会调用ViewRootImpl中的dispatchInputEvent方法,

    附上核心流程图


    Android按键事件流程

    输入事件认识

    Android所有输入事件都会封装为InputEvent事件然后进行分发,InputEvent又分为两种类型,实体按键事件(KeyEvent),触摸事件(MotionEvent)。这些事件流入到上层之后才会进行分别进行处理。

    下面源码分析

    按键事件流入

    InputEvent就包含了KeyEvent,接下来就看该输入事件如何传递和分别处理的,首先是ViewRootImpl的dispatchInputEvent方法

    frameworks/base/core/java/android/view/ViewRootImpl.java

    public void dispatchInputEvent(InputEvent event) {
        dispatchInputEvent(event, null);
    }
    
    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = event;
        args.arg2 = receiver; // 此处receiver为null
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
        msg.setAsynchronous(true);
        //发送MSG_DISPATCH_INPUT_EVENT消息
        mHandler.sendMessage(msg); 
    }
    

    mHandler是其内部类ViewRootHandler,接收到消息如下

    final class ViewRootHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            ...
            case MSG_DISPATCH_INPUT_EVENT: {
                SomeArgs args = (SomeArgs)msg.obj;
                InputEvent event = (InputEvent)args.arg1;
                InputEventReceiver receiver = (InputEventReceiver)args.arg2;
                enqueueInputEvent(event, receiver, 0, true);
                args.recycle();
            } break;
        }
    }
    

    走enqueueInputEvent方法

    void enqueueInputEvent(InputEvent event,
                InputEventReceiver receiver, int flags, boolean processImmediately) {
        adjustInputEventForCompatibility(event);
        // 1. 将输入事件event封装为QueuedInputEvent
        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 {
            // 2. 追加新事件到mPendingInputEventTail上,形成事件链表
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        mPendingInputEventCount += 1;
        if (processImmediately) {
            // 3. 处理输入事件
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents(); 
        }
    }
    
    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;
            ...
            // 4. 分发QueuedInputEvent队列中的所有事件
            deliverInputEvent(q); 
        }
        ...
    }
    

    InputEvent事件会形成一个事件链表,最后循环分发链表队列中的事件

    private void deliverInputEvent(QueuedInputEvent q) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }
    
        // 关于InputStage下面详解,它表示了输入事件的一个分发阶段,eg: ime之前处理,ime处理,ime之后处理等
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            // 是否跳过键盘消息(IME),如果true,返回EarlyPostImeInputStage对象,否则返回NativePreImeInputStage对象
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }
    
        if (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
    

    NativePreImeInputStage是在ViewRootImpl中的setView()创建,而setview方法是在完成view绘制时调用的。

    ViewRootImpl.java

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                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;
            }
        }
    }
    

    先接着看deliverInputEvent方法中的stage.deliver(q);

    abstract class InputStage {
        /**
         * Delivers an event to be processed.
         */
        public final void deliver(QueuedInputEvent q) {
            /// M: [ANR] Add for monitoring stage status. {
            ViewDebugManager.getInstance().debugInputStageDeliverd(this,
                    System.currentTimeMillis());
            /// }
            // 当前事件还没有处理,因此不包含FLAG_FINISHED标致
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
                // 一般不会丢弃输入事件
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                ViewDebugManager.getInstance().debugInputDispatchState(q.mEvent, this.toString());
                // 1. 执行apply方法,传入onProcess的返回结果,该方法由子类重写
                apply(q, onProcess(q));
            }
        }
    
        ...
        /**
         * Applies a result code from {@link #onProcess} to the specified event.
         */
        protected void apply(QueuedInputEvent q, int result) {
            if (result == FORWARD) {
                forward(q);
            } else if (result == FINISH_HANDLED) {
                finish(q, true);
            } else if (result == FINISH_NOT_HANDLED) {
                finish(q, false);
            } else {
                throw new IllegalArgumentException("Invalid result: " + result);
            }
        }
    
        /**
         * Called when an event is ready to be processed.
         * @return A result code indicating how the event was handled.
         */
        protected int onProcess(QueuedInputEvent q) {
            return FORWARD;
        }
        ...
    }
    

    上面提到的所有InputState都继承自InputStage
    如果不跳过IME消息,则inputStage为NativePreImeInputStage,其是由ViewPreImeInputStage,ImeInputStage,EarlyPostImeInputStage,NativePostImeInputStage,ViewPostImeInputStage,SyntheticInputStage作为嵌套参数构成的,所以调用NativePreImeInputStage的deliver(q),会依次调用到每个InputState的子类的onProcess()方法

    这里先介绍下InputStage

    InputStage

    输入事件的传递过程如下,每个前面处理事件的阶段都有拦截传递的能力。

    1. NativePreImeInputStage 分发早于IME的InputEvent事件到NativeActivity中去处理, NativeActivity和普通acitivty的功能区别不大,只是很多代码都在native层去实现,这样执行效率更高,并且NativeActivity在游戏开发中很实用。 不支持触摸事件。
    2. ViewPreImeInputStage 分发早于IME的InputEvent到View框架处理,会调用acitivity的所有view的onkeyPreIme方法,这样就给View在输入法处理key事件之前先得到消息并处理的机会。 不支持触摸事件
    3. ImeInputStage 分发InputEvent到IME处理 ImeInputStage的onProcess方法会调用InputMethodManager的dispatchInputEvent方法处理消息。 不支持触摸事件。
    4. EarlyPostImeInputStage 输入法之后输入事件就会流到该阶段,此时 屏幕上有焦点的View会高亮显示,用来提示用户焦点所在。支持触摸事件。
    5. NativePostImeInputStage 分发InputEvent事件到NativeActivity,为了让IME处理完消息后能先于普通的Activity处理消息。此时支持触摸事件。
    6. ViewPostImeInputStage 分发InputEvent事件到View框架,支持触摸事件。
    7. SyntheticInputStage 未处理的InputEvent都会传到这个阶段,例如手机上的虚拟按键消息

    所有InputStage类的构造方法都会传入一个InputStage类的变量,这样最终会形成流水线线式的处理结构,也就是采用了责任链模式,每经过一个InputStage对象的处理都会进行判断,看是否还需要将 events继续向前传输,如果需要就调用forward()函数让该变量中存储的下一个InputStage对象处理该events,如果不需要就调用finish()函数结束events的传输

    我们所关心的View框架的输入事件,故着重分析ViewPostImeInputStage

    /**
         * Delivers post-ime input events to the view hierarchy.
         */
    final class ViewPostImeInputStage extends InputStage {
        ...
        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                // 处理按键事件
                return processKeyEvent(q); 
            } else {
                // else中处理触摸事件,触摸事件又会根据不同类型的触摸做不同的处理,例如鼠标触摸,轨迹球(Android很早的一种交互方式,现在没有手机用)触摸,一般的手指触摸
                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);
                }
            }
        }
    }
    

    此处我们分析按键事件

    private int processKeyEvent(QueuedInputEvent q) {
        // 转为KeyEvent事件
        final KeyEvent event = (KeyEvent)q.mEvent; 
    
        // Deliver the key to the view hierarchy.
        // 1. 先由DecorView进行按键事件派发
        if (mView.dispatchKeyEvent(event)) {
            return FINISH_HANDLED;
        }
        ...
        int groupNavigationDirection = 0; 
        
        // 根据TAB和SHIFT键的按下来判断焦点方向为向前还是向后
        if (event.getAction() == KeyEvent.ACTION_DOWN
            && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
            if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
                groupNavigationDirection = View.FOCUS_FORWARD;
            } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
                                                      KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
                groupNavigationDirection = View.FOCUS_BACKWARD;
            }
        }
    
        // If a modifier is held, try to interpret the key as a shortcut.
        if (event.getAction() == KeyEvent.ACTION_DOWN
            && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
            && event.getRepeatCount() == 0
            && !KeyEvent.isModifierKey(event.getKeyCode())
            && groupNavigationDirection == 0) {
            // 交由DecorView处理快捷键分发
            if (mView.dispatchKeyShortcutEvent(event)) {
                return FINISH_HANDLED;
            }
            ...
        }
        // mFallbackEventHandler会处理系统的一些按键,针对的是所有窗口
        // Apply the fallback event policy.
        if (mFallbackEventHandler.dispatchKeyEvent(event)) {
            return FINISH_HANDLED;
        }
        ...
        // Handle automatic focus changes.
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            if (groupNavigationDirection != 0) {
                if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                    return FINISH_HANDLED;
                }
            } else {
                // 2. 处理键盘的上下左右的焦点查找
                if (performFocusNavigation(event)) {
                    return FINISH_HANDLED;
                }
            }
        }
        return FORWARD;
    }
    

    注释1处,mView处理按键事件mView具体指的是? 如果是Activity和Dialog,mView就是DecorView,是所有view的根;如果是Toast,mView是id为com.android.internal.R.id.message,这点在Toast.makeText方法中可以看出。此处只分析Activity。

    本文主要分析View框架的按键事件派发

    按键事件派发

    1. DecorView.dispatchKeyEvent方法

    DecorView.java

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        final int action = event.getAction();
        final boolean isDown = action == KeyEvent.ACTION_DOWN;
    
        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;
                }
            }
        }
    
        if (!mWindow.isDestroyed()) {
            // cb是Activiy或者Dialog,我们只分析Activity 
            // mFeatureId在installDecor是构造的DecorView传入的为-1,故调用Activity的dispatchKeyEvent
            final Window.Callback cb = mWindow.getCallback();
            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)  
                : super.dispatchKeyEvent(event);
            if (handled) {
                return true;
            }
        }
    
        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
            : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }
    

    2. Activity的dispatchKeyEvent

    Activity.java

    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();
    
        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        final int keyCode = event.getKeyCode();
        // 如果按键是menu事件,则先回调Actionbar的onMenuKeyEvent()事件处理,如果返回没有处理才会继续往下走
        if (keyCode == KeyEvent.KEYCODE_MENU &&
            mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }
        
        Window win = getWindow();
        // 1. 调用Phonewindow的superDispatchKeyEvent,最终会调用到DecorView的dispatchKeyEvent方法中
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        // 2. 如果Phonewindow 分发后返回false,则交由KeyEvent派发事件,调用Activity的onKeyDown/Up()方法
        return event.dispatch(this, decor != null
                              ? decor.getKeyDispatcherState() : null, this);
    }
    

    继续分析注释1,看如何派发输入事件到ViewGroup

    PhoneWindow.java

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

    发现又进入到DecorView,但这次调用的方法是superDispatchKeyEvent

    DecorView.java

    public boolean superDispatchKeyEvent(KeyEvent event) {
        // Give priority to closing action modes if applicable.
        // 对BACK按键做处理,如果存在ActionMode则先退出ActionMode,ActionMode相当于一个临时的ActionBar,具体使用还是google吧,此处不是重点
        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;
            }
        }
        // 1. DecorView父类是FrameLayout,但其没有实现dispatchKeyEvent方法,故该处调用的是ViewGroup的方法
        return super.dispatchKeyEvent(event);
    }
    

    注释1会调用其父类ViewGroup的dispatchKeyEvent()方法

    3. ViewGroup的dispatchKeyEvent

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }
        // 1. 如果viewgroup获得焦点且边界确定,则调用父view的也就是View的dispatchKeyEvent
        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) {
            // 2. 交由获取焦点的子view进行按键事件的派发
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }
    
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }
    

    注释2中mFocused存在于每个ViewGroup,其标识了ViewGroup的直接子View是否拥有或者包含焦点,通过mFocused.dispatchKeyEvent即可递归调用找到最终获取焦点的View,然后调用该View的dispatchKeyEvent()方法,如注释1.

    4. View的dispatchKeyEvent

    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;
        // 1. 当对view设置了OnKeyListener,且该view处于enabled状态,则调用OnKeyListener的onKey()方法
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }
        // 2. KeyEvent派发事件,receiver为view,会回调View的onKeyDown/Up()方法
        if (event.dispatch(this, mAttachInfo != null
                           ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }
    
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }
    

    注释1中对View是否设置了OnKeyListener做了判断,如果设置,则先回调onKey()方法

    注释2,在onKey()返回false情况下会通过KeyEvent的dispatch方法调用View的onKeyDown/Up()方法。

    KeyEvent的dispatch(),该方法在View#dispatchKeyEvent()方法中,如果onKey()返回false会调用,参数传入的是View;该方法也会在Activity#dispatchKeyEvent()方法中win.superDispatchKeyEvent()方法返回false后进行调用,只不过参数传入的是Activity,最终会调用Activity或者View的onKeyDown/Up()方法。

    KeyEvent.java

    public final boolean dispatch(Callback receiver, DispatcherState state,
                Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                mFlags &= ~FLAG_START_TRACKING; 
                // 1. 执行Activity或者View的onKeyDown()方法
                boolean res = receiver.onKeyDown(mKeyCode, this); 
                if (state != null) {
                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                        state.startTracking(this, target);
                    } else if (isLongPress() && state.isTracking(this)) {
                        try {
                            if (receiver.onKeyLongPress(mKeyCode, this)) {
                                state.performedLongPress(this);
                                res = true;
                            }
                        } catch (AbstractMethodError e) {
                        }
                    }
                }
                return res;
            }
            case ACTION_UP:
                if (state != null) {
                    state.handleUpEvent(this);
                }
                // 2. 执行Activity或者View的onKeyUp()方法
                return receiver.onKeyUp(mKeyCode, this); 
           ...
        }
        return false;
    }
    

    receiver可能是Activity对象,也可能是view对象,具体情况具体分析

    View对象的onKeyDown(),onKeyUp()

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // isConfirmKey中会对keycode判断是否是KEYCODE_DPAD_CENTER,KEYCODE_ENTER,KEYCODE_SPACE,KEYCODE_NUMPAD_ENTER几个表示确定的键,也就是可以触发点击作用的键
        if (KeyEvent.isConfirmKey(keyCode)) {
            // 如果view处于DISABLED状态,则直接返回true
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }
    
            if (event.getRepeatCount() == 0) {
                // Long clickable items don't necessarily have to be clickable.
                // View的CLICKABLE 和 LONG_CLICABLE是独立的,互不影响
                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
                    // 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;
                    if (clickable) {
                        // 设置按下状态,比如更换view颜色,切换图片等
                        setPressed(true, x, y);
                    }
                    // 该方法做的事情是发送一个可以判断长按时间的演示runnable,时间一到则执行长按操作
                    checkForLongClick(0, x, y);
                    return true;
                }
            }
        }
    
        return false;
    }
    
    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();
                    if (!event.isCanceled()) {
                        // 回调OnClickListener的onClick()方法
                        return performClick();
                    }
                }
            }
        }
        return false;
    }
    

    如果View设置了onClickListener()方法,则按下enter键后抬起时会执行onClick()方法

    如果View的onKeyDown/Up()也返回false,则dispatchKeyEvent()方法会一直返回至Activity中,交由其onKeyDown/Up()处理

    Activity对象的onKeyDown(),onKeyUp()

    public boolean onKeyDown(int keyCode, KeyEvent event)  {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (getApplicationInfo().targetSdkVersion
                >= Build.VERSION_CODES.ECLAIR) {
                // >= android2.1则跟踪按键传递过程
                event.startTracking();
            } else {
                // android2.1之前按下返回键直接返回
                onBackPressed();
            }
            return true;
        }
    
        ...
            return handled;
        }
    }
    
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        // 如果android版本>= 2.1 回调onBackPressed()方法退出Activity
        if (getApplicationInfo().targetSdkVersion
            >= Build.VERSION_CODES.ECLAIR) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                && !event.isCanceled()) {
                onBackPressed();
                return true;
            }
        }
        return false;
    }
    

    如果Activity里面的任何view、布局都没有处理按键,就会传递到Activity的onKeyDown,onKeyUp。比如,当在EditText中输入文字时,Activity的onKeyDown,onKeyUp不会接收到按键事件,因为EditText有自己的处理按键事件的方法,如果此时把焦点从EditText移走,onKeyDown,onKeyUp就会接收到按键事件。

    Activity中onKeyDown/Up()也不做处理返回false时,事件一路返回至DecorView的dispatchKeyEvent()方法中,此时继续调用PhoneWindow的onKeyDown/Up()方法

    PhoneWindow的onKeyDown/Up()

    PhoneWindow.java

    protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
    
        final KeyEvent.DispatcherState dispatcher =
            mDecor != null ? mDecor.getKeyDispatcherState() : null;
    
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                // If we have a session send it the volume command, otherwise
                // use the suggested stream.
                if (mMediaController != null) {
                    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;
                    }
                    mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
                } else {
                    MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
                        event, mVolumeControlStreamType, false);
                }
                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;
    }
    

    onKeyDown/onKeyUp方法主要针对当前获得焦点的窗口对一些特殊按键进行处理,包括音量+/-,多媒体控制按键,MENU,BACK

    总结

    按键事件从Framework层到View框架整体流程如流程图,我们主要关心是Activity,ViewGroup和View的事件派发。

    Activity可以通过dispatchKeyEvent()将KeyEvent派发给ViewGroup直到找到获取焦点的View(当然可能就是ViewGroup获取焦点),获取焦点的View先去判断OnKeyListener存在与否,存在回调onKey(),如果不存在或者返回false,则回调其onKeyDown/Up()方法,onClick()方法在onKeyUp()方法中会进行回调,此时如果还是返回false,则Activity中的onKeyDown/Up()方法得以调用,最后没有处理则交给PhoneWindow的onKeyDown/Up()。我们也可以通过重写对应方法来达到事件消费,也就是不继续走剩余事件传递流程。

    注意:PhoneFallbackEventHandler在ViewRoolImpl中提到过,其也是对特殊按键进行处理,但是那是针对所有的窗口,包括当前获得焦点的窗口,而PhoneWindow只针对当前获得焦点的窗口。PhoneFallbackEventHandler是在使用代码如下

    ViewRootImpl.java

    private int processKeyEvent(QueuedInputEvent q) {
        ...
        // Apply the fallback event policy. mFallbackEventHandler就是PhoneFallbackEventHandler
        if (mFallbackEventHandler.dispatchKeyEvent(event)) {
            return FINISH_HANDLED;
        }
        ...
    }
    

    PhoneFallbackEventHandler

    public boolean dispatchKeyEvent(KeyEvent event) {
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
    
        if (action == KeyEvent.ACTION_DOWN) {
            return onKeyDown(keyCode, event);
        } else {
            return onKeyUp(keyCode, event);
        }
    }
    boolean onKeyDown(int keyCode, KeyEvent event) {
        final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
    
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
                        event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                return true;
            }
    
    
            case KeyEvent.KEYCODE_MEDIA_PLAY:
            case KeyEvent.KEYCODE_MEDIA_PAUSE:
            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                /* Suppress PLAY/PAUSE toggle when phone is ringing or in-call
                 * to avoid music playback */
                if (getTelephonyManager().getCallState() != TelephonyManager.CALL_STATE_IDLE) {
                    return true;  // suppress key event
                }
            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:
            case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
                handleMediaKeyEvent(event);
                return true;
            }
    
            case KeyEvent.KEYCODE_CALL: {
            ...
        }
    }
    

    主要是针对音量键,媒体相关,Call和Camera键进行处理。

    特殊按键处理

    第一次拦截在interceptKeyBeforeQueueing中,
    result &= ~ACTION_PASS_TO_USER或者0,将会拦截事件,不在向下传递



    第二次在拦截在interceptKeyBeforeDispatching,
    return -1,事件将被拦截不在向下传递


    例如对Home键做操作,由于Home并不会下发至View,故应用是无法监听,但我们可以在interceptKeyBeforeDispatching中对其进行客制化,例如将其remap返回键KEYCODE_BACK下发,这时PhoneWindowManager就会收到被remap后的KEYCODE_BACK,进而最后交由View框架处理.
    PhoneWindowManager.java

    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
            final boolean keyguardOn = keyguardOn();
            final int keyCode = event.getKeyCode();
            final int repeatCount = event.getRepeatCount();
            final int metaState = event.getMetaState();
            final int flags = event.getFlags();
            final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
            ......
            if (keyCode == KeyEvent.KEYCODE_HOME) {
                // If we have released the home key, and didn't do anything else
                // while it was pressed, then it is time to go home!
                if (!down) {
                    // wangyannan begin
                        Log.i("wangyannan","foreground");
                        /**
                        *通过inputManager注入KEYCODE_BACK事件,相当于在按home键时接收到的为back事件
                        */
                        InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
                        KeyEvent ke1 = new KeyEvent(event.getDownTime(), event.getEventTime(),
                        KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, event.getRepeatCount());
                        KeyEvent ke2 = new KeyEvent(event.getDownTime(), event.getEventTime(),
                        KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, event.getRepeatCount());                                     
                        inputManager.injectInputEvent(ke1, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
                        inputManager.injectInputEvent(ke2, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
                        return -1;
                    }
                    //wangyannan end
                    cancelPreloadRecentApps();
    
                    ......
                    handleShortPressOnHome();
                    return -1;//返回-1则不响应home键
                }
                ......
            }
    }
    //wangyannan begin
    

    例如某个Activity监听按键,对HOME键做处理
    XXXActivity.java

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode){
            case KeyEvent.KEYCODE_BACK:
                // 如果该条件成立,则为系统原本的按键事件,否则为重定向的时间
                if(event.getFlags()!=0&&event.getDeviceId()!=-1&&event.getSource()!=0){
                    //实际接收到的back事件
                    Toast.makeText(this,"没有重定向",Toast.LENGTH_SHORT).show();
                }else{
                    //接受到的back事件为代码中注入的back事件
                    Toast.makeText(this,"重定向",Toast.LENGTH_SHORT).show();
                }
                return ture;
        }
        return super.onKeyDown(keyCode, event);
    }
    

    相关文章

      网友评论

          本文标题:Android8.0 按键事件处理流程(一)

          本文链接:https://www.haomeiwen.com/subject/cehouqtx.html