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队列中。这是环环相扣的。
  • 那接下来,我们针对键盘事件,先讨论到这里。再来看看触摸事件的队列,随后再继续讨论。

 评论