KeyEvent事件的InputStage队列:
- 从《ViewRootImpl事件队列过程》文章,我们知道了InputStage的事件队列一层一层下来,如果没有持续地转发或者标示完成。就会回调到InputStage自己的onProcess()函数,去进行深一步的事件分发处理流程。
- 由此,我们通过事件的大概分类,分别对InputStage的onProcess()函数进行详细的分析,看看事件是怎么传递和分发的。
- 我们大致可以将事件划分为:按键事件(键盘、实体按键)、触摸事件(屏幕触摸,鼠标)
- 上篇文章,我们看到过两个事件处理队列:分别代表是否经输入法预处理事件。
- 而区分两者的方案就是:判断事件是否是鼠标指针、屏幕触摸
- 因此我们可以直接认为:
- 按键事件,走的就是需要输入法预处理的事件队列:
- 触摸事件,走的就是不需要输入法预处理的事件队列:
InputStage mFirstInputStage;//输入法预处理队列的表头
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
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;
...
}
- 从上面代码,我们能清晰的看到,InputStage使用了一个很经典的装饰模式来进行设计。和Java中的流比较类似,每一个装饰都负责自己所需要完成的一部分工作。
- 面试的时候,看来可以拿出来装13了。
按键事件:
- 前文的代码,我们可以看到输入法预处理队列的表头为:nativePreImeStag,因此会调用NativePreImeInputStage的deliver()函数。
NativePreImeInputStage
- deliver()函数我们在前面已经分析过,NativePreImeInputStage中的deliver()调用时,事件处于未完成和不应丢弃状态,因此走入了NativePreImeInputStage的onProcess()中:
@Override
protected int onProcess(QueuedInputEvent q) {
//InputQueue是用于Native层获取事件的机制,因此只对Native Code生效,这里不会走进去
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
}
//因此,返回的是FORWARD,于是事件就要被转发给队列的下一位了。
return FORWARD;
}
- 事件在NativePreImeInputStage中匹配到的是转发,于是在apply()函数中,将最终调用到InputStage中的onDeliverToNext()函数:
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
//如果自己不是队尾,则将事件传给自己的下一位进行处理
mNext.deliver(q);
} else {
//否则增加事件完成的flag
finishInputEvent(q);
}
}
- NativePreImeInputStage自己不处理事件事件,转发给了队列中的下一位,下一位是:
- ViewPreImeInputStage
ViewPreImeInputStage
- 它的作用在App开发端其实有相对重要的位置。比如说如果一个View想要在输入法之前抢走本应交给输入法的事件,就需要通过它来搞事情。
- 同样的,事件最终会像NativePreImeInputStage一样,回调到ViewPreImeInputStage的onProcess()函数中,这个读者可以参考前面介绍NativePreImeInputStage的过程,不再赘述:
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);//如果是键盘事件,直接回调自己内部函数processKeyEvent()
}
return FORWARD;//随后还是会转发
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {//关键在这里,它回调了ViewRootImpl中DecorView的dispatchKeyEventPreIme()函数
return FINISH_HANDLED;
}
return FORWARD;
}
我们可以看到,其实最终代码实在processKeyEvent()函数中。它回调了ViewRootImpl中DecorView的dispatchKeyEventPreIme()函数。
如果开发者想要让View在输入法之前处理事件,就需要覆写该View的dispatchKeyEventPreIme()函数。这个函数是在输入法处理事件之前回调的。
我们来看下DecorView的dispatchKeyEventPreIme()函数:
//函数最终回调到ViewGroup中
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
//如果这个View是Focus状态,就走这个分支
//PFLAG_HAS_BOUNDS表示的是这个View是否已经Layout完成
//如果这个View是Focus状态,就调用View中的dispatch,处理下一步
return super.dispatchKeyEventPreIme(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//否则如果自己的子View拥有Focus,则往下传递
//这就保证了,View树中,每一个View,从父亲往下走,都能接收到这个Key事件,询问他们是否处理
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
}
- 由此可见,即便事件是走入了输入法预处理队列,我们也能让事件被View先拦截。这样的设计,更人性化。
- 如果事件被View先拦截,则会返回已经被处理的结果。否则的话还会继续转发
ImeInputStage
- 到这里,就表示所有的View都不拦截这个事件,那么系统就可以将事件交给输入法来处理了。你看,输入法多憋屈,别人都不要的,才给它。。。摔!!!
- 在ImeInputStage中,事件将交给InputMethodManager处理,它会分发给输入法处理此次的事件,返回处理的结果。这里由于不属于我们的讨论范畴,就不展开讲了。
- 如果事件被输入法处理了,则返回FINISH_HANDLED,后续就会回调事件被处理的一系列逻辑了。
- 如果事件没有被处理,则会返回DEFER结果。这个结果,在随后的apply()函数中,将会把这个事件,加入到异步处理的队列中,表示这个事件将通过异步方式被处理
- 随后,经过一系列的Native层的处理,事件又会被底层回调到一个叫InputEventSender的类中。
InputEventSender:
- InputEventSender也是一个抽象类,我们主要关注它的实现:ImeInputEventSender
- ImeInputEventSender是在InputMethodManager中的一个成员,从这里我们不难猜出来,如果刚才的事件,输入法没有处理而导致事件被异步之后。InputMethodManager最终会回调到ImeInputEventSender,将事件进行下一步的下发处理。
- 这里,InputEventSender其实是从native层回调上来的,调用的是InputEventSender中的dispatchInputEventFinished()函数:
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEventFinished(int seq, boolean handled) {
onInputEventFinished(seq, handled);
}
- native层回调到这里,就会回调onInputEventFinished()函数,这个函数是一个空实现,需要子类覆写实现,我们回到刚才的ImeInputEventSender:
@Override
public void onInputEventFinished(int seq, boolean handled) {
finishedInputEvent(seq, handled, false);
}
- ImeInputEventSender是InputMethodManager中的一个内部类,从上面代码可以看出来,最终ImeInputEventSender又回调到了InputMethodManager中。(套路和InputStage、ViewRootImpl很像啊)
void finishedInputEvent(int seq, boolean handled, boolean timeout) {
final PendingEvent p;
...
//最终回调到这里,
invokeFinishedInputEventCallback(p, handled);
}
// Assumes the event has already been removed from the queue.
void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
p.mHandled = handled;
if (p.mHandler.getLooper().isCurrentThread()) {
//最终回调到PendingEvent的run()函数
p.run();
} else {
...
}
}
private final class PendingEvent implements Runnable {
...
public FinishedInputEventCallback mCallback;
...
public void run() {
//run()函数中,回调到了mCallback接口
//Callback对象就是ImeInputStage,大家可以看下ImeInputStage实现了FinishedInputEventCallback这个接口,最终又回调回来了
mCallback.onFinishedInputEvent(mToken, mHandled);
synchronized (mH) {
recyclePendingEventLocked(this);
}
}
}
- 到这里,大家疑问就来了,为什么Callback会变成ImeInputStage呢?毫无疑问,肯定是ImeInputStage主动将自己set进去的,在哪里做的呢?
- 我们回到ImeInputStage中去
ImeInputStage:
- ImeInputStage中的onProcess()函数将事件交给InputMethodManager处理事件,它就是在这时候被注册到callback中的:
@Override
protected int onProcess(QueuedInputEvent q) {
...
InputMethodManager imm = InputMethodManager.peekInstance();
int result = imm.dispatchInputEvent(event, q, this, mHandler);
...
return FORWARD;
}
- InputMethodManager中的dispatchInputEvent()函数被调用,这时候,我们将ImeInputStage的this指针传了进去,我们看下这是什么参数:
public int dispatchInputEvent(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler)
//果不其然,ImeInputStage就是在这里被传进来,最后被作为callback参数,传给了PendingEvent
- 兜兜转转,事件被异步处理的意思,其实最终也还是回调到了ImeInputStage的onFinishedInputEvent()函数:
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent)token;
if (handled) {//如果事件被处理了,那么直接finish()
finish(q, true);
return;
}
forward(q);//否则还会进行转发
}
- 从这段代码可以看到,事件传到这里,要么完成,要么转发。之前我们一路跟下来,事件都没有被处理,所以肯定还是被转发了出去。
- 从文章已开始的时候,ViewRootImpl构建两个队列时,我们就能看到,ImeInputStage的mNext其实也赋值了,换言之ImeInputStage也有后续队列:
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
//ImeInputStage内部保存了EarlyPostImeInputStage,表示如果转发,则会转发给EarlyPostImeInputStage
- 因此,如果键盘事件一路走下来,输入法也不处理,那么就会走到输入法不预处理的InputStage队列中。这是环环相扣的。
- 那接下来,我们针对键盘事件,先讨论到这里。再来看看触摸事件的队列,随后再继续讨论。