在Android系统中,输入法窗口是一种特殊类型的窗口,它总是位于需要使用输入法的窗口的上面。也就是说,一旦WindowManagerService服务检测到焦点窗口需要使用输入法,那么它就会调整输入法窗口在窗口堆栈中的位置,使得输入法窗口位于在焦点窗口的上面,这样用户可以通过输入法窗口来录入字母或者文字。
在Android系统中,除了输入法窗口之外,还有一种窗口称为输入法对话框,它们总是位于输入法窗口的上面。Activity窗口、输入法窗口和输入法对话框的位置关系如图所示:
WindowManagerService服务是使用堆栈来组织系统中的窗口的,因此,如果我们在窗口堆栈中观察Activity窗口、输入法窗口和输入法对话框,它们的位置关系就如图所示:
对象的关系如下所示:
- 在ActivityManagerService服务内部的Activity组件堆栈顶端的ActivityRecord对象N描述的是系统当前激活的Activity组件。
- ActivityRecord对象N在WindowManagerService服务内部的窗口令牌列表顶端对应有一个AppWindowToken对象N。
- AppWindowToken对象N在WindowManagerService服务内部的窗口堆栈中对应有一个WindowState对象N,用来描述系统当前激活的Activity组件窗口。
- WindowState对象N上面有一个WindowState对象IMW,用来描述系统中的输入法窗口。
- WindowState对象IMW上面有三个WindowState对象IMD-1、IMD-2和IMD-3,它们用来描述系统中的输入法对话框。
- 系统中的输入法窗口以及输入法对话框在WindowManagerService服务内部中对应的窗口令牌是由WindowToken对象IM来描述的。
- WindowToken对象IM在InputMethodManagerService服务中对应有一个Binder对象。
总的来说,就是描述了系统当前激活的Activity窗口上面显示输入法窗口,而输入法窗口上面又有一系列的输入法对话框的情景。
WindowManagerService服务的职能之一就是要时刻关注系统中是否有窗口需要使用输入法。WindowManagerService服务一旦发现有窗口需要使用输入法,那么就会调整输入法窗口以及输入法对话框在窗口堆栈中的位置,使得它们放置在需要使用输入法的窗口的上面。
第一个需要调整输入法窗口以及输入法对话框在窗口堆栈中的位置的情景是增加一个窗口到WindowManagerService服务去的时候。增加一个窗口到WindowManagerService服务最终是通过调用WindowManagerService类的成员函数addWindow来实现的。
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
WindowState mInputMethodWindow = null;
final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>();
......
public int addWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int viewVisibility,
Rect outContentInsets, InputChannel outInputChannel) {
......
synchronized(mWindowMap) {
......
WindowToken token = mTokenMap.get(attrs.token);
if (token == null) {
......
if (attrs.type == TYPE_INPUT_METHOD) {
......
return WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
......
}
......
win = new WindowState(session, client, token,
attachedWindow, attrs, viewVisibility);
......
boolean imMayMove = true;
if (attrs.type == TYPE_INPUT_METHOD) {
mInputMethodWindow = win;
addInputMethodWindowToListLocked(win);
imMayMove = false;
} else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) {
mInputMethodDialogs.add(win);
addWindowToListInOrderLocked(win, true);
adjustInputMethodDialogsLocked();
imMayMove = false;
}
......
boolean focusChanged = false;
if (win.canReceiveKeys()) {
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
moveInputMethodWindowsIfNeededLocked(false);
}
......
}
......
}
......
}
如果当前增加到WindowManagerService服务来的是一个输入法窗口,即参数attrs所描述的一个WindowManager.LayoutParams对象的成员变量type的值等于TYPE_INPUT_METHOD,那么就要求与该输入法窗口所对应的类型为WindowToken的窗口令牌已经存在,否则的话,WindowManagerService类的成员函数addWindow就会直接返回一个错误码WindowManagerImpl.ADD_BAD_APP_TOKEN给调用者。
这个类型为WindowToken的窗口令牌是InputMethodManagerService服务请求WindowManagerService服务创建的,即调用WindowManagerService类的成员函数addWindowToken来创建的。
如果当前增加到WindowManagerService服务来的是一个输入法窗口,那么就会将前面为它所创建的一个WindowState对象win保存在WindowManagerService类的成员变量mInputMethodWindow中,接着还会调用WindowManagerService类的成员函数addInputMethodWindowToListLocked来将该WindowState对象插入到窗口堆栈的合适位置去。
如果当前增加到WindowManagerService服务来的是一个输入法对话框,即参数attrs所描述的一个WindowManager.LayoutParams对象的成员变量type的值等于TYPE_INPUT_METHOD_DIALOG,那么就会将前面为它所创建的一个WindowState对象win添加到WindowManagerService类的成员变量mInputMethodDialogs所描述的一个ArrayList中去,并且先后调用WindowManagerService类的成员函数addWindowToListInOrderLocked和adjustInputMethodDialogsLocked来将该WindowState对象插入到窗口堆栈的合适位置去。
在上述两种情况中,由于用来描述输入法窗口或者输入法对话框的WindowState对象已经被插入到了窗口堆栈中的合适位置,因此,接下来就不再需要考虑移动该输入法窗口或者输入法对话框了,这时候变量imMayMove的值就会被设置为false。
另一方面,如果当前增加到WindowManagerService服务来的既不是一个输入法窗口,也不是一个输入法对话框,并且该窗口需要接收键盘事件,即前面所创建的WindowState对象win的成员函数canReceiveKeys的返回值为true,那么就可能会导致系统当前获得焦点的窗口发生变化,这时候就需要调用WindowManagerService类的成员函数updateFocusedWindowLocked来重新计算系统当前获得焦点的窗口。如果系统当前获得焦点的窗口发生了变化,那么WindowManagerService类的成员函数updateFocusedWindowLocked的返回值focusChanged就会等于true,同时系统的输入法窗口和输入法对话框在窗口堆栈中的位置也会得到调整,即位它们会位于系统当前获得焦点的窗口的上面,因此,这时候变量imMayMove的值也会被设置为false,表示接下来不再需要考虑移动系统中的输入法窗口或者输入法对话框在窗口堆栈中的位置。
最后,如果变量imMayMove的值保持为初始值,即保持为true,那么就说明当前增加的窗口可能会引发系统的输入法窗口和输入法对话框在窗口堆栈中的位置发生变化,因此,这时候就需要调用WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked来作检测,并且在发生变化的情况下,将系统的输入法窗口和输入法对话框移动到窗口堆栈的合适位置上去。
从上面的分析就可以知道,在增加一个窗口的过程中,可能需要调用WindowManagerService类的成员函数addInputMethodWindowToListLocked、addWindowToListInOrderLocked、adjustInputMethodDialogsLocked和moveInputMethodWindowsIfNeededLocked来移动系统的输入法窗口和输入法对话框。其中,WindowManagerService类的成员函数addWindowToListInOrderLocked在前面Android窗口管理服务WindowManagerService窗口的组织方式分析一文已经分析过了,本文只要关注其余三个成员函数的实现。
第二个需要调整输入法窗口以及输入法对话框在窗口堆栈中的位置的情景是一个应用程序进程请求WindowManagerService服务重新布局一个窗口的时候。应用程序进程请求WindowManagerService服务重新布局一个窗口最终是通过调用WindowManagerService类的成员函数relayoutWindow来实现的。接下来我们就主要分析这个函数中与输入法窗口以及输入法对话框调整相关的逻辑,如下所示:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
public int relayoutWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, boolean insetsPending,
Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
Configuration outConfig, Surface outSurface) {
......
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
......
int attrChanges = 0;
int flagChanges = 0;
if (attrs != null) {
flagChanges = win.mAttrs.flags ^= attrs.flags;
attrChanges = win.mAttrs.copyFrom(attrs);
}
......
boolean imMayMove = (flagChanges&(
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0;
boolean focusMayChange = win.mViewVisibility != viewVisibility
|| ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0)
|| (!win.mRelayoutCalled);
......
if