這是一篇介紹Android窗口管理服務功能的文章,下面就讓我們一起來了解一下吧!
在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來實現的。接下來我們就主要分析這個函數中與輸入法窗口以及輸入法對話框調整相關的邏輯,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
WindowState mInputMethodWindow = null;
final ArrayList mInputMethodDialogs = new ArrayList();
......
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類的成員函數addInputMethodWindowToListLock