美文网首页
Android 输入事件简单记录

Android 输入事件简单记录

作者: 梧叶已秋声 | 来源:发表于2020-04-03 11:36 被阅读0次

根据官方文档 输入事件,事件的输入一般就2种,屏幕和按键的输入。

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

屏幕的输入事件的处理,可以参考这两篇。
安卓自定义View进阶-事件分发机制原理
安卓自定义View进阶-事件分发机制详解
Android 在事件分发过程中,有2种情况,继续向下分发(返回false,事件继续向下分发,如果到最终始终返回false,事件会再逐次向上传递到最开始的类中,如果该类也不需要,事件就被抛弃)或事件消费(即事件拦截,返回true表示消费了事件,则不再向下分发,事件被拦截)

按键的输入事件的处理,首先来看PhoneWindowManager中的interceptKeyBeforeQueueinginterceptKeyBeforeDispatching
interceptKeyBeforeQueueing中包括了大部分的按键,而interceptKeyBeforeDispatching中只有小部分的。

出处:https://www.iteye.com/blog/345757144-1965493
interceptKeyBeforeQueueing,其中包括了几乎所有按键的处理,interceptKeyBeforeDispatching主要处理Home键、Menu键、Search键等.

Android的按键输入事件分发之前,存在着2次拦截。
首先是PhoneWindowManager中的interceptKeyBeforeQueueing函数。

//  frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        if (!mSystemBooted) {
            // If we have not yet booted, don't let key events do anything.
            return 0;
        }
      ............
      final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
      ............
        // Handle special keys.
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK: {
                if (down) {
                    interceptBackKeyDown();
                } else {
                    boolean handled = interceptBackKeyUp(event);

                    // Don't pass back press to app if we've already handled it via long press
                    if (handled) {
                        result &= ~ACTION_PASS_TO_USER;
                    }
                }
                break;
            }
        ............
             case KeyEvent.KEYCODE_POWER: {
                // Any activity on the power button stops the accessibility shortcut
                cancelPendingAccessibilityShortcutAction();
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);
                }
                break;
            }
        ............
            }
        ............
        return result;
    }

当有事件输入(如按下按键),首先判断系统是否启动,如果没有启动则返回0,返回0则意味着事件被拦截,事件不再向下传递。当系统启动后,假设按下back键,程序会走到case KeyEvent.KEYCODE_BACK,按下back键,这里要注意,一个按键从按下到抬起,按键会响应2次。当一个按键按下的时候,event.getAction() = KeyEvent.ACTION_DOWN,也就是说down为true,会执行interceptBackKeyDown(),当按键抬起的时候,会执行else中的代码。

if (handled) {
// Don't pass back press to app if we've already handled it via long press
//result  = 0
 result &= ~ACTION_PASS_TO_USER;
}

由于这种运算符老忘所以这里做下记录。

//frameworks\base\core\java\android\view\WindowManagerPolicy.java
public final static int ACTION_PASS_TO_USER = 0x00000001;

result &= ~ACTION_PASS_TO_USER;

~ACTION_PASS_TO_USER是对ACTION_PASS_TO_USER取反,结果是0x11111110,&=是result = result & ~ACTION_PASS_TO_USER,因此result = result & 0x11111110
假设result 之前为0x0000 0000,进行运算。
0x0000 0000
0x1111 1110
———————
0x0000 0000
因此当输入事件被处理时,result = 0,即interceptKeyBeforeQueueing返回0,输入事件不再向下传递。

interceptKeyBeforeQueueing除了拦截还可以消费事件,例如当设备需要调节音乐的音量大小,但却缺少音量键,但是有上下左右按键,那么可以在 加上case KeyEvent.KEYCODE_DPAD_DOWN,然后判断Audiomanager.isMusicActive()(判断当前是否有音频运行),然后调用getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG)

//  frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
public class PhoneWindowManager implements WindowManagerPolicy {
  private AudioManager mAudioManager;
      ............
    @Override
    public void init(Context context, IWindowManager windowManager,
            WindowManagerFuncs windowManagerFuncs) {
            ..........
            if (mAudioManager == null){
            mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        }
            ..........
    }
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
      ............
      // Handle special keys.
        switch (keyCode) {
      ............
           case KeyEvent.KEYCODE_DPAD_DOWN:
                if (down){
                    if (mAudioManager.isMusicActive()){
                         dispatchAudioEvent(event,AudioManager.STREAM_MUSIC);
                    }
                    result &= ~ACTION_PASS_TO_USER;
                }
            break;
      ............
        }
      ............
    }
      ............
    private void dispatchAudioEvent(KeyEvent event,int streamType) {
        if (event.getAction() != KeyEvent.ACTION_DOWN) {
            return;
        }
        int keyCode = event.getKeyCode();
        int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
                | AudioManager.FLAG_FROM_KEY;
        String pkgName = mContext.getOpPackageName();
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_UP:
                try {
                    getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
                            streamType, flags, pkgName, TAG);
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
                }
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                try {
                    getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
                            streamType, flags, pkgName, TAG);
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
                }
                break;
            case KeyEvent.KEYCODE_MUTE:
                try {
                    if (event.getRepeatCount() == 0) {
                        getAudioService().adjustSuggestedStreamVolume(
                                AudioManager.ADJUST_TOGGLE_MUTE,
                                streamType, flags, pkgName, TAG);
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
                }
                break;
        }
    }

}

音量键的处理定义是在dispatchDirectAudioEvent中,是KEYCODE_VOLUME_UP,KEYCODE_VOLUME_DOWN以及KEYCODE_VOLUME_MUTE(虽然PhoneWindowManager中定义了KEYCODE_VOLUME_DOWN等按键的处理,但是实际音量加减处理并不一定走PhoneWindowManager,有可能是走System UI中的VolumeDialogImpl),而dispatchDirectAudioEvent是在interceptKeyBeforeDispatching中调用的。但是由于大部分的按键处理都放到了interceptKeyBeforeQueueing中去处理,所以KEYCODE_DPAD_DOWN的处理就放到interceptKeyBeforeQueueing中。

第二次是拦截是PhoneWindowManager中的interceptKeyBeforeDispatching函数。
interceptKeyBeforeDispatching对事件做了二次拦截,返回为-1则说明事件被拦截,返回值为0则说明事件被放行。

    @Override
    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;
        final boolean canceled = event.isCanceled();
        .........
        // First we always handle the home key here, so applications
        // can never break it, although if keyguard is on, we do let
        // it handle it, because that gives us the correct 5 second
        // timeout.
        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) {
                cancelPreloadRecentApps();

                mHomePressed = false;
                if (mHomeConsumed) {
                    mHomeConsumed = false;
                    return -1;
                }

                if (canceled) {
                    Log.i(TAG, "Ignoring HOME; event canceled.");
                    return -1;
                }

                // Delay handling home if a double-tap is possible.
                if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
                    mHomeDoubleTapPending = true;
                    mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                            ViewConfiguration.getDoubleTapTimeout());
                    return -1;
                }

                handleShortPressOnHome();
                return -1;
            }

            // If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == TYPE_KEYGUARD_DIALOG
                        || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
                final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                for (int i=0; i<typeCount; i++) {
                    if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                        // don't do anything, but also don't pass it to the app
                        return -1;
                    }
                }
            }

            // Remember that home is pressed and handle special actions.
            if (repeatCount == 0) {
                mHomePressed = true;
                if (mHomeDoubleTapPending) {
                    mHomeDoubleTapPending = false;
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
                    handleDoubleTapOnHome();
                } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
                    preloadRecentApps();
                }
            } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                if (!keyguardOn) {
                    handleLongPressOnHome(event.getDeviceId());
                }
            }
            return -1;
        } 
        .........
        // Let the application handle the key.
        return 0;
    }

从代码中可以看出,基本上都是返回-1,对home键进行拦截,不会下发,因此应用是无法监听的,除非the "app" is keyguard, so give it the key。

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

如果按键输入事件在interceptKeyBeforeQueueinginterceptKeyBeforeDispatching被拦截或被消费了,不再传递下去,那么则不会向下分发。如果没有被拦截,那么则会走到某个activity或view的dispatchKeyEvent等函数中。
具体可参考这篇。
Android8.0 按键事件处理流程(一)

参考链接:
PhoneWindowManager源码
Android输入事件从读取到分发五:事件分发前的拦截过程
Android输入事件分发与拦截
输入事件
android6.0 power按键深入分析
Android 输入系统(二)EventHub
Java 运算符
PhoneWindowManager
power键和音量键组合实现截图功能
Android电源管理之关机流程
Android8.0 按键事件处理流程(一)

相关文章

网友评论

      本文标题:Android 输入事件简单记录

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