您现在的位置:首页 > 博客 > Android开发 > 正文
Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
http://www.drovik.com/      2013-2-1 15:58:30      来源:老罗的Android之旅      点击:
      接下来,我们就分为两种情况来分析输入法对话框在窗口是如何移动到输入法窗口的上面去的。

        第一种情况是参数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来实现的,它的实现如下所示:

  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     void addInputMethodWindowToListLocked(WindowState win) {  
  6.         int pos = findDesiredInputMethodWindowIndexLocked(true);  
  7.         if (pos >= 0) {  
  8.             win.mTargetAppToken = mInputMethodTarget.mAppToken;  
  9.             ......  
  10.             mWindows.add(pos, win);  
  11.             mWindowsChanged = true;  
  12.             moveInputMethodDialogsLocked(pos+1);  
  13.             return;  
  14.         }  
  15.         win.mTargetAppToken = null;  
  16.         addWindowToListInOrderLocked(win, true);  
  17.         moveInputMethodDialogsLocked(pos);  
  18.     }  
  19.   
  20.     ......  
  21. }  
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来实现的,如下所示:

  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     void adjustInputMethodDialogsLocked() {  
  6.         moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));  
  7.     }  
  8.   
  9.     ......  
  10. }  
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来实现的,如下所示:

  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     boolean moveInputMethodWindowsIfNeededLocked(boolean needAssignLayers) {  
  6.         final WindowState imWin = mInputMethodWindow;  
  7.         final int DN = mInputMethodDialogs.size();  
  8.         if (imWin == null && DN == 0) {  
  9.             return false;  
  10.         }  
  11.   
  12.         int imPos = findDesiredInputMethodWindowIndexLocked(true);  
  13.         if (imPos >= 0) {  
  14.             // In this case, the input method windows are to be placed  
  15.             // immediately above the window they are targeting.  
  16.   
  17.             // First check to see if the input method windows are already  
  18.             // located here, and contiguous.  
  19.             final int N = mWindows.size();  
  20.             WindowState firstImWin = imPos < N  
  21.                     ? mWindows.get(imPos) : null;  
  22.   
  23.             // Figure out the actual input method window that should be  
  24.             // at the bottom of their stack.  
  25.             WindowState baseImWin = imWin != null  
  26.                     ? imWin : mInputMethodDialogs.get(0);  
  27.             if (baseImWin.mChildWindows.size() > 0) {  
  28.                 WindowState cw = baseImWin.mChildWindows.get(0);  
  29.                 if (cw.mSubLayer < 0) baseImWin = cw;  
  30.             }  
  31.   
  32.             if (firstImWin == baseImWin) {  
  33.                 // The windows haven't moved...  but are they still contiguous?  
  34.                 // First find the top IM window.  
  35.                 int pos = imPos+1;  
  36.                 while (pos < N) {  
  37.                     if (!(mWindows.get(pos)).mIsImWindow) {  
  38.                         break;  
  39.                     }  
  40.                     pos++;  
  41.                 }  
  42.                 pos++;  
  43.                 // Now there should be no more input method windows above.  
  44.                 while (pos < N) {  
  45.                     if ((mWindows.get(pos)).mIsImWindow) {  
  46.                         break;  
  47.                     }  
  48.                     pos++;  
  49.                 }  
  50.                 if (pos >= N) {  
  51.                     // All is good!  
  52.                     return false;  
  53.                 }  
  54.             }  
  55.   
  56.             if (imWin != null) {  
  57.                 ......  
  58.                 imPos = tmpRemoveWindowLocked(imPos, imWin);  
  59.                 ......  
  60.                 imWin.mTargetAppToken = mInputMethodTarget.mAppToken;  
  61.                 reAddWindowLocked(imPos, imWin);  
  62.                 ......  
  63.                 if (DN > 0) moveInputMethodDialogsLocked(imPos+1);  
  64.             } else {  
  65.                 moveInputMethodDialogsLocked(imPos);  
  66.             }  
  67.   
  68.         } else {  
  69.             // In this case, the input method windows go in a fixed layer,  
  70.             // because they aren't currently associated with a focus window.  
  71.   
  72.             if (imWin != null) {  
  73.                 ......  
  74.                 tmpRemoveWindowLocked(0, imWin);  
  75.                 imWin.mTargetAppToken = null;  
  76.                 reAddWindowToListInOrderLocked(imWin);  
  77.                 ......  
  78.                 if (DN > 0) moveInputMethodDialogsLocked(-1);;  
  79.             } else {  
  80.                 moveInputMethodDialogsLocked(-1);;  
  81.             }  
  82.   
  83.         }  
  84.   
  85.         if (needAssignLayers) {  
  86.             assignLayersLocked();  
  87.         }  
  88.   
  89.         return true;  
  90.     }  
  91.   
  92.     ......  
  93. }  
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://blog.csdn.net/luoshengyang/article/details/8526644

 

分享到:
发表评论(2)
1楼 Shha  发表于  2013-2-17 4:11:06
Call me wind because I am asboluetly blown away.
2楼 izdqkp  发表于  2013-2-19 19:07:29
NODnvZ , [url=http://xailgvuafjty.com/]xailgvuafjty[/url], [link=http://ftrtocnbahjf.com/]ftrtocnbahjf[/link], http://kiwluyngufcl.com/
姓名 *
评论内容 *
验证码 *图片看不清?点击重新得到验证码