Android按键事件传递流程(二)

660 阅读3分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

Android按键事件传递流程(二)

应用层接收按键事件

上篇文章讲到了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.
            //把按键事件传给了view处理,转交给应用层
            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.
            //处理Ctrl组合按键
            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.
            //处理所有的回退按键事件,主要是一些未处理的特殊按键,例如拍照键、拨号键等。如果特殊按键没有在PhoneWindowManager、view树、窗口中处理,就会传到此处。
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }
​
            // Handle automatic focus changes.
            //处理方向键和TAB键,找到获取焦点的view并将按键传递过去,如果view都没有焦点,就会找一个最适合的view并把按键传递过去。
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;
        }

应用层按键事件的传递

...
//把按键事件传给了view处理,转交给应用层
if (mView.dispatchKeyEvent(event)) {
    return FINISH_HANDLED;
}
...

事件传递给了mView的dispatchKeyEvent方法,mView是DecorView对象。

可以这样理解:

先找到当前获取焦点的窗口,把事件发送到窗口,窗口在启动Activity时会被创建,最终事件就传递到了窗口中所有view的根类DecorView。

这里主要讲解两部分,mView的创建事件传递流程

1. mView的创建过程

Activity的启动流程中handleResumeActivity方法

...
if (r.window == null && !a.mFinished && willBeVisible) {
            //返回一个PhoneWindow对象
            r.window = r.activity.getWindow();
            //获取DectorView对象
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //创建了一个与Activity对应的WindowManagerImpl对象。
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //调用WindowManagerImpl对象的addView方法
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
​
            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
...

r.activity.getWindow() r.activity就是最新启动的Activity对象,通过getWindow()返回mWindow对象。

wm.addView(decor, l);

 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

其中mGlobal是WindowManagerGlobal的单例对象。

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        
        ...
            
        ViewRootImpl root;
        
        ...
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        
    }

root是ViewRootImpl对象,调用其setView方法把DecorView对象传递过去并赋值给mView,详细请看源码ViewRootImpl.java。

mView就是与Activity对应的DecorView对象,在创建PhoneWindow对象时创建的。

\