二. 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中。
参数event描述的是窗口接收到的键盘事件,另外一个参数sendDone表示该键盘事件处理完成后,是否需要向系统的输入管理器发送一个通知。
ViewRoot类的成员变量mView描述的是窗口的顶层视图,即它指向的是一个DecorView对象,ViewRoot类的成员函数deliverKeyEvent首先是调用它的成员函数dispatchKeyEventPreIme来让它优先于输入法处理参数event所描述的键盘事件。如果这个DecorView对象的成员函数dispatchKeyEventPreIme的返回值handled等于true,那么就说明参数event所描述的键盘事件已经处理完毕,即ViewRoot类的成员函数deliverKeyEvent不用往下执行了。在这种情况下,如果参数sendDone的值等于true,那么ViewRoot类的成员函数deliverKeyEvent在返回之前,还会调用成员函数finishInputEvent来通知系统的输入管理器,当前激活的窗口已经处理完成刚刚发生的键盘事件了。在接下来的Step 2到Step 4中,我们再详细分析键盘事件优先于输入法分发给窗口处理的过程。
假设窗口不在输入法前面拦截参数event所描述的键盘事件,接下来ViewRoot类的成员函数deliverKeyEvent就会将该键盘事件分发给输入法处理,这个分发过程如下所示:
原文转自:http://blog.csdn.net/luoshengyang/article/details/8636153