(四十八) Android O 事件下发流程学习

前言:在之前博客(四十七) 蓝牙自拍杆原理学习 写到原理其实就是event事件的下发,那下发流程到底是怎么样的呢,探究一下=-=


demo: https://github.com/happyjiatai/demo_csdn/tree/master/demo_47_bluetoothzipaigan


1. 获取调用堆栈

修改下测试demo打印调用堆栈

demo:

package com.example.demo_47_bluetoothzipaigan;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "jiatai";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG, "onKeyDown : "+ keyCode);
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
                Log.d(TAG, "KeyEvent.KEYCODE_VOLUME_UP");
                return true;
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                Log.d(TAG, "KeyEvent.KEYCODE_VOLUME_DOWN");
                return true;
            default:
                break;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
Log.d(TAG, "onKeyUp", new RuntimeException());
        //Log.d(TAG, "onKeyUp : "+ keyCode);
        return super.onKeyUp(keyCode, event);
    }
}

抓取到的调用堆栈

06-22 20:03:51.584 23239-23239/com.example.demo_47_bluetoothzipaigan D/jiatai: onKeyUp
    java.lang.RuntimeException
        at com.example.demo_47_bluetoothzipaigan.MainActivity.onKeyUp(MainActivity.java:35)
        at android.view.KeyEvent.dispatch(KeyEvent.java:2715)
        at android.app.Activity.dispatchKeyEvent(Activity.java:3320)
        at android.support.v7.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:534)
        at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:58)
        at android.support.v7.app.AppCompatDelegateImplBase$AppCompatWindowCallbackBase.dispatchKeyEvent(AppCompatDelegateImplBase.java:316)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:354)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4768)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4640)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4175)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4228)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4194)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4321)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4202)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4378)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4175)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4228)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4194)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4202)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4175)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4228)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4194)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4354)
        at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4522)
        at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2458)
        at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2021)
        at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2012)
        at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2435)
        at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:331)
        at android.os.Looper.loop(Looper.java:147)
        at android.app.ActivityThread.main(ActivityThread.java:6684)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)


2. 流程探究

从上面的调用堆栈可以看出事件的keyEvent下发经过了

InputEventSender-InputMethodManager-ViewRootImpl-DecorView-AppCompatDelegateImplBase-WindowCallbackWrapper-Activity-KeyEvent-MainActivity

事件下发的流程个人感觉没意思,意义不是很大,按照上面的类和方法跟踪就好了,我比较感兴趣是事件处理,其实分了3部分,分为Actionbar、Window和activity,这三部分按顺序拦截,只要之前的拦截了事件下面的就没份了。

Activity.java

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

2.1 ActionBar

        // 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;
        }

actionbar拦截了KeyEvent.KEYCODE_MENU,actionbar大家应该都知道右上角会有三个点,点开是会显示几个选项的菜单(话说现在还有菜单键么,一般不都是返回、home和recent么)


2.2 window

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }

window初始化相关代码是在activity的attach里调用的

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }

而PhoneWindow的superDispatchKeyEvent

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

继续调用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;
            }
        }

        return super.dispatchKeyEvent(event);
    }

DecoreView的super是ViewGroup,关注下这边判断了PFLAG_FOCUSED,说明view是需要获取焦点才能响应KeyEvent的。

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

ViewGroup的super是view

    /**
     * Dispatch a key event to the next view on the focus path. This path runs
     * from the top of the view tree down to the currently focused view. If this
     * view has focus, it will dispatch to itself. Otherwise it will dispatch
     * the next node down the focus path. This method also fires any key
     * listeners.
     *
     * @param event The key event to be dispatched.
     * @return True if the event was handled, false otherwise.
     */
    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;
    }

这里可以看出window拦截keyEvent可以通过设置view的onKeyListener来拦截

我们再改造一下demo:

package com.example.demo_47_bluetoothzipaigan;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "bluetoothzipaigan";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = findViewById(R.id.button);
        btn.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                Log.d(TAG, "Button OnKeyListener : "+ keyCode);
                if (KeyEvent.KEYCODE_VOLUME_UP == keyCode) {
                   return true;
                }
                return false;
            }
        });
        //btn.setFocusable(true);

        EditText editText= findViewById(R.id.editText);
        editText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                Log.d(TAG, "editText OnKeyListener : "+ keyCode);
                if (KeyEvent.KEYCODE_VOLUME_UP == keyCode) {
                    return true;
                }
                return false;
            }
        });
        editText.setFocusable(true);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG, "onKeyDown : "+ keyCode);
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
                Log.d(TAG, "KeyEvent.KEYCODE_VOLUME_UP");
                return true;
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                Log.d(TAG, "KeyEvent.KEYCODE_VOLUME_DOWN");
                return true;
            default:
                break;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        //Log.d(TAG, "onKeyUp", new RuntimeException());
        Log.d(TAG, "onKeyUp : "+ keyCode);
        return super.onKeyUp(keyCode, event);
    }
}

对应log:

06-22 21:10:31.031 26393-26393/com.example.demo_47_bluetoothzipaigan D/bluetoothzipaigan: editText OnKeyListener : 24
06-22 21:10:31.237 26393-26393/com.example.demo_47_bluetoothzipaigan D/bluetoothzipaigan: editText OnKeyListener : 24
对比使用button即使设置了setFocusable(ture)也是不行的,button看样子无法获取焦点,而EditText天生可以。EditText拦截了KeyEvent,那么activity的onKeyDown和onKeyUp就不会收到下发的事件也就不会再继续处理了。

看源码调用setFocusable是使其获取焦点的能力,并没有说获取了焦点。

    /**
     * Set whether this view can receive the focus.
     * <p>
     * Setting this to false will also ensure that this view is not focusable
     * in touch mode.
     *
     * @param focusable If true, this view can receive the focus.
     *
     * @see #setFocusableInTouchMode(boolean)
     * @see #setFocusable(int)
     * @attr ref android.R.styleable#View_focusable
     */
    public void setFocusable(boolean focusable) {
        setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
    }



2.3 activity

其实第三个拦截者说是activity不大好,按源码说法叫做receiver,也就是接收者,但我截的是activity的源码,所以这个this代指的就是activity。

        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);

流程走到上面继续往下就是走到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;
    }

这边根据action的不同回调不同的activity方法,比如我们是按的volume up,那对应的action是ACTION_UP。

继而就是走到receiver.onKeyUp(mKeyCode, this)里去。

    /**
     * Called when a key was released 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>The default implementation handles KEYCODE_BACK to stop the activity
     * and go back.
     *
     * @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 #onKeyDown
     * @see KeyEvent
     */
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (getApplicationInfo().targetSdkVersion
                >= Build.VERSION_CODES.ECLAIR) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                    && !event.isCanceled()) {
                onBackPressed();
                return true;
            }
        }
        return false;
    }
我们平常按返回键可以起作用就是这边activity有个默认实现,当按键是返回键的时候,调用onBackPressed()方法。



3. 总结

KeyEvent的下发会受到ActionBar、获取了焦点的View和activity的依次拦截,只要其中一方处理完成返回true下面的成员就不会接着处理了。

猜你喜欢

转载自blog.csdn.net/sinat_20059415/article/details/80778115