按键分发流程
研究按键的分发和触摸事件一样,我们需要知道从哪里接收到输入事件
接收事件
android.view.ViewRootImpl.java
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
从Activity与Window和ViewRootImpl绑定会调用android.view.ViewRootImpl#setView方法,setView方法中会创建一个WindowInputEventReceiver(窗口输入事件接收器)
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
这个接收器的超类(InputEventReceiver)有一个dispatchInputEvent方法,这个方法会由native层调用。当接收到输入事件时,native层会向java层传递输入事件,此方法调用了onInputEvent方法,onInputEvent方法由子类重载
final class WindowInputEventReceiver extends InputEventReceiver{
@Override
public void onInputEvent(InputEvent event) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
enqueueInputEvent(event, this, 0, true);
}
}
}
WindowInputEventReceiver的onInputEvent方法有个InputCompatProcessor会为了兼容性处理输入事件,处理完之后剩余的输入事件列表会经历for循环来排队输入事件(enqueueInputEvent)
@UnsupportedAppUsage
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
.....................
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
enqueueInputEvent会根据入参的processImmediately来决定立刻或是异步执行处理
void doProcessInputEvents() {
// 在队列中传递所有未解决的输入事件。
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
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);
}
}
这里有一个链表,一般来说一次系统只会向app发送一个输入事件,但是由于app或者输入法编辑器可以一次合成多个事件,而无需通过输入调度程序,所以在向app端发送事件时需要排队输入事件
doProcessInputEvents中调用了deliverInputEvent方法
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
InputStage是一个抽象类,表示输入阶段,实现类有SyntheticInputStage,ViewPostImeInputStage,NativePostImeInputStage,EarlyPostImeInputStage,分批次处理输入事件,类似于前置和后置的作用
stage是责任链中的一个阶段,负责分发输入事件,事件通过deliver方法传递到阶段。 然后,该阶段可以选择结束分发或将其转发到下一个阶段。
先看handleWindowFocusChanged方法
onWindowFocusChanged
private void handleWindowFocusChanged() {
final boolean hasWindowFocus;
final boolean inTouchMode;
synchronized (this) {
if (!mWindowFocusChanged) {
return;
}
mWindowFocusChanged = false;
hasWindowFocus = mUpcomingWindowFocus;
inTouchMode = mUpcomingInTouchMode;
}
if (mAdded) {
................
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.hideTooltip();
}
}
.............
}
这里我们看到只有窗口聚焦发生变化才会执行下面的逻辑
handleWindowFocusChanged会调用View的dispatchWindowFocusChanged方法,而
ViewRootImpl中的mView是DecorView,DecorView没有覆盖此方法,所以调用的是ViewGroup的dispatchWindowFocusChanged
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchWindowFocusChanged(hasFocus);
}
}
dispatchWindowFocusChanged会先将窗口焦点变化时间发送给自己,调用自己的同名方法
public void dispatchWindowFocusChanged(boolean hasFocus) {
onWindowFocusChanged(hasFocus);
}
View的dispatchWindowFocusChanged又调用了onWindowFocusChanged方法,所以我们自定义View可以重写onWindowFocusChanged方法来监听窗口的焦点变化时间
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
........
final Window.Callback cb = mWindow.getCallback();
if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
cb.onWindowFocusChanged(hasWindowFocus);
}
........
updateElevation();
}
DecorView的onWindowFocusChanged里面调用了window的callback的onWindowFocusChanged,这里的callback是Activity,所以onWindowFocusChanged的回调顺序是DecorView->Activity->ViewGroup->View
按键事件分发
回到ViewRootImpl的deliverInputEvent方法
其中有一行 stage.deliver(q);
abstract class InputStage {
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}
}
这里主要看 apply(q, onProcess(q));的onProcess方法
InputStage类的onProcess默认返回FORWARD,这里我们需要找到它的实现类
在ViewRootImpl#setView方法的末尾,创建了一系列的InputStage
// 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;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
其中ViewPostImeInputStage类就是我们要找的类,我们看它的onProcess方法
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
.....
}
}
KeyEvent才属于我们的目标
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
................
// 将按键事件传递到视图层次结构
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
................
// 处理自动化焦点更新
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),
View#dispatchKeyEvent方法就是在视图层次结构之间发送按键事件的方法,与触摸事件一样return true表示消费事件。
performFocusNavigation方法顾名思义就是执行焦点导航,此方法会执行系统默认的焦点导航,从一个焦点视图跳到下一个焦点视图。
这里系统判断只有按下的事件才会执行焦点导航,所以该跳转按下就会跳转了,而不会等到抬起。
通常我们可以重写dispatchKeyEvent来消费事件并自己处理哪个视图该获取焦点,以至于系统就不会执行默认的导航跳转