Android TV 按键事件分发流程分析与焦点导航

本文源码分析基于api30

事件输入

了解事件分发机制我们需要了解App从哪里接收的到输入事件
Android中Java层有一个类叫做ViewRootImpl,其中setView方法会在Activity与Window连接时调用
ViewRootImpl中有一个内部类叫做WindowInputEventReceiver,其继承了InputEventReceiver抽象类

 final class WindowInputEventReceiver extends InputEventReceiver{
 
 }
复制代码

InputEventReceiver的dispatchInputEvent方法会在native层进行调用,将输入事件传递到Java层

public abstract class InputEventReceiver {
 // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }
}
复制代码

在WindowInputEventReceiver的onInputEvent方法中,会将输入事件传递给各个输入时间接受阶段

 final class WindowInputEventReceiver extends InputEventReceiver {
       @Override
        public void onInputEvent(InputEvent event) {
            List<InputEvent> processedEvents= mInputCompatProcessor.processInputEventForCompatibility(event);
            if (processedEvents != null) {
                //省略兼容处理代码
            } else {
                enqueueInputEvent(event, this, 0, true);
            }
        }
 }
复制代码

WindowInputEventReceiver调用ViewRootImpl的enqueueInputEvent方法生成输入事件队列,并准备处理

public final class ViewRootImpl {
    @UnsupportedAppUsage
    void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }
}
复制代码

在doProcessInputEvents方法中

  void doProcessInputEvents() {
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;
            deliverInputEvent(q);
        }

        // We are done processing all input events that we can process right now
        // so we can clear the pending flag immediately.
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }
复制代码

mPendingInputEventHead是输入事件队列QueuedInputEvent,待处理的输入事件就是等待传递到输入阶段并由应用程序处理的输入事件。

输入阶段

接着上面,deliverInputEvent方法会将输入事件进行传递

    private void deliverInputEvent(QueuedInputEvent q) {
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }
            if (stage != null) {
                handleWindowFocusChanged();
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
    }
复制代码

mFirstPostImeInputStage和mFirstInputStage是先前在setView方法中创建好的输入阶段

public final class ViewRootImpl {
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                int userId) {
               // Set up the input pipeline.
               CharSequence counterSuffix = attrs.getTitle();
               mSyntheticInputStage = new SyntheticInputStage();
               InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
               InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                                "aq:native-post-ime:" + counterSuffix);
               InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
               InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                                "aq:ime:" + counterSuffix);
               InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
               InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                                "aq:native-pre-ime:" + counterSuffix);
               mFirstInputStage = nativePreImeStage;
               mFirstPostImeInputStage = earlyPostImeStage;
           }
}
复制代码

这里定义了6种输入阶段,组成了一个链表,前一个处理完成后将会调用InputStage.deliver方法传递给下一个stage,该阶段可以选择完成事件或将其转发到下一个阶段。
其中View.dispatchKeyEvent就是在viewPostImeStage中调用的,它将是最后接收到输入事件的阶段
同样的,上面的deliverInputEvent方法中如果stage不为null,则将输入事件进行传递,

 abstract class InputStage {
        /**
         * Delivers an event to be processed.
         */
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                traceEvent(q, Trace.TRACE_TAG_VIEW);
                final int result;
                try {
                    result = onProcess(q);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
                apply(q, result);
            }
        }
 }
复制代码

如果事件未被完成,则会将其进行传递给下一阶段(forward);如果事件应该丢弃,则直接完成此次输入事件(finish);否则当前阶段对输入时间进行处理(onProcess),最后应用改变到输入事件的状态(apply)。

分发按键事件到视图

final class ViewPostImeInputStage extends InputStage {
        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
}
复制代码

这里包含了4个处理方法,分别对应于processKeyEvent(按键),processPointerEvent(鼠标指针),processTrackballEvent(手柄轨迹球),processGenericMotionEvent(操纵杆运动,鼠标悬停,触控板触摸,滚轮运动和其他输入事件)
processKeyEvent中

public final class ViewRootImpl {
        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            //省略部分代码
   
            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;
        }
}
复制代码

mView.dispatchKeyEvent(event)将按键事件传递给视图层次结构
如果视图层次结构未处理,则将执行默认的焦点导航performFocusNavigation(event)

我们来看看系统默认的焦点导航

 private boolean performFocusNavigation(KeyEvent event) {
            int direction = 0;
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_LEFT;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_RIGHT;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_UP:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_UP;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_DOWN;
                    }
                    break;
                case KeyEvent.KEYCODE_TAB:
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_FORWARD;
                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                        direction = View.FOCUS_BACKWARD;
                    }
                    break;
            }
            if (direction != 0) {
                View focused = mView.findFocus();
                if (focused != null) {
                    View v = focused.focusSearch(direction);
                    if (v != null && v != focused) {
                        // do the math the get the interesting rect
                        // of previous focused into the coord system of
                        // newly focused view
                        focused.getFocusedRect(mTempRect);
                        if (mView instanceof ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focused, mTempRect);
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTempRect);
                        }
                        if (v.requestFocus(direction, mTempRect)) {
                            playSoundEffect(SoundEffectConstants
                                    .getContantForFocusDirection(direction));
                            return true;
                        }
                    }

                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        return true;
                    }
                } else {
                    if (mView.restoreDefaultFocus()) {
                        return true;
                    }
                }
            }
            return false;
        }
复制代码

具体步骤为

  1. 查找方向
  2. 查找当前焦点
  3. 当前焦点视图为基准搜索目标方向焦点
  4. 聚焦目标视图,播放对应音效
  5. 未找到当前聚焦视图,恢复默认焦点(焦点丢失)
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享