Android視窗管理服務WindowManagerService對輸入法視窗(Input Method Window)的管理分析
在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來實現的。接下來我們就主要分析這個函式中與輸入法視窗以及輸入法對話方塊調整相關的邏輯,如下所示:
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的值是否