在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。
1、输入法整体框架图
输入法系统的整个框架如下:
InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。
2、InputMethodManager创建
每个应用APP程序有一个InputMethodManager实例,这个是应用APP程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。
//frameworks/base/core/java/android/view/ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
}
//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
//这个进程的InputMethodManager实例就生成了
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
// InputMethodManager其实就是一个Binder service的proxy
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
3、APP程序的Window获得焦点
程序的window获得焦点的时序图如下
哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
//计算焦点window ----见3.1
WindowState newFocus = computeFocusedWindowLocked();
if (mCurrentFocus != newFocus) {
//焦点window发生变化,post一个message来通知程序焦点发生变化了
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); //见3.2
//省略.....
return true;
}
return false;
}
3.1 焦点window计算
//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
WindowState computeFocusedWindow() {
// While the keyguard is showing, we must focus anything besides the main display.
// Otherwise we risk input not going to the keyguard when the user expects it to.
final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded();
for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
final WindowState win = dc.findFocusedWindow();
if (win != null) {
if (forceDefaultDisplay && !dc.isDefaultDisplay) {
EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
continue;
}
return win;
}
}