美文网首页
Android 硬按键传值流程

Android 硬按键传值流程

作者: 月影路西法 | 来源:发表于2019-08-19 11:31 被阅读0次

我们在写应用的过程中,监听back键都是使用的onKeyDown的方法

/**

* 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();

        }else {

onBackPressed();

        }

return true;

    }

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 if (keyCode == KeyEvent.KEYCODE_TAB) {

// Don't consume TAB here since it's used for navigation. Arrow keys

// aren't considered "typing keys" so they already won't get consumed.

        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;

    }

}

其中键值都是从KeyEvent传过来的,那么back这个键值是从哪过来的呢

public class KeyEvent extends InputEvent implements Parcelable 

KeyEvent继承了InputEvent,在KeyEvent类中有一个Callback接口

    public interface Callback {
        /**
         * Called when a key down event has occurred.  If you return true,
         * you can first call {@link KeyEvent#startTracking()
         * KeyEvent.startTracking()} to have the framework track the event
         * through its {@link #onKeyUp(int, KeyEvent)} and also call your
         * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
         *
         * @param keyCode The value in event.getKeyCode().
         * @param event Description of the key event.
         *
         * @return If you handled the event, return true.  If you want to allow
         *         the event to be handled by the next receiver, return false.
         */
        boolean onKeyDown(int keyCode, KeyEvent event);

在这个接口中我们看到了OnKeyDown的方法,从名字就可以看出这是一个回调方法


image.png

从图中我们可以看到大部分的方法都是调用了super.OnKeyDown,但是在KeyEvent类中又调用了这个方法的痕迹

    /**
     * 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);
                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);
                }
                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;
    }
image.png

我们在往上倒,发现在Activity中调用了此方法

    /**
     * Called to process key events.  You can override this to intercept all
     * key events before they are dispatched to the window.  Be sure to call
     * this implementation for key events that should be handled normally.
     *
     * @param event The key event.
     *
     * @return boolean Return true if this event was consumed.
     */
    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();
        if (keyCode == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

看到这个方法咋觉得那么的熟悉呢,从注释上看,这个方法是处理所有key event事件的


image.png

在网上倒发现在DecorView中有调用了这个方法,DecorView是activity的根view

@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()) {
            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);
    }

这还是一个被重写的方法
我们往上倒

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks 
public class FrameLayout extends ViewGroup {
public abstract class ViewGroup extends View implements ViewParent, ViewManager 
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

在view 与viewGroup中都有 dispatchKeyEvent方法
view中的dispatchKeyEvent

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;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

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

ViewGroup中的dispatchKeyEvent

    @Override
    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;
    }

有点偏了,那么我们接着看看谁调用了DecorView的dispatchKeyEvent方法吧


image.png

看到了PhoneWindow这个类,大家都是DecorView的外面都有一层PhoneWindow,那么肯定是这个类啦
进入到PhoneWindow中全局搜索没有搜索到dispatchKeyEvent方法调用的地,难道不在这里面?


image.png
按照之前方法,我们看到在ViewRootImpl类中有调用此方法的地方,在ViewGroup中也有,暂时先忽略
ViewRootImpl->processKeyEvent
        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mUnhandledKeyManager.preViewDispatch(event)) {
                return FINISH_HANDLED;
            }

            // Deliver the key to the view hierarchy.
          //再次调用了方法
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

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

            // This dispatch is for windows that don't have a Window.Callback. Otherwise,
            // the Window.Callback usually will have already called this (see
            // DecorView.superDispatchKeyEvent) leaving this call a no-op.
            if (mUnhandledKeyManager.dispatch(mView, event)) {
                return FINISH_HANDLED;
            }

            int groupNavigationDirection = 0;

            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) {
                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.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;
        }

我再一次往上倒
ViewRootImpl->ViewPostImeInputStage->onProcess
ViewRootImpl->deliverapply->apply->onProcess
ViewRootImpl->deliverInputEvent->deliver
ViewRootImpl->doProcessInputEvents->deliverInputEvent
enqueueInputEvent->doProcessInputEvents


image.png

追到这里我好到有个handle,这个handle之前好像在别的帖子中见到过,应该就是这里

    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = event;
        args.arg2 = receiver;
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }

调用这个方法发送的handle
public void dispatchInputEvent(InputEvent event)从这android studio就网上倒不上去了
我们回过头来,看看PhoneWindow中有没有掉用到这个方法


image.png

哈哈,果然有但是还是往上追不了,我们先看看PhoneWindow继承了谁吧

public class PhoneWindow extends Window implements MenuBuilder.Callback {

继承了Window,在Window中有这个方法

    @Override
    public void injectInputEvent(InputEvent event) {
        getViewRootImpl().dispatchInputEvent(event);
    }

追到这里追不下去了。。。
记得刚才在ViewRootImpl的enqueueInputEvent方法有好几处调用它的地方,从这从新开始

    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);
        }

        @Override
        public void onBatchedInputEventPending() {
            if (mUnbufferedInputDispatch) {
                super.onBatchedInputEventPending();
            } else {
                scheduleConsumeBatchedInput();
            }
        }

        @Override
        public void dispose() {
            unscheduleConsumeBatchedInput();
            super.dispose();
        }
    }

WindowInputEventReceiver 这个类只有在ViewRootImpl的setView方法中有调用的地方

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
...
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }
...
}

到目前为止算是java层算是追到头了,暂时总结一下
方法依次执行的顺序是
ViewRootImpl.setView->
ViewRootImpl.WindowInputEventReceiver.onInputEvent->
ViewRootImpl.enqueueInputEvent->
ViewRootImpl.doProcessInputEvents->
ViewRootImpl.deliverInputEvent->
ViewRootImpl.InputStage.deliver->
ViewRootImpl.apply->
ViewRootImpl.ViewPostImeInputStage.onProcess->
ViewRootImpl.processKeyEvent->
DecorView.dispatchKeyEvent->
ViewGroup.dispatchKeyEvent->
view.dispatchKeyEvent->
Activity.dispatchKeyEvent->
KeyEvent.dispatch->
Activity.OnKeyDown

相关文章

网友评论

      本文标题:Android 硬按键传值流程

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