二. TextView控件获取键盘输入的过程分析

  从前面Android应用程序键盘(Keyboard)消息处理机制分析一文可以知道,每一个窗口的创建的时候,都会与系统的输入管理器建立一个用户输入接收通道。输入管理器在启动两个线程,其中一个用来监控用户输入,即监控用户是否按下或者放开了键盘按键,或者是否触摸了屏幕,另外一个用来将监控到的用户输入事件分发给当前激活的窗口来处理,而这个分发过程是通过前面建立的通道来进行的。

  当前激活的窗口接收到输入管理器分发过来的用户输入事件之后,会该事件封装成一个消息发送到当前激活的窗口所运行在的应用程序进程的主线程的消息队列中去。等到这个消息被处理的时候,会调用与当前激活的窗口所关联的一个ViewRoot对象的成员函数deliverKeyEvent或者deliverPointerEvent来将前面接收到的用户输入分发给合适的控件。其中,ViewRoot类的成员函数deliverKeyEvent负责分发键盘输入事件,而ViewRoot类的成员函数deliverPointerEvent负责分发触摸屏输入事件。

  接下来,我们从ViewRoot类的成员函数deliverKeyEvent开始,分析一个TextView控件获得键盘输入的过程(获得触摸屏输入的过程是类似的),如图2所示:

  图2 TextView控件获得键盘输入的过程

  这个过程可以分为14个步骤,接下来我们详细分析每一个步骤。

  Step 1. ViewRoot.deliverKeyEvent

  [java] view plaincopyprint?

  public final class ViewRoot extends Handler implements ViewParent,

  View.AttachInfo.Callbacks {

  ......

  private void deliverKeyEvent(KeyEvent event, boolean sendDone) {

  // If mView is null, we just consume the key event because it doesn't

  // make sense to do anything else with it.

  boolean handled = mView != null

  ? mView.dispatchKeyEventPreIme(event) : true;

  if (handled) {

  if (sendDone) {

  finishInputEvent();

  }

  return;

  }

  // If it is possible for this window to interact with the input

  // method window, then we want to first dispatch our key events

  // to the input method.

  if (mLastWasImTarget) {

  InputMethodManager imm = InputMethodManager.peekInstance();

  if (imm != null && mView != null) {

  int seq = enqueuePendingEvent(event, sendDone);

  ......

  imm.dispatchKeyEvent(mView.getContext(), seq, event,

  mInputMethodCallback);

  return;

  }

  }

  deliverKeyEventToViewHierarchy(event, sendDone);

  }

  ......

  }

  public final class ViewRoot extends Handler implements ViewParent,

  View.AttachInfo.Callbacks {

  ......

  private void deliverKeyEvent(KeyEvent event, boolean sendDone) {

  // If mView is null, we just consume the key event because it doesn't

  // make sense to do anything else with it.

  boolean handled = mView != null

  ? mView.dispatchKeyEventPreIme(event) : true;

  if (handled) {

  if (sendDone) {

  finishInputEvent();

  }

  return;

  }

  // If it is possible for this window to interact with the input

  // method window, then we want to first dispatch our key events

  // to the input method.

  if (mLastWasImTarget) {

  InputMethodManager imm = InputMethodManager.peekInstance();

  if (imm != null && mView != null) {

  int seq = enqueuePendingEvent(event, sendDone);

  ......

  imm.dispatchKeyEvent(mView.getContext(), seq, event,

  mInputMethodCallback);

  return;

  }

  }

  deliverKeyEventToViewHierarchy(event, sendDone);

  }

  ......

  }

  这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。