当在launcher的allapps界面按下按键1(当前设备为带键盘的Android设备)的时候,PhoneWindowManager.java到Launcher.java的处理log如下。
2021-02-24 17:29:58.511 679-831/? I/WindowManager: interceptKeyTq keycode=8 interactive=true keyguardActive=false policyFlags=22000000
2021-02-24 17:29:58.512 679-831/? I/PhoneWindowManager: interceptKeyBeforeQueueing result=1
2021-02-24 17:29:58.514 679-830/? D/WindowManager: interceptKeyTi keyCode=8 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0
2021-02-24 17:29:58.514 679-830/? I/PhoneWindowManager: interceptKeyBeforeDispatching result 0
2021-02-24 17:29:58.519 1438-1438/? I/Launcher: inner dispatchKeyEvent ,getCurrentFocus = com.android.launcher3.allapps.AllAppsRecyclerView{6a62e0e VFED..... .F...... 134,87-1146,740 #7f0b001e app:id/apps_list_view}
interceptKeyBeforeQueueing 返回1,interceptKeyBeforeDispatching返回0,事件没有被PhoneWindowManager拦截,走到了Launcher的public boolean dispatchKeyEvent(KeyEvent event)。
由于设备带按键KEYCODE_1-KEYCODE_POUND,要求在launcher界面按下KEYCODE_1-KEYCODE_POUND键会跳转至打电话界面,因此之前在Launcher的onKeyUp中定义了跳转拨号界面。
//vendor/mediatek/proprietary/packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Log.i("Launcher", "onKeyUp keyCode="+keyCode+"--event.action ="+event.getAction());
final boolean handled = super.onKeyUp(keyCode, event);
if (!handled&&shortPress){
switch(keyCode){
case KeyEvent.KEYCODE_0:
//定义了跳转至拨号界面
...............
return true;
case KeyEvent.KEYCODE_1:
//定义了跳转至拨号界面
...............
return true;
...............
case KeyEvent.KEYCODE_POUND:
//定义了跳转至拨号界面
...............
return true;
case KeyEvent.KEYCODE_MENU:
// Ignore the menu key if we are currently dragging or are on the custom content screen
if (!isOnCustomContent() && !mDragController.isDragging()) {
// Close any open floating view
AbstractFloatingView.closeAllOpenViews(this);
// Stop resizing any widgets
mWorkspace.exitWidgetResizeMode();
// Show the overview mode if we are on the workspace
if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
!mWorkspace.isSwitchingState()) {
mOverviewPanel.requestFocus();
showOverviewMode(true, true /* requestButtonFocus */);
}
}
return true;
}
}
return super.onKeyUp(keyCode, event);
}
但是,这里还有另一个问题,当显示了launcher的search_container_all_apps.xml的时候(即显示了 搜索应用这部分UI,因为大部分时候都会隐藏这个UI),
按下KEYCODE_1-KEYCODE_POUND按键时,例如按下KEYCODE_1,ExtendedEditText会先获取焦点,弹出软键盘,并在ExtendedEditText中显示输入的1,然后再跳转至拨号界面。
来看看当走到Launcher的dispatchKeyEvent中之后发生了什么。
//vendor/mediatek/proprietary/packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.i(TAG, "inner dispatchKeyEvent ,getCurrentFocus "+getCurrentFocus());
return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
}
按键的一次按下和抬起,会产生一次Action_Down和Action_Up。
当按键按下按键后,都产生 Action_Down ,抬起产生Action_Up。
在frameworks/base/core/java/android/app/Activity.java和frameworks/base/core/java/android/view/KeyEvent.java中加log会发现,首先会走到Launcher的dispatchKeyEvent,然后走到event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this),然后走到KeyEvent的public final boolean dispatch(Callback receiver, DispatcherState state,Object target)中,由于按键按下action是ACTION_DOWN,因此走到boolean res = receiver.onKeyDown(mKeyCode, this);
这里要对KeyEvent事件分发流程有一定的了解。
出处:https://www.cnblogs.com/chengliu/p/4130608.html
事件处理流程
1)InputManager负责读取事件并把事件送到frameworks的java层
2)WindowManagerService里会有一个InputMonitor类来监听事件变化并做相应的分发处理。
3)在WindowManagerService会有一个WindowManagerPolicy来做消息拦截处理。
4)WindowManagerService会把消息发给最上面运行的窗口接收
也就是说,当一个按键按下,产生KeyEvent事件,实际是由InputManager接收事件,传递给InputMonitor,然后会调用PhoneWindowManager中的interceptKeyBeforeDispatching和interceptKeyBeforeDispatching,判断是否拦截,当不拦截的时候,就会把KeyEvent往下传递,传递给最上面运行的窗口,也就是一个DecorView(实际是一个FrameLayout)。
然后走到了。
I/WindowManager: interceptKeyTq keycode=8 interactive=true keyguardActive=false policyFlags=22000000
I/PhoneWindowManager: interceptKeyBeforeQueueing result=1
D/WindowManager: interceptKeyTi keyCode=8 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0
I/PhoneWindowManager: interceptKeyBeforeDispatching result 0
com.android.launcher3 I/Launcher: dispatchKeyEvent focus =com.android.launcher3.allapps.AllAppsRecyclerView{6a62e0e VFED..... .F...... 134,87-1146,740 #7f0b001e app:id/apps_list_view}
com.android.launcher3 I/AppsSearchContainerLayout: preDispatchKeyEvent request search field
com.android.launcher3 I/Launcher: onKeyDown keycode=8focus =com.android.launcher3.allapps.AllAppsRecyclerView{6a62e0e VFED..... .F...... 134,87-1146,740 #7f0b001e app:id/apps_list_view}
I/dex2oat: /system/bin/dex2oat --input-vdex-fd=18 --output-vdex-fd=19 --compiler-filter=speed-profile --profile-file-fd=22 --classpath-dir=/system/priv-app/MtkDocumentsUI --class-loader-context=PCL[]
I/WindowManager: interceptKeyTq keycode=8 interactive=true keyguardActive=false policyFlags=22000000
I/PhoneWindowManager: interceptKeyBeforeQueueing result=1
D/WindowManager: interceptKeyTi keyCode=8 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0
I/PhoneWindowManager: interceptKeyBeforeDispatching result 0
com.android.launcher3 I/Launcher: dispatchKeyEvent focus =com.android.launcher3.allapps.AllAppsRecyclerView{6a62e0e VFED..... .F...... 134,87-1146,740 #7f0b001e app:id/apps_list_view}
com.android.launcher3 I/Launcher: onKeyUp keyCode=8--event.action =1
I/PhoneWindowManager: interceptKeyBeforeDispatching result 0
com.android.launcher3 I/Launcher: dispatchKeyEvent focus =com.android.launcher3.allapps.AllAppsRecyclerView{6a62e0e VFED..... .F...... 134,87-1146,740 #7f0b001e app:id/apps_list_view}
当按键1按下,走到Launcher的dispatchKeyEvent中,再到Activity的dispatchKeyEvent中,再到win.superDispatchKeyEvent(event),然后到了DecorView的superDispatchKeyEvent中。
//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
//frameworks/base/core/java/com/android/internal/policy/DecorView.java
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);
}
dispatchKeyEvent定义在ViewGroup中。
//frameworks/base/core/java/android/view/ViewGroup.java
@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) {
/// M : add log to help debugging
if (ViewDebugManager.DEBUG_KEY) {
Log.d(TAG, "dispatchKeyEvent to focus child event = " + event + ", mFocused = "
+ mFocused + ",this = " + this);
}
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
由于DecorView有焦点(此时焦点在AllAppsRecyclerView上),
DecorView中的mFocused != null,因此会调用ViewGroup的dispatchKeyEvent(KeyEvent event)中的
mFocused.dispatchKeyEvent(event)
dispatchKeyEvent(event)会遍历调用当DecorView下的View中dispatchKeyEvent。
我暂时没有弄明白,这个遍历到底是如何调用的,目前的log信息不足以显示出遍历过程,但是事实就是如此。
//vendor/mediatek/proprietary/packages/apps/Launcher3/res/layout/all_apps.xml
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
launcher:revealBackground="@drawable/round_rect_primary">
......................
由于遍历DecorView下的View,并调用dispatchKeyEvent,直至找到带焦点的View,所以一步步走到了AllAppsContainerView中的dispatchKeyEvent中。
//vendor/mediatek/proprietary/packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
mSearchUiManager.preDispatchKeyEvent(event);
return super.dispatchKeyEvent(event);
}
preDispatchKeyEvent在AppsSearchContainerLayout中重写,调用了focusSearchField。而调用focusSearchField会使ExtendedEditText弹出软键盘,使ExtendedEditText获取焦点,因此,屏蔽掉AppsSearchContainerLayout的preDispatchKeyEvent中的mSearchBarController.focusSearchField();即可。
//vendor/mediatek/proprietary/packages/apps/Launcher3/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@Override
public void preDispatchKeyEvent(KeyEvent event) {
// Determine if the key event was actual text, if so, focus the search bar and then dispatch
// the key normally so that it can process this key event
if (!mSearchBarController.isSearchFieldFocused() &&
event.getAction() == KeyEvent.ACTION_DOWN) {
final int unicodeChar = event.getUnicodeChar();
final boolean isKeyNotWhitespace = unicodeChar > 0 &&
!Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
if (isKeyNotWhitespace) {
boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
event.getKeyCode(), event);
if (gotKey && mSearchQueryBuilder.length() > 0) {
Log.i("AppsSearchContainerLayout", "preDispatchKeyEvent request search field");
//mSearchBarController.focusSearchField();
}
}
}
}
由于是响应ACTION_DOWN,也可以直接将ACTION_DOWN的事件消费掉,不过这样也有可能带来别的问题,虽然没有测出来,还是直接改View中(即AppsSearchContainerLayout)的响应逻辑比较好。
//vendor/mediatek/proprietary/packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.i(TAG, "inner dispatchKeyEvent ,getCurrentFocus "+getCurrentFocus());
if ( event.getKeyCode() >= KeyEvent.KEYCODE_0 && event.getKeyCode() <= KeyEvent.KEYCODE_POUND && event.getAction() == KeyEvent.ACTION_DOWN){
return true;
}
return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
}
出处:Android焦点事件分发与传递机制
要修改ViewGroup焦点事件的分发:
重写view的dispatchKeyEvent方法
给某个子view设置onKeyListener监听