首页 > 代码库 > Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8526644
在Android系统中,输入法窗口是一种特殊类型的窗口,它总是位于需要使用输入法的窗口的上面。也就是说,一旦 WindowManagerService服务检测到焦点窗口需要使用输入法,那么它就会调整输入法窗口在窗口堆栈中的位置,使得输入法窗口位于在焦点窗 口的上面,这样用户可以通过输入法窗口来录入字母或者文字。本文就将详细分析WindowManagerService服务是如何管理系统中的输入法窗口 的。
在Android系统中,除了输入法窗口之外,还有一种窗口称为输入法对话框,它们总是位于输入窗口的上面。Activity窗口、输入法窗口和输入法对话框的位置关系如图1所示:
图1 Activity窗口、输入法窗口和输入法对话框的位置关系
在前面Android窗口管理服务WindowManagerService组织窗口的方式分析一文中提到,WindowManagerService服务是使用堆栈来组织系统中的窗口的,因此,如果我们在窗口堆栈中观察Activity窗口、输入法窗口和输入法对话框,它们的位置关系就如图2所示:
图2 Activity窗口、输入法窗口和输入法对话框在窗口堆栈中的位置关系
图2中的对象的关系如下所示:
1. 在ActivityManagerService服务内部的Activity组件堆栈顶端的ActivityRecord对象N描述的是系统当前激活的Activity组件。
2. ActivityRecord对象N在WindowManagerService服务内部的窗口令牌列表顶端对应有一个AppWindowToken对象N。
3. AppWindowToken对象N在WindowManagerService服务内部的窗口堆栈中对应有一个WindowState对象N,用来描述系统当前激活的Activity组件窗口。
4. WindowState对象N上面有一个WindowState对象IMW,用来描述系统中的输入法窗口。
5. WindowState对象IMW上面有三个WindowState对象IMD-1、IMD-2和IMD-3,它们用来描述系统中的输入法对话框。
6. 系统中的输入法窗口以及输入法对话框在WindowManagerService服务内部中对应的窗口令牌是由WindowToken对象IM来描述的。
7. WindowToken对象IM在InputMethodManagerService服务中对应有一个Binder对象。
总的来说,就是图2描述了系统当前激活的Activity窗口上面显示输入法窗口,而输入法窗口上面又有一系列的输入法对话框的情景。 WindowManagerService服务的职能之一就是要时刻关注系统中是否有窗口需要使用输入法。WindowManagerService服务 一旦发现有窗口需要使用输入法,那么就会调整输入法窗口以及输入法对话框在窗口堆栈中的位置,使得它们放置在需要使用输入法的窗口的上面。
接下来,我们就首先分析两个需要调整输入法窗口以及输入法对话框在窗口堆栈中的位置的情景,然后再分析它们是如何在窗口堆栈中进行调整的。
第一个需要调整输入法窗口以及输入法对话框在窗口堆栈中的位置的情景是增加一个窗口到WindowManagerService服务去的时候。从前面Android应用程序窗口(Activity)与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);
- }
- ......
- }
- ......
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
如果当前增加到WindowManagerService服务来的是一个输入法窗口,即参数attrs所描述的一个 WindowManager.LayoutParams对象的成员变量type的值等于TYPE_INPUT_METHOD,那么就要求与该输入法窗口所 对应的类型为WindowToken的窗口令牌已经存在,否则的话,WindowManagerService类的成员函数addWindow就会直接返 回一个错误码WindowManagerImpl.ADD_BAD_APP_TOKEN给调用者。这个类型为WindowToken的窗口令牌是 InputMethodManagerService服务请求WindowManagerService服务创建的,即调用 WindowManagerService类的成员函数addWindowToken来创建的,具体可以参考前面Android窗口管理服务WindowManagerService组织窗口的方式分析一文。
如果当前增加到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服务重新布局一个窗口的时候。从前面Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析一 文可以知道,应用程序进程请求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 (viewVisibility == View.VISIBLE &&
- (win.mAppToken == null || !win.mAppToken.clientHidden)) {
- displayed = !win.isVisibleLw();
- ......
- if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
- // To change the format, we need to re-build the surface.
- win.destroySurfaceLocked();
- displayed = true;
- }
- ......
- if (win.mAttrs.type == TYPE_INPUT_METHOD
- && mInputMethodWindow == null) {
- mInputMethodWindow = win;
- imMayMove = true;
- }
- if (displayed) {
- focusMayChange = true;
- }
- ......
- } else {
- ......
- if (win.mSurface != null) {
- ......
- // If we are not currently running the exit animation, we
- // need to see about starting one.
- if (!win.mExiting || win.mSurfacePendingDestroy) {
- ......
- if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() &&
- applyAnimationLocked(win, transit, false)) {
- focusMayChange = true;
- win.mExiting = true;
- } else if (win.isAnimating()) {
- // Currently in a hide animation... turn this into
- // an exit.
- win.mExiting = true;
- } else if (win == mWallpaperTarget) {
- // If the wallpaper is currently behind this
- // window, we need to change both of them inside
- // of a transaction to avoid artifacts.
- win.mExiting = true;
- win.mAnimating = true;
- } else {
- if (mInputMethodWindow == win) {
- mInputMethodWindow = null;
- }
- win.destroySurfaceLocked();
- }
- }
- }
- ......
- }
- if (focusMayChange) {
- ......
- if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
- imMayMove = false;
- }
- ......
- }
- // updateFocusedWindowLocked() already assigned layers so we only need to
- // reassign them at this point if the IM window state gets shuffled
- boolean assignLayers = false;
- if (imMayMove) {
- if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {
- // Little hack here -- we -should- be able to rely on the
- // function to return true if the IME has moved and needs
- // its layer recomputed. However, if the IME was hidden
- // and isn‘t actually moved in the list, its layer may be
- // out of data so we make sure to recompute it.
- assignLayers = true;
- }
- }
- ......
- if (assignLayers) {
- assignLayersLocked();
- }
- ......
- }
- ......
- return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0)
- | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
应用程序进程在请求WindowManagerService服务重新布局一个窗口的时候,这个窗口的一些布局参数可能会发生变化,而这些变化可能会 同时引发系统的输入法窗口以及输入法对话框在窗口堆栈中的位置发生变化。如果系统的输入法窗口以及输入法对话框在窗口堆栈中的位置发生了变化,那么就需要 调整它们在窗口堆栈中的位置。
WindowManagerService类的成员函数relayoutWindow首先调用根据参数session和client来调用另外一个成 员函数windowForClientLocked,以便可以获得用来描述要重新布局的窗口的一个WindowState对象win。
WindowState对象win的成员变量mAttrs指向的是一个WindowManager.LayoutParams对象,该 WindowManager.LayoutParams对象的成员变量flags描述的是窗口上一次所设置的布局属性标志位,而参数attrs所描述的一 个WindowManager.LayoutParams对象的成员变量flags描述的是窗口当前被设置的布局属性标志位。 WindowManagerService类的成员函数relayoutWindow通过对这两个标志位执行一个异或操作,就可以知道窗口的哪些布局属性 标志位发生了变化,这些变化就记录在变量flagChanges中。
WindowManagerService类的成员函数relayoutWindow在对WindowState对象win所描述的窗口进行布局之前,还 要将参数attrs指的是一个WindowManager.LayoutParams对象的内容拷贝到 WindowState对象win的成员变量 mAttrs指向的是一个WindowManager.LayoutParams对象中去。在拷贝的过程中,如果发现这两个 WindowManager.LayoutParams对象所描述的窗口布局属性有发生变化,那么这些变化就会记录在变量attrChanges中。
在窗口的布局属性标志中,位WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE表示窗口是否可以获得焦点,另 外一个位WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM是用来反转 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位的作用的。一个窗口是否可以获得焦点意味着它是否需要与 输入法窗口交互,即如果一个窗口是可以获得焦点的,那么就意味着它需要与输入法窗口交互,否则就不需要。当一个窗口的 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位等于1,那么就表示窗口不可以获得焦点,即不需要与输入 法窗口交互,但是如果该窗口的WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM位也等于1,那么就表 示窗口仍然是需要与输入法窗口交互的。另一方面,如果一个窗口的 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM位等于1,但是该窗口的 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位等于0,那么就表示窗口仍然是不可以与输入法窗口交互 的。因此,当前面得到的变量flagChanges的WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位或 者WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM位发生了变化时,都意味着对 WindowState对象win所描述的窗口进行重新布局会影响系统中的输入法窗口以及输入法对话框,即该窗口可能会由需要显示输入法窗口以及输入法对 话框,到不需要显示输入法窗口以及输入法对话框,反之亦然。最后得到的变量imMayMove的值等于true就表示要移动系统中的输入法窗口以及输入法 对话框在窗口堆栈中的位置。
一个窗口由不可获得焦点到可以获得焦点,或者由可获得焦点到不可以获得焦点,即窗口布局属性标志中的 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位发生了变化,那么就意味着要重新计算系统当前获得焦点的 窗口。从前面分析增加窗口到WindowManagerService服务的情景可以知道,当系统当前获得焦点的窗口发生变化时,也意味着需要系统中的移 动输入法窗口以及输入法对话框在窗口堆栈中的位置。除了窗口布局属性标志中的 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE位变化会引发系统当前获得焦点的窗口发生变化之外,还有另 外两个因素会引发系统当前获得焦点的窗口发生变化。第一个因素是窗口的可见性发生变化。WindowState对象win的成员变量 mViewVisibility描述的是窗口上一次布局时的可见性,而参数viewVisibility描述的是窗口当前的可见性,当它们的值不相等时, 就意味着窗口的可见性发生了变化。第二个因素是窗口是第一次被应用程序进程请求WindowManagerService服务布局,这时候 WindowState对象win的成员变量mRelayoutCalled的值就会等于false。最后得到的变量focusMayChange等于 true,就表示需要重新计算系统当前获得焦点的窗口。
WindowState对象win所描述的窗口在此次重新布局中是否会引起移动系统中的输入法窗口以及输入法对话框在窗口堆栈中的位置,还取决于它在的可 见性以及它的绘图表面属性等信息,接下来我们就按照 WindowState对象win所描述的窗口当前是可见还是不可见来分别分析。
我们首先分析WindowState对象win所描述的窗口在此次重新布局中是可见的情景,即参数viewVisibility的值等于 View.VISIBLE。注意,如果WindowState对象win所描述的是一个Activity窗口,而该Activity组件是不可见的,那么 即使参数viewVisibility的值等于View.VISIBLE,那么WindowState对象win所描述的窗口在此次重新布局中也是认为不 可见的。从前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一 文可以知道,当WindowState对象win的成员变量mAppToken的值不等于null时,那么该WindowState对象win描述的是一 个Activity窗口,而当该成员变量所指向的一个AppWindowToken对象的成员变量clientHidden的值等于false时,就表示 对应的Activity组件是可见的。
WindowState对象win所描述的窗口在上一次布局时的可见性可以调用它的成员函数isVisibleLw来获得。如果 WindowState对象win所描述的窗口在上一次布局时是不可见的,那么现在就需要将它设置为可见的,即要将它显示出来,这时候变量 displayed的值就会等于true。另一方面,如果WindowState对象win所描述的窗口的绘图表面的像素格式发生了变化,即变量 attrChanges的WindowManager.LayoutParams.FORMAT_CHANGED位等于1,那么这时候就需要调用 WindowState对象win的成员函数destroySurfaceLocked来销毁它所描述的窗口的绘图表面,以便接下来可以为它重新创建一个 新的绘图表面,这时候也会将变量displayed的值设置为true,表示接下来是要显示WindowState对象win所描述的窗口的。如果最终得 到的变量displayed的值设置为true,那么就相当于说明WindowState对象win所描述的窗口经历一个由不可见到可见的状态变化,因此 就可能会导致系统当前获得焦点的窗口发生变化,这时候就会将变量focusMayChange的值设置为true。
如果WindowState对象win描述的是一个输入法窗口,即它的成员变量mAttrs所描述的一个 WindowManager.LayoutParams对象的成员变量type的值等于TYPE_INPUT_METHOD,并且系统中的输入法窗口尚未 设置,即WindowManagerService类的成员变量mInputMethodWindow的值等于null,那么就说明接下来要显示的其实是 输入法窗口,这情况会导致需要移动系统中的输入法窗口以及输入法对话框在窗口堆栈中的位置,因此,这时候除了需要将WindowState对象win保存 在WindowManagerService类的成员变量mInputMethodWindow之外,还需要将变量imMayMove的值设置为 true。
我们接下来再分析WindowState对象win所描述的窗口在此次重新布局中是不可见的情景。一个窗口变得不可见了,就意味着可能要销毁它的绘图表 面,取决于它的绘图表面是否存在,以及它的退出动画是否已经显示结束。WindowState对象win所描述的窗口的绘图表面保存在它的成员变量 mSurface中,因此,当WindowState对象win的成员变量mSurface不等于null的时候,就意味着可能会销毁它所描述的绘图表 面。
如果WindowState对象win的成员变量mExiting等于false时,那么就说明该WindowState对象win所描述的窗口的退出动 画可能尚未开始,也可能已经结束。另一方面,如果WindowState对象win的成员变量mSurfacePendingDestroy的值等于 true,那么就说明该WindowState对象win所描述的窗口的绘图表面正在等待销毁。这两种情况都需要进一步确定接下来是要开始 WindowState对象win所描述的窗口的退出动画,还是要销毁WindowState对象win所描述的窗口的绘图表面。
如果WindowState对象win的成员变量mSurfacePendingDestroy的值等于false,那么同时也意味着它所描述的窗口还未 开始显示退出动画,因而它的绘图表面就没有进入正在等待销毁的状态。在这种情况下,如果WindowState对象win所描述的窗口是可见的,即它的成 员函数isWinVisibleLw的返回值等于true,那么就意味要开始该窗口的退出动画了,这是通过调用WindowManagerService 类的成员函数applyAnimationLocked来实现的。WindowState对象win描述的窗口开始退出动画之后,就意味要重新计算系统当 前获得焦点的窗口,因此,这时候就会将变量focusMayChange的值设置为true,同时还会将WindowState对象win的成员变量 mExiting的值设置为true,表示它描述的窗口正在退出的过程中。
如果WindowState对象win所描述的窗口正在处于退出动画的过程中,即它的成员函数isAnimating的返回值等于true,那么这时候需要确保WindowState对象win的成员变量mExiting的值为true。
如果WindowState对象win所描述的窗口已经结束退出动画,但是它仍然是壁纸窗口的目标,即WindowManagerService类的成员 变量mWallpaperTarget的值不等于null,并且它的值就等于WindowState对象win,那么这时候就需要等待壁纸窗口也退出之 后,才销毁WindowState对象win所描述的窗口,因此,这时候就需要将WindowState对象win的成员变量mExiting和 mAnimating的值设置为true,即假装它所描述的窗口还处于正在退出的过程,这样做是为了等待壁纸窗口退出完成。
如果WindowState对象win所描述的窗口已经结束退出动画,并且它不是壁纸窗口的目标,那么这时候就需要调用它的成员函数 destroySurfaceLocked来销毁它的绘图表面了。在销毁WindowState对象win所描述的窗口之前,还会判断它是否就是系统当前 的输入法窗口,即WindowManagerService类的成员变量mInputMethodWindow的值是否等于win。如果等于的话,那么就 说明系统当前的输入法窗口被销毁了,因此,就需要将WindowManagerService类的成员变量mInputMethodWindow的值设置 为null。
经过上面的一系列操作之后,如果最终得到的变量focusMayChange的值等于true,那么就说明需要重新计算系统当前获得焦点的窗口了,这是通 过调用WindowManagerService类的成员函数updateFocusedWindowLocked来实现的。一旦 WindowManagerService类的成员函数updateFocusedWindowLocked的返回值为true,那么就说明统当前获得焦 点的窗口发生了变化,并且系统中的输入法窗口以及输入法对话框也移动到窗口堆栈中的正确位置了,因此,这时候就会将变量imMayMove的值设置为 false。
经过上面的一系列操作之后,如果最终得到的变量imMayMove的值等于true,那么就说明有可能需要移动系统中的输入法窗口以及输入法对话框在 窗口堆栈中的位置,这是通过调用WindowManagerService类的成员函数 moveInputMethodWindowsIfNeededLocked来实现的。一旦系统中的输入法窗口以及输入法对话框在窗口堆栈中的位置发生了 移动,那么WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked的返回值就 等于true,这时候就需要将变量assignLayers的值设置为true,表示要重新计算系统中的窗口的Z轴位置,以便可以同步到 SurfaceFlinger服务中去。注意,如果系统中的输入法窗口以及输入法对话框在窗口堆栈中的位置没有发生变化,但是前面得到的变量 displayed的值等于true,那么也是需要将变量assignLayers的值设置为true的,因为这个变量displayed的值等于 true意味着WindowState对象win所描述的窗口经历了从不可见到可见的状态变化,因此也需要重新计算系统中的窗口的Z轴位置。
经过上面的一系列操作之后,如果最终得到的变量assignLayers的值等于true,那么就需要调用WindowManagerService 类的成员函数assignLayersLocked来执行重新计算统中的窗口的Z轴位置的操作了。在后面的文章中,我们再详细分析 WindowManagerService服务是如何计算系统中的窗口的Z轴位置的。
从上面的分析就可以知道,在布局一个窗口的过程中,可能需要调用WindowManagerService类的成员函数 moveInputMethodWindowsIfNeededLocked来移动系统的输入法窗口和输入法对话框。再结合前面增加窗口的情景,我们就可 以知道,在WindowManagerService类中,与输入法窗口以及输入法对话框相关的成员函数有 addInputMethodWindowToListLocked、adjustInputMethodDialogsLocked和 moveInputMethodWindowsIfNeededLocked,它们的作用如下所示:
A. 成员函数addInputMethodWindowToListLocked用来将输入法窗口插入到窗口堆栈的合适位置,即插入到需要显示输入法窗口的窗口的上面。
B. 成员函数adjustInputMethodDialogsLocked用来移动输入法对话框到窗口堆栈的合适位置,即移动到输入法窗口的上面。
C. 成员函数moveInputMethodWindowsIfNeededLocked用来检查是否需要移动输入法窗口以及输入法对话框。如果需要的话,那 么就将它们移动到窗口堆栈的合适位置去,即将输入法窗口移动到需要显示输入法窗口的窗口的上面,而将输入法对话框移动到输入法窗口的上面。
在分析这三个成员函数的实现之前,我们首先分析WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked和moveInputMethodDialogsLocked,它们是 两个基本的操作,其中:
D. 成员函数findDesiredInputMethodWindowIndexLocked用来查找输入法窗口在窗口堆栈的正确位置,这个位置刚好就是在需要显示输入法窗口的窗口在窗口堆栈中的上一个位置。
E. 成员函数moveInputMethodDialogsLocked用来将移动输入法对话框移动到输入法窗口的上面去。
接下来我们开始分析上述五个函数的实现。
1. 计算输入法窗口在窗口堆栈中的位置
输入法窗口在窗口堆栈中的位置是通过调用WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked来获得的,它首先找到需要显示输入法的窗口在窗口堆栈中的位置,然后再 将这个位置加1,就可以得到输入法窗口在窗口堆栈中的位置。
WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked定义在文件 frameworks/base/services/java/com/android/server /WindowManagerService.java中,它的实现比较长,我们分段来阅读:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- int findDesiredInputMethodWindowIndexLocked(boolean willMove) {
- final ArrayList<WindowState> localmWindows = mWindows;
- final int N = localmWindows.size();
- WindowState w = null;
- int i = N;
- while (i > 0) {
- i--;
- w = localmWindows.get(i);
- ......
- if (canBeImeTarget(w)) {
- ......
- // Yet more tricksyness! If this window is a "starting"
- // window, we do actually want to be on top of it, but
- // it is not -really- where input will go. So if the caller
- // is not actually looking to move the IME, look down below
- // for a real window to target...
- if (!willMove
- && w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
- && i > 0) {
- WindowState wb = localmWindows.get(i-1);
- while (i > 1 && wb.mAppToken == w.mAppToken && !canBeImeTarget(wb)) {
- i--;
- wb = localmWindows.get(i-1);
- }
- if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
- i--;
- w = wb;
- }
- }
- break;
- }
- }
- mUpcomingInputMethodTarget = w;
这段代码从上到下遍历WindowManagerService服务内部的窗口堆栈,即WindowManagerService类的成员变量 mWindows所描述的一个ArrayList。如果发现有一个窗口是可见的,并且需要显示输入法窗口,那么整个查找过程就会结束。检查一个窗口是否可 见以及需要显示输入法窗口是通过调用WindowManagerService类的成员函数canBeImeTarget来实现的。最后得到的需要显示输 入法的窗口就使用WindowState对象w中,这个WindowState对象w接下来还会保存在WindowManagerService类的成员 变量mUpcomingInputMethodTarget中,表示它即将要成为输入法窗口的目标窗口。
参数willMove表示调用者计算输入法窗口在窗口堆栈中的位置的目的。如果它的值等于true,那么就说明调用者获得了输入法窗口在窗口堆栈中的位置 之后,接下来就会将输入法窗口移动到需要显示输入法窗口的窗口的上面去,否则的话,就说明调用者只是为了知道输入法窗口在窗口堆栈中的位置,而不打算移动 输入法窗口。
在从上到下查找需要显示输入法的窗口的过程中,如果找到一个WindowState对象w,它所描述的窗口需要显示输入法窗口,但是这个窗口其实是一个 Activity窗口的启动窗口,即该WindowState对象w的成员变量mAttrs所描述的一个 WindowManager.LayoutParams对象的成员变量type的值等于 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING,那么由于调用 WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked的目的不是用来 移动输入法窗口,而是用来查找输入法窗口在窗口堆栈中的确切位置,因此就不能前面所找到的启动窗口看作是一个需要输入法的窗口,因为这个启动窗口只是 Activity窗口在显示过程中出现的一个临时窗口。在这种情况下,这段代码就会继续沿着窗口堆栈往下查找另外一个窗口,该窗口一方面是需要显示输入法 窗口的,另一方面要与前面所找到的启动窗口对应的是同一个窗口令牌的。如果能找到这样的一个窗口,那么就会将用来描述它的一个WindowState对象 wb保存在变量w中。如果找不到这样的一个窗口,那么这段代码就会继续沿着窗口堆栈往下查找另外一个需要显示输入法的窗口。
我们继续往下阅读代码:
- if (willMove && w != null) {
- final WindowState curTarget = mInputMethodTarget;
- if (curTarget != null && curTarget.mAppToken != null) {
- // Now some fun for dealing with window animations that
- // modify the Z order. We need to look at all windows below
- // the current target that are in this app, finding the highest
- // visible one in layering.
- AppWindowToken token = curTarget.mAppToken;
- WindowState highestTarget = null;
- int highestPos = 0;
- if (token.animating || token.animation != null) {
- int pos = 0;
- pos = localmWindows.indexOf(curTarget);
- while (pos >= 0) {
- WindowState win = localmWindows.get(pos);
- if (win.mAppToken != token) {
- break;
- }
- if (!win.mRemoved) {
- if (highestTarget == null || win.mAnimLayer >
- highestTarget.mAnimLayer) {
- highestTarget = win;
- highestPos = pos;
- }
- }
- pos--;
- }
- }
- if (highestTarget != null) {
- ......
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- // If we are currently setting up for an animation,
- // hold everything until we can find out what will happen.
- mInputMethodTargetWaitingAnim = true;
- mInputMethodTarget = highestTarget;
- return highestPos + 1;
- } else if (highestTarget.isAnimating() &&
- highestTarget.mAnimLayer > w.mAnimLayer) {
- // If the window we are currently targeting is involved
- // with an animation, and it is on top of the next target
- // we will be over, then hold off on moving until
- // that is done.
- mInputMethodTarget = highestTarget;
- return highestPos + 1;
- }
- }
- }
- }
这段代码用来处理一种特殊情况,即参数willMove的值等于true,并且前面找到了一个需要显示输入法的窗口w,但是当前输入法窗口已经存在一个目 标窗口,并且该目标窗口正在切换的过程中。在这种情况下,调用WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked的函数就需要等到当前输入法窗口的目标窗口的切换过程结束之后,再将输 入法窗口移动到窗口w的上面去,换句话说,就是要保持输入法窗口在它当前的目标窗口的上面,直到它当前的目标窗口的切换过程结束为止。这样 WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked就需要找到当前 输入法窗口的目标窗口在窗口堆栈中的位置,然后再将该位置加1后返回给调用者。
当WindowManagerService类的成员变量mInputMethodTarget的值不等于null,并且它描述的是一个 Activity窗口时,即它的成员变量mAppToken的值不等于null时,那么就说明当前输入法窗口已经存在一个目标窗口,而这个目标窗口就是使 用WindowManagerService类的成员变量mInputMethodTarget所指向的一个WindowState对象来描述的。接下来 这段代码就检查该目标窗口是否正在切换的过程中,即是否正在显示切换动画。如果是的话,那么WindowState对象curTarget的成员变量 animating的值就会等于true,或者另外一个成员变量animation的值不等于null,这时候就需要在与该目标窗口所对应的窗口令牌 token所描述的一组窗口中,找到一个Z轴位置最大的并且不是已经被移除的窗口。WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked的调用者最后就是需要将输入法窗口移动到这个Z轴位置最大的并且不是已 经被移除的窗口的上面的。
一个窗口的Z轴位置是记录在用描述它的一个WindowState对象的成员变量mAnimLayer中的,而它是否是已经被移除是记录在这个 WindowState对象的成员变量mRemoved中的,因此,如果在窗口令牌token所描述的一组WindowSate对象中,能找到一个 WindowSate对象,它的成员变量mAnimLayer的值最大,并且它的成员变量mRemoved不等于true,那么这段代码就会将它保存在变 量highestTarget中,并且将它描述的窗口在窗口堆栈中的位置保存在变量highestPos中。
经过前面的一系列计算之后,如果变量highestTarget的值不等于null,那么就说明我们碰到前面所说的特殊的情况,这时候又要分为两种情况来讨论。
第一种情况是当前输入法窗口的目标窗口即将要进入到切换过程,但是这个切换过程尚开始,即WindowManagerService类的成员变量 mNextAppTransition的值不等于WindowManagerPolicy.TRANSIT_UNSET。这时候就需要将 WindowManagerService类的成员变量mInputMethodTargetWaitingAnim的值设置为true,表示当前输入法 窗口的目标窗口正在等待进入切换动画中,并且需要将WindowManagerService类的成员变量mInputMethodTarget修正为变 量highestTarget所描述的一个WindowState对象,因为这个WindowState对象才是真正用来描述当前输入法窗口的目标窗口 的。
第二种情况是当前输入法窗口的目标窗口已经处于切换的过程了,即变量highestTarget所描述的一个WindowState对象的成员函数 isAnimating的返回值为true,并且该目标窗口的Z轴位置大于前面所找到的需要显示输入法窗口的窗口的Z轴,即变量 highestTarget所描述的一个WindowState对象的成员变量mAnimLayer的值大于变量w所描述的一个WindowState对 象的成员变量mAnimLayer的值。这时候就需要将WindowState对象highestTarget所描述的窗口维持为当前输入法窗口的目标窗 口,即将WindowManagerService类的成员变量mInputMethodTarget设置为变量highestTarget,直到 WindowState对象highestTarget所描述的窗口的切换过程结束为止。
上述两种情况最后都需要将WindowState对象highestTarget所描述的窗口在窗口堆栈中的位置highestPos加1,然后再返回给调用者,以便调用者接下来可以输入法窗口移动在窗口堆栈的第(highestPos+1)个位置上。
如果我们没有碰到前面所说的特殊的情况,那么WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked就会继续往下执行:
- if (w != null) {
- if (willMove) {
- ......
- mInputMethodTarget = w;
- if (w.mAppToken != null) {
- setInputMethodAnimLayerAdjustment(w.mAppToken.animLayerAdjustment);
- } else {
- setInputMethodAnimLayerAdjustment(0);
- }
- }
- return i+1;
- }
如果变量w的值不等于null,那么就说明WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked在前面找到了一个需要显示输入法窗口的窗口。这个窗口是使用 WindowState对象w来描述的,并且它在窗品堆栈中的位置记录在变量i中。这时候WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked就会执行以下三个操作:
A. 将WindowState对象w保存在WindowManagerService类的成员变量mInputMethodTarget中,以便WindowManagerService服务可以知道当前输入法窗口的目标窗口是什么。
B. 检查WindowState对象w描述的窗口是否是Activity窗口,即检查WindowState对象w的成员变量mAppToken的值是否不等 于null。如果WindowState对象w描述的窗口是Activity窗口的话,那么就需要根据WindowState对象w的成员变量 mAppToken所描述的一个AppWindowToken对象的成员变量animLayerAdjustment来调整系统中的输入法窗口以及输入法 对话框的Z轴位置,即在系统中的输入法窗口以及输入法对话框的现有Z轴位置的基础上再增加一个调整量,这个调整量就保存在WindowState对象w的 成员变量mAppToken所描述的一个AppWindowToken对象的成员变量animLayerAdjustment中。这个调整的过程是通过调 用WindowManagerService类的成员函数setInputMethodAnimLayerAdjustment来实现的。如果 WindowState对象w描述的窗口不是Activity窗口,那么就不需要调整系统中的输入法窗口以及输入法对话框的Z轴位置,但是仍然需要调用 WindowManagerService类的成员函数setInputMethodAnimLayerAdjustment来将系统中的输入法窗口以及 输入法对话框的Z轴位置调整量设置为0,即将WindowManagerService类的成员变量 mInputMethodAnimLayerAdjustment的值设置为0。
C. 将变量i的值加1之后返回给调用者,以便调用者可以将系统中的输入法窗口移动到窗口堆栈中的第(i+1)个位置上。
如果变量w的值等于null,那么就说明WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked在前面没有找到一个需要显示输入法窗口的窗口,我们继续往下阅读它的代 码,以便可以了解它是如何处理这种情况的:
- if (willMove) {
- ......
- mInputMethodTarget = null;
- setInputMethodAnimLayerAdjustment(0);
- }
- return -1;
- }
- ......
- }
WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked对在前面没有 找到一个需要显示输入法窗口的窗口的情况的处理很简单。它判断参数willMove的值是否等于true。如果等于true的话,那么就会将 WindowManagerService类的成员变量mInputMethodTarget的值设置为null,并且调用 WindowManagerService类的成员函数setInputMethodAnimLayerAdjustment来将系统中的输入法窗口以及 输入法对话框的Z轴位置调整量设置为0。这实际上是用来通知WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked的调用者,系统当前没有需要显示输入法窗口的窗口。
最后,WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked返回一个-1值给调用者,也是表明系统当前没有需要显示输入法窗口的窗口。
2. 移动输入法对话框移动到输入法窗口的上面
系统中的输入法对话框是需要位于输入法窗口的上面的,因此,我们就需要有一个函数来将输入法对话框移动到输入法窗口的上面去。这个函数就是 WindowManagerService类的成员函数moveInputMethodDialogsLocked,它的实现如下所示:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- void moveInputMethodDialogsLocked(int pos) {
- ArrayList<WindowState> dialogs = mInputMethodDialogs;
- final int N = dialogs.size();
- ......
- for (int i=0; i<N; i++) {
- pos = tmpRemoveWindowLocked(pos, dialogs.get(i));
- }
- ......
- if (pos >= 0) {
- final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken;
- if (pos < mWindows.size()) {
- WindowState wp = mWindows.get(pos);
- if (wp == mInputMethodWindow) {
- pos++;
- }
- }
- ......
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- win.mTargetAppToken = targetAppToken;
- pos = reAddWindowLocked(pos, win);
- }
- ......
- return;
- }
- for (int i=0; i<N; i++) {
- WindowState win = dialogs.get(i);
- win.mTargetAppToken = null;
- reAddWindowToListInOrderLocked(win);
- ......
- }
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
在调用WindowManagerService类的成员函数moveInputMethodDialogsLocked之前,必须要保证系统中的输入法 窗口已经被移动到窗口堆栈的正确位置,即已经被移动到需要显示输入法窗口的窗口的上面。这时候参数pos描述的或者是输入法窗口在窗口堆栈中的位置,或者 是输入法窗口在窗口堆栈的位置的上一个位置,即输入法对话框在窗口堆栈中的起始位置。参数pos的值还可以小于0,这时候就表示系统当前没有需要显示输入 法窗口的窗口。
在移动输入法对话框到输入法窗口的上面之前,首先要将输入法对话框从窗口堆栈中移除,以便接下来可以重新将它们插入到窗口堆栈中。系统中的输入法对话框都 保存在WindowManagerService类的成员变量mInputMethodDialogs所描述的一个ArrayList中,通过调用 WindowManagerService类的成员函数来tmpRemoveWindowLocked来移除保存在这个ArrayList中的每一个 WindowState对象,就可以将系统中的输入法对话框从窗口堆栈中移除中。注意,将一个WindowState对象从窗口堆栈中移除之后,可能会影 响参数pos的值。例如,如果参数pos的值大于被移除的WindowState对象原来在窗口堆栈中的位置值,那么在该WindowState对象被移 除之后,参数pos的值就要相应地减少1,这样它才能正确地反映输入法窗口在窗口堆栈中的位置,或者输入法对话框在窗口堆栈中的起始位置。 WindowManagerService类的成员函数来tmpRemoveWindowLocked在将一个WindowState对象从窗口堆栈中移 除的过程中,会正确处理好参数pos的值,这一点可以参考前面Android窗口管理服务WindowManagerService组织窗口的方式分析一文。
接下来,我们就分为两种情况来分析输入法对话框在窗口是如何移动到输入法窗口的上面去的。
第一种情况是参数pos的值大于等于0,这表明系统当前存在一个需要显示输入法窗口的窗口,这个窗口是通过WindowManagerService类的成员变量mInputMethodTarget所指向的一个WindowState对象来描述的。
前面提到,参数pos描述的或者是输入法窗口在窗口堆栈中的位置,或者是输入法对话框在窗口堆栈中的起始位置,我们首先要将它统一描述为输入法对话框在窗 口堆栈中的起始位置。这时候就需要检查保存在窗口堆栈的第pos个位置的WindowState对象wp,是否就是 WindowManagerService类的成员变量mInputMethodWindow所指向的那个WindowState对象。如果是的话,那么 就说明参数pos描述的或者是输入法窗口在窗口堆栈中的位置,这时候将它的值增加1,就可以让它表示为输入法对话框在窗口堆栈中的起始位置。
得到了输入法对话框在窗口堆栈中的起始位置pos之后,接下来只需要调用WindowManagerService类的成员函数 reAddWindowLocked来依次地将保存在WindowManagerService类的成员变量mInputMethodDialogs所描 述的一个ArrayList中的第i个WindowState对象保存在窗口堆栈中的第(pos+i)个以位置上即可,这样就可以将输入法对话框都移动到 输入法窗口的上面去了。
注意,在移动的过程中,用来描述每一个输入法对话框的每一个WindowState对象的成员变量mTargetAppToken的值设置为 WindowManagerService类的成员变量mInputMethodTarget所描述的一个WindowState对象的成员变量 mAppToken的值,以便可以将输入法对话框和输入法窗口的目标窗口设置为同一个窗口。
第二种情况是参数pos的值小于0,这表明系统当前不存在一个需要显示输入法窗口的窗口。这时候就需要根据输入法窗口自身的属性来将它们移动到窗口堆 栈的合适的位置上去,这是通过调用WindowManagerService类的成员函数reAddWindowToListInOrderLocked 来实现的。WindowManagerService类的成员函数reAddWindowToListInOrderLocked的实现可以参考前面Android窗口管理服务WindowManagerService组织窗口的方式分析一文,这里不再详细。
注意,在移动的过程中,用来描述每一个输入法对话框的每一个WindowState对象的成员变量mTargetAppToken的值会被设置为null,这是因为系统当前不存在一个需要显示输入法窗口的窗口,即这时候每一个输入法对话框都没有目标窗口。
理解了WindowManagerService类的成员函数findDesiredInputMethodWindowIndexLocked和 moveInputMethodDialogsLocked的实现之后,对WindowManagerService类的另外三个成员函数 addInputMethodWindowToListLocked、adjustInputMethodDialogsLocked和 moveInputMethodWindowsIfNeededLocked的实现就很有帮助,接下来我们就继续分析这三个成员函数的实现。
3. 插入输入法窗口到需要显示输入法窗口的窗口上面
插入输入法窗口到窗口堆栈的合适位置,使得它位于需要显示输入法窗口的窗口上面,这是通过调用WindowManagerService类的成员函数addInputMethodWindowToListLocked来实现的,它的实现如下所示:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- void addInputMethodWindowToListLocked(WindowState win) {
- int pos = findDesiredInputMethodWindowIndexLocked(true);
- if (pos >= 0) {
- win.mTargetAppToken = mInputMethodTarget.mAppToken;
- ......
- mWindows.add(pos, win);
- mWindowsChanged = true;
- moveInputMethodDialogsLocked(pos+1);
- return;
- }
- win.mTargetAppToken = null;
- addWindowToListInOrderLocked(win, true);
- moveInputMethodDialogsLocked(pos);
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
参数win描述的是要添加到窗口堆栈中去的输入法窗口。
WindowManagerService类的成员函数addInputMethodWindowToListLocked首先调用另外一个成员函数 findDesiredInputMethodWindowIndexLocked来计算输入法窗口在窗口堆栈中的位置,并且保存在变量pos。
如果变量pos的值大于等于0,那么就说明WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked在窗口堆栈中找到了一个合适的位置来放置输入法窗口,于是接下来就会参 数win所描述的输入法窗口插入在WindowManagerService类的成员变量mWIndows所描述的窗口堆栈的第pos个位置上。由于系统 中的输入法对话框要保持在输入法窗口的上面,因此,WindowManagerService类的成员函数 addInputMethodWindowToListLocked还需要继续调用另外一个成员函数 moveInputMethodDialogsLocked来将系统中的输入法对话框在窗口堆栈中的起始位置设置为(pos+1)。
还有一个地方需要注意的是,前面在调用WindowManagerService类的成员函数 addInputMethodWindowToListLocked来计算输入法窗口在窗口堆栈中的位置的时候,已经将用来描述需要显示输入法窗口的 Activity窗口的一个WindowState对象保存了WindowManagerService类的成员变量 mInputMethodTarget中,因此,这里就需要这个WindowState对象的成员变量mAppToken所指向的一个 AppWindowToken对象保存在用来描述输入法窗口的WindowState对象的win的成员变量mTargetAppToken中,以便 WindowManagerService服务可以知道当前输入法窗口的目标窗口是什么。
如果变量pos的值小于0,那么就说明WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked没有找一个需要输入法窗口的窗口,因此,这时候就需要调用另外一个成员 函数addWindowToListInOrderLocked来将参数win所描述的输入法窗口插入到窗口堆栈中去。 WindowManagerService类的成员函数addWindowToListInOrderLocked会根据要目标窗口所对应的窗口令牌在窗 口令牌列表中的位置以及是否在窗口堆栈中存在其它窗口等信息来在窗口堆栈中找到一个合适的前位置来放置目标窗口,它的具体实现可以参考前面Android窗口管理服务WindowManagerService组织窗口的方式分析一 文。将参数win所描述的输入法窗口插入到窗口堆栈中去之后,WindowManagerService类的成员函数 addInputMethodWindowToListLocked还需要继续调用另外一个成员函数 moveInputMethodDialogsLocked来调整系统中的输入法对话框。
注意,在调用WindowManagerService类的成员函数moveInputMethodDialogsLocked的时候,传递进去的参数 pos的值等于-1,这时候WindowManagerService类的成员函数moveInputMethodDialogsLocked就不是直接 调整输入法对话框在窗口堆栈中的位置的,而是调用另外一个成员函数reAddWindowToListInOrderLocked来调整的。
还有另外一个地方需要注意的是,由于前面在调用WindowManagerService类的成员函数 findDesiredInputMethodWindowIndexLocked的时候,没有找到一个需要输入法窗口的窗口,因此,这里就需要将参数 win所描述的一个WindowState对象的成员变量mTargetAppToken的值设置为null,以便 WindowManagerService服务可以知道当前输入法窗口的没有目标窗口。
4. 调整输入法对话框在窗口堆栈的位置
一旦系统中存在需要显示输入法窗口的窗口,那么就需要系统中的输入法对话框在窗口堆栈中的位置,使得它们放置在输入法窗品的上面,这是通过调用 WindowManagerService类的成员函数adjustInputMethodDialogsLocked来实现的,如下所示:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- void adjustInputMethodDialogsLocked() {
- moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类的成员函数adjustInputMethodDialogsLocked的实现很简单,它首先调用成员 函数findDesiredInputMethodWindowIndexLocked来找到输入法窗口在窗口堆栈中的位置,然后再调用成员函数 moveInputMethodDialogsLocked来将输入法对话框保存在这个位置之上。
5. 调整输入法窗口在窗口堆栈的位置
当系统中的窗口布局发生了变化之后,例如,当前获得焦点的窗口发生了变化,或者新增了一个窗口,那么都可能需要调整输入法窗口在窗口堆栈中的位置,以便它 可以痊于需要显示输入法窗口的窗口的上面,这是通过调用WindowManagerService类的成员函数 moveInputMethodWindowsIfNeededLocked来实现的,如下所示:
- public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor {
- ......
- boolean moveInputMethodWindowsIfNeededLocked(boolean needAssignLayers) {
- final WindowState imWin = mInputMethodWindow;
- final int DN = mInputMethodDialogs.size();
- if (imWin == null && DN == 0) {
- return false;
- }
- int imPos = findDesiredInputMethodWindowIndexLocked(true);
- if (imPos >= 0) {
- // In this case, the input method windows are to be placed
- // immediately above the window they are targeting.
- // First check to see if the input method windows are already
- // located here, and contiguous.
- final int N = mWindows.size();
- WindowState firstImWin = imPos < N
- ? mWindows.get(imPos) : null;
- // Figure out the actual input method window that should be
- // at the bottom of their stack.
- WindowState baseImWin = imWin != null
- ? imWin : mInputMethodDialogs.get(0);
- if (baseImWin.mChildWindows.size() > 0) {
- WindowState cw = baseImWin.mChildWindows.get(0);
- if (cw.mSubLayer < 0) baseImWin = cw;
- }
- if (firstImWin == baseImWin) {
- // The windows haven‘t moved... but are they still contiguous?
- // First find the top IM window.
- int pos = imPos+1;
- while (pos < N) {
- if (!(mWindows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- pos++;
- // Now there should be no more input method windows above.
- while (pos < N) {
- if ((mWindows.get(pos)).mIsImWindow) {
- break;
- }
- pos++;
- }
- if (pos >= N) {
- // All is good!
- return false;
- }
- }
- if (imWin != null) {
- ......
- imPos = tmpRemoveWindowLocked(imPos, imWin);
- ......
- imWin.mTargetAppToken = mInputMethodTarget.mAppToken;
- reAddWindowLocked(imPos, imWin);
- ......
- if (DN > 0) moveInputMethodDialogsLocked(imPos+1);
- } else {
- moveInputMethodDialogsLocked(imPos);
- }
- } else {
- // In this case, the input method windows go in a fixed layer,
- // because they aren‘t currently associated with a focus window.
- if (imWin != null) {
- ......
- tmpRemoveWindowLocked(0, imWin);
- imWin.mTargetAppToken = null;
- reAddWindowToListInOrderLocked(imWin);
- ......
- if (DN > 0) moveInputMethodDialogsLocked(-1);;
- } else {
- moveInputMethodDialogsLocked(-1);;
- }
- }
- if (needAssignLayers) {
- assignLayersLocked();
- }
- return true;
- }
- ......
- }
这个函数定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。
WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked首先检查系统中是 否存在输入法窗口和输入法对话框,即检查WindowManagerService类的成员变量mInputMethodWindow的值是否等于 null,并且WindowManagerService类的成员变量mInputMethodDialogs所描述的一个ArrayList的大小是否 等于0。如果输入法窗口和输入法对话框都不存在的话,那么就不用调整它们在窗口堆栈中的位置了,否则的话,WindowManagerService类的 成员变量mInputMethodWindow所指向的一个WindowState对象就会保存在变量imWin中,以便接下来可以通过它来描述系统中的 输入法窗口。
在输入法窗口或者输入法对话框存在的情况下,WindowManagerService类的成员函数 moveInputMethodWindowsIfNeededLocked接下来就会继续调用另外一个成员函数 findDesiredInputMethodWindowIndexLocked来找到输入法窗口在窗口堆栈中的位置,并且保存在变量imPos中。注 意,变量imPos的值可能大于等于0,也可能等于-1。当变量imPos的值大于等于0的时候,就说明系统当前存在一个窗口需要显示输入法窗口,而当变 量imPos的值等于-1的时候,就说明系统当前不存在一个窗口需要显示输入法窗口,或者系统中不存在输入法窗口。接下来我们分两种情况来分析 WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked的实现。
第一种情况是变量imPos的值可能大于等于0。这时候可能需要调整输入法窗口在窗口堆栈中的位置,也可能不需要调整输入法窗口在窗口堆栈中的位置,取决 于输入法窗口的位置是否已经在窗口堆栈的第imPos个位置上,以及是否所有与输入法相关的窗口都连续在放置在窗口堆栈中。
变量firstImWin描述的是当前位于窗口堆栈中Z轴位置最小的与输入法相关的窗口,它是通过变量imPos来获得的。另外一个变量 baseImWin描述的是Z轴位置最小的与输入法相关的窗口。如果这两个变量描述的是同一个窗口,那么就说明输入法窗口的位置已经在窗口堆栈的第 imPos个位置上,因此,就有可能不需要调整输入法窗品在窗口堆栈中的位置了。接下来我们就描述如何找到这个Z轴位置最小的与输入法相关的窗口。
如果变量imWin的值不等于null,即WindowManagerService类的成员变量mInputMethodWindow的值不等于 null,那么它所描述的窗口就是Z轴位置最小的与输入法相关的窗口,否则的话,Z轴位置最小的与输入法相关的窗口就是位于 WindowManagerService类的成员变量mInputMethodDialogs所描述的一个ArrayList的第0个位置上的输入法对 话框。这一步得到的Z轴位置最小的与输入法相关的窗口就保存在变量baseImWin中。
如果变量baseImWin所描述的窗口有子窗口,即它所指向的一个WindowState对象的成员变量mChildWindows所描述的一个 ArrayList的大小大于0。这时候如果用来描述第一个子窗口的WindowState对象的成员变量mSubLayer的值小于0,那么就说明变量 baseImWin所描述的窗口在所有与输入法相关的窗口中的Z轴位置还不是最小的,因为在它的下面还存在着Z轴位置更小的子窗口。在这种情况下,变量 baseImWin就会指向这个Z轴位置最小的子窗口。
经过上面的一系列计算之后,如果变量firstImWin和变量baseImWin描述的是同一个窗口,那么还需要继续判断所有与输入法相关的窗口都连续在放置在窗口堆栈中。判断的方法如下所示:
(1). 从窗口堆栈的第(imPos + 1)个位置开始往上查找一个非输入法相关的窗口。
(2). 如果第(1)步能在窗口堆栈中大于等于(imPos+1)的位置pos上找到一个非输入法窗口,那么再继续从第pos个位置开始往上查找一个与输入法相关的窗口。
(3). 如果第(2)步能在窗口堆栈中找到一个与输入法相关的窗口,那么就说明所有与输入法相关的窗口不是连续在放置在窗口堆栈中的,因为在它们中间有一个非输入法相关的窗口,否则的话,就说明所有与输入法相关的窗口都是连续在放置在窗口堆栈中的。
在所有与输入法相关的窗口都是连续在放置在窗口堆栈中的情况下,WindowManagerService类的成员函数 moveInputMethodWindowsIfNeededLocked就会直接返回一个false值给调用者,表明不需要调整系统中的输入法窗口以 及输入法对话框在窗口堆栈中的位置。
在所有与输入法相关的窗口不是连续在放置在窗口堆栈中的情况下,就需要重新调整系统中的输入法窗口以及输入法对话框在窗口堆栈中的位置。这里又需要分两个情景来讨论。
第一个情景是变量imWin的值不等于null,这时候说明系统中存在一个输入法窗口,因此,就需要调整这个输入法窗口在窗口堆栈中的位置。调整的方法很简单:
(1). 调用WindowManagerService类的成员函数tmpRemoveWindowLocked来从窗口堆栈中移除变量imWin所描述的输入法 窗口。在移除的过程中,会同时计算输入法窗口在窗口堆栈中的新位置,这个位置还是保存在变量imPos中。
(2). 调用WindowManagerService类的成员函数reAddWindowLocked重新将变量imWin所描述的输入法窗口插入到窗口堆栈的 第imPos个位置中。在插入之前,还会将变量imWin所描述的一个WindowState对象的成员变量mTargetAppToken与 WindowManagerService类的成员变量mInputMethodTarget所描述的一个WindowState对象的成员变量 mAppToken指向同一个AppWindowToken对象,这样WindowManagerService服务就可以知道imWin所描述的输入法 窗口的目标窗口是什么。
(3). 如果系统中还存在输入法对话框,那么就调用WindowManagerService类的成员函数moveInputMethodDialogsLocked来将它们放置在第(imPos+1)个位置上,目的是将它们放置在输入法窗口的上面。
第二个情景是变量imWin的值等于null,这时候说明系统中不存在输入法窗口。在这个情景下,系统中肯定会存在输入法对话框,否则的 话,WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked在前面就会返回 了。因此,WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked接下来就会 直接调用成员函数moveInputMethodDialogsLocked来将系统中的输入法对话框放置在在第imPos个位置上。
第二种情况是变量imPos的值等于-1。这时候说明系统中不存在需要显示输入法窗口的窗口。这里同样也需要分两个情景来分析。
第一个情景是变量imWin的值不等于null,这时候说明系统中存在一个输入法窗口,因此,就需要调整这个输入法窗口在窗口堆栈中的位置。调整的方法与 前面第一种情况的第一个情景是类似的。不过由于事先不知道输入法窗口在窗口堆栈中的位置,因此,这里就会调用WindowManagerService类 的成员函数reAddWindowToListInOrderLocked和moveInputMethodDialogsLocked来间接地调整输入 法窗口和输入法对话框在窗口堆栈中的位置。注意,在调用WindowManagerService类的成员函数 moveInputMethodDialogsLocked的时候,传进去的参数为-1。另外一个地方需要注意的是,在 WindowManagerService类的成员函数reAddWindowToListInOrderLocked来间接地调整输入法窗口在窗口堆栈 中的位置之前,会将量imWin所描述的一个WindowState对象的成员变量mTargetAppToken的值设置为null,这样 WindowManagerService服务就可以知道imWin所描述的输入法窗口没有目标窗口。
第二情景是变量imWin的值等于null,这时候系统中不存在输入法窗口。这个情景与前面第一种情况的第二个情景也是类似的。由于系统中不存在输入法窗 口,因此只需要调用WindowManagerService类的成员函数moveInputMethodDialogsLocked来间接地输入法对话 框在窗口堆栈中的位置即可,即以参数-1来调用WindowManagerService类的成员函数 moveInputMethodDialogsLocked。
至此,我们就分析完成WindowManagerService服务对输入法窗口的基本操作了。从分析的过程中,我们可以得到以下两个结论:
A. 系统中与输入法相关的窗口有两种,一种是输入法窗口,另一种是输入法对话框。
B. 当Z轴位置最大的窗口需要使用输入法时,输入法窗口就会位于它的上面,而输入法对话框又会位于输入法窗口的上面。
在WindowManagerService服务中,还有一种类型的窗口与输入法窗口类似,它总是与Activity窗口粘在一起。不过,这种类型的窗口 是位于Activity窗口的下面,刚好与输入法窗口相反,它就是壁纸窗口(Wallpaper)。在接下来的一篇文章中,我们就将继续分析 WindowManagerService服务是如何管理系统中的壁纸窗口的。敬请关注!
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析