根据官方文档 输入事件,事件的输入一般就2种,屏幕和按键的输入。
出处:https://www.jianshu.com/p/ab59b241992e
Android所有输入事件都会封装为InputEvent事件然后进行分发,InputEvent又分为两种类型,实体按键事件(KeyEvent),触摸事件(MotionEvent)。这些事件流入到上层之后才会进行分别进行处理。
屏幕的输入事件的处理,可以参考这两篇。
安卓自定义View进阶-事件分发机制原理
安卓自定义View进阶-事件分发机制详解
Android 在事件分发过程中,有2种情况,继续向下分发(返回false,事件继续向下分发,如果到最终始终返回false,事件会再逐次向上传递到最开始的类中,如果该类也不需要,事件就被抛弃)或事件消费(即事件拦截,返回true表示消费了事件,则不再向下分发,事件被拦截)。
按键的输入事件的处理,首先来看PhoneWindowManager
中的interceptKeyBeforeQueueing
和interceptKeyBeforeDispatching
。
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框架处理。
如果按键输入事件在interceptKeyBeforeQueueing
和interceptKeyBeforeDispatching
被拦截或被消费了,不再传递下去,那么则不会向下分发。如果没有被拦截,那么则会走到某个activity或view的dispatchKeyEvent
等函数中。
具体可参考这篇。
Android8.0 按键事件处理流程(一)
参考链接:
PhoneWindowManager源码
Android输入事件从读取到分发五:事件分发前的拦截过程
Android输入事件分发与拦截
输入事件
android6.0 power按键深入分析
Android 输入系统(二)EventHub
Java 运算符
PhoneWindowManager
power键和音量键组合实现截图功能
Android电源管理之关机流程
Android8.0 按键事件处理流程(一)
网友评论