原文出處——>[Android窗口管理服務WindowManagerService對輸入法窗口(Input Method Window)的管理分析](http://blog.csdn.net/luoshengyang/article/details/8526644)
在Android系統中,輸入法窗口是一種特殊類型的窗口,它總是位于需要使用輸入法的窗口的上面。也就是說,一旦WindowManagerService服務檢測到焦點窗口需要使用輸入法,那么它就會調整輸入法窗口在窗口堆棧中的位置,使得輸入法窗口位于在焦點窗口的上面,這樣用戶可以通過輸入法窗口來錄入字母或者文字。本文就將詳細分析WindowManagerService服務是如何管理系統中的輸入法窗口的。
《Android系統源代碼情景分析》一書正在進擊的程序員網(http://0xcc0xcd.com)中連載,點擊進入!
在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來實現的。接下來我們就主要分析這個函數中與輸入法窗口以及輸入法對話框調整相關的邏輯,如下所示:
[java] view plain copy
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來實現的。接下來我們就主要分析這個函數中與輸入法窗口以及輸入法對話框調整相關的邏輯,如下所示:
[java] view plain copy
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中,它的實現比較長,我們分段來閱讀:
[java] view plain copy
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中。如果找不到這樣的一個窗口,那么這段代碼就會繼續沿著窗口堆棧往下查找另外一個需要顯示輸入法的窗口。
我們繼續往下閱讀代碼:
[java] view plain copy
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就會繼續往下執行:
[java] view plain copy
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在前面沒有找到一個需要顯示輸入法窗口的窗口,我們繼續往下閱讀它的代碼,以便可以了解它是如何處理這種情況的:
[java] view plain copy
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,它的實現如下所示:
[java] view plain copy
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來實現的,它的實現如下所示:
[java] view plain copy
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來實現的,如下所示:
[java] view plain copy
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來實現的,如下所示:
[java] view plain copy
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服務是如何管理系統中的壁紙窗口的。敬請關注!
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析