回到WMS的addWindow()函數中,繼續往下看:
**WindowManagerService.java::WindowManagerService.addWindow()**
```
public int addWindow(Session session, IWindowclient, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
RectoutContentInsets, InputChannel outInputChannel) {
......
synchronized(mWindowMap){
//在前面的代碼中,WMS驗證了添加窗口的令牌的有效性,并為新窗口創建了新的WindowState對象
// 新的WindowState對象在其構造函數中根據窗口類型初始化了其主序mBaseLayer和mSubLayer
......
// 接下來,將新的WindowState按照顯示次序插入到當前DisplayContent的mWindows列表中
// 為了代碼結構的清晰,不考慮輸入法窗口和壁紙窗口的處理
if (type== TYPE_INPUT_METHOD) {
......
}else if (type == TYPE_INPUT_METHOD_DIALOG) {
}else {
// 將新的WindowState按顯示次序插入到當前DisplayContent的mWindows列表中
addWindowToListInOrderLocked(win,true);
if(type == TYPE_WALLPAPER) {
......
}
}
......
// 根據窗口的排序結果,為DisplayContent的所有窗口分配最終的顯示次序
assignLayersLocked(displayContent.getWindowList());
......
}
......
returnres;
}
```
這里有兩個關鍵點:
- addWindowToListInOrderLocked()將新建的WindowState按照一定的順序插入到當前DisplayContent的mWindows列表中。在分析WMS的重要成員時提到過這個列表。它嚴格地按照顯示順序存儲了所有窗口的WindowState。
- assignLayersLocked()將根據mWindows的存儲順序對所有的WindowState的主序和子序進行調整。
接下來分別分析一下這兩個函數。
#### 1.addWindowToListInOrderLocked()分析
addWindowToListInOrderLocked()的代碼很長,不過其排序原則卻比較清晰。這里直接給出其處理原則,感興趣的讀者可根據這些原則自行深究相關代碼。
**注意** :再次強調一下,mWindows列表是按照主序與子序的升序進行排序的,所以顯示靠前的窗口放在列表靠后的位置,而顯示靠前的窗口,則位于列表的前面。也就是說,列表順序與顯示順序是相反的。這點在閱讀代碼時要牢記,以免混淆。
在后面的敘述中,非特別強調,所謂的前后都是指顯示順序而不是在列表的存儲順序。
**子窗口的排序規則**:子窗口的位置計算是相對父窗口的,并根據其子序進行排序。由于父窗口的子序為0,所以子序為負數的窗口會放置在父窗口的后面,而子序為正數的窗口會放置在父窗口的前面。如果新窗口與現有窗口子序相等,則正數子序的新窗口位于現有窗口的前面,負數子序的新窗口位于現有窗口的后面。
非子窗口的排序則是依據主序進行的,但是其規則較為復雜,分為應用窗口和非應用窗口兩種情況。之所以要區別處理應用窗口是因為所有的應用窗口的初始主序都是21000,并且應用窗口的位置應該與它所屬的應用的其他窗口放在一起。例如應用A顯示于應用B的后方,當應用A因為某個動作打開一個新的窗口時,新窗口應該位于應用A其他窗口的前面,但是不得覆蓋應用B的窗口。只依據主序進行排序是無法實現這個管理邏輯的,還需要依賴Activity的順序。在WindowToken一節的講解中,曾經簡單分析了mAppTokens列表的性質,它所保存的AppWindowToken的順序與AMS中ActivityRecord的順序時刻保持一致。因此,AppWindowToken在mAppTokens的順序就是Activity的順序。
**非應用窗口的排序規則**:依照主序進行排序,主序高者排在前面,當現有窗口的主序與新窗口相同時,新窗口位于現有窗口的前面。
**應用窗口的排序規則**:如上所述,同一個應用的窗口的顯示位置必須相鄰。如果當前應用已有窗口在顯示(當前應用的窗口存儲在其WindowState.appWindowToken.windows中),新窗口將插入到其所屬應用其他窗口的前面,但是保證STARTING\_WINDOW永遠位于最前方,BASE\_APPLICATION永遠位于最后方。如果新窗口是當前應用的第一個窗口,則參照其他應用的窗口順序,將新窗口插入到位于前面的最后一個應用的最后一個窗口的后方,或者位于后面的第一個應用的最前一個窗口的前方。如果當前沒有其他應用的窗口可以參照,則直接根據主序將新窗口插入到列表中。
窗口排序的總結如下:
- 子窗口依據子序相對于其父窗口進行排序。相同子序的窗體,正子序則越新越靠前,負子序則越新越靠后。
- 應用窗口參照本應用其他窗口或相鄰應用的窗口進行排序。如果沒有任何窗口可以參照,則根據主序進行排序。
- 非應用窗口根據主序進行排序。
經過addWindowToListInOrderLocked()函數的處理之后,當前DisplayContent的窗口列表被插入了一個新的窗口。然后等待assignLayersLocked()的進一步處理。
#### 2.assignLayersLocked分析
assignLayersLocked()函數將根據每個窗口的主序以及它們在窗口列表中的位置重新計算最終的顯示次序mLayer。
**WindowManagerService.java::WindowManagerService.assignLayersLocked()**
```
privatefinal void assignLayersLocked(WindowList windows) {
int N = windows.size();
int curBaseLayer = 0;
// curLayer表示當前分配到的Layer序號
int curLayer = 0;
int i;
// 遍歷列表中的所有的窗口,逐個分配顯示次序
for (i=0; i<N; i++) {
final WindowState w = windows.get(i);
final WindowStateAnimator winAnimator =w.mWinAnimator;
boolean layerChanged = false;
int oldLayer = w.mLayer;
if (w.mBaseLayer == curBaseLayer ||w.mIsImWindow
|| (i > 0 &&w.mIsWallpaper)) {
// 為具有相同主序的窗口在curLayer上增加一個偏移量,并將curLayer作為最終的顯示次序
curLayer +=WINDOW_LAYER_MULTIPLIER;
w.mLayer = curLayer;
} else {
// 此窗口擁有不同的主序,直接將主序作為其顯示次序并更新curLayer
curBaseLayer = curLayer =w.mBaseLayer;
w.mLayer = curLayer;
}
// 如果現實次序發生了變化則進行標記
if (w.mLayer != oldLayer) {
layerChanged = true;
anyLayerChanged = true;
}
......
}
......
// 向當前DisplayContent的監聽者通知顯示次序的更新
if (anyLayerChanged) {
scheduleNotifyWindowLayersChangedIfNeededLocked(
getDefaultDisplayContentLocked());
}
}
```
assignLayersLocked()的工作原理比較繞,簡單來說,如果某個窗口在整個列表中擁有唯一的主序,則該主序就是其最終的顯示次序。如果若干個窗口擁有相同的主序(注意經過addWindowToListInOrderLocked()函數的處理后,擁有相同主序的窗口都是相鄰的),則第i個相同主序的窗口的顯示次序為在主序的基礎上增加i \* WINDOW\_LAYER\_MULTIPLIER的偏移。
經過assignLayersLocked()之后,一個擁有9個窗口的系統的現實次序的信息如表4-3所示。
:-: 表4- 3 窗口最終的顯示次序信息
| | 窗口1 | 窗口2 | 窗口3 | 窗口4 | 窗口5 | 窗口6 | 窗口7 | 窗口8 | 窗口9 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 主序mBaseLayer | 11000 | 11000 | 21000 | 21000 | 21000 | 21000 | 71000 | 71000 | 101000 |
| 子序mSubLayer | 0 | 0 | 0 | -1 | 0 | 0 | 0 | 0 | 0 |
| 顯示次序mLayer | 11000 |11005 | 21000 | 21005 | 21010 | 21015 | 71000 | 71005 | 101000 |
在確定了最終的顯示次序mLayer后,又計算了WindowStateAnimator另一個屬性:mAnimLayer。如下所示:
**WindowManagerService.java::assignLayersLocked()**
```
finalWindowStateAnimator winAnimator = w.mWinAnimator;
......
if (w.mTargetAppToken != null) {
// 輸入目標為Activity的輸入法窗口,其mTargetAppToken是其輸入目標所屬的AppToken
winAnimator.mAnimLayer =
w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment;
} elseif (w.mAppToken != null) {
// 屬于一個Activity的窗口
winAnimator.mAnimLayer =
w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment;
} else {
winAnimator.mAnimLayer = w.mLayer;
}
......
```
對于絕大多數窗口而言,其對應的WindowStateAnimator的mAnimLayer就是mLayer。而當窗口附屬為一個Activity時,mAnimLayer會加入一個來自AppWindowAnimator的矯正:animLayerAdjustment。
WindowStateAnimator和AppWindowAnimator是動畫系統中的兩員大將,它們負責渲染窗口動畫以及最終的Surface顯示次序的修改。回顧一下4.1.2中的WMS的組成結構圖,WindowState屬于窗口管理體系的類,因此其所保存的mLayer的意義偏向于窗口管理。WindowStateAnimator/AppWindowAnimator則是動畫體系的類,其mAnimLayer的意義偏向于動畫,而且由于動畫系統維護著窗口的Surface,因此**mAnimLayer是Surface的實際顯示次序**。
在沒有動畫的情況下,mAnimLayer與mLayer是相等的,而當窗口附屬為一個Activity時,則會根據AppTokenAnimator的需要適當地增加一個矯正值。這個矯正值來自AppTokenAnimator所使用的Animation。當Animation要求動畫對象的ZOrder必須位于其他對象之上時(Animation.getZAdjustment()的返回值為Animation.ZORDER\_TOP),這個矯正是一個正數WindowManagerService.TYPE\_LAYER\_OFFSET(1000),這個矯正值很大,于是窗口在動畫過程中會顯示在其他同主序的窗口之上。相反,如果要求ZOrder必須位于其他對象之下時,矯正為-WindowManagerService.TYPE\_LAYER\_OFFSET(-1000),于是窗口會顯示在其他同主序的窗口之下。在動畫完結后,mAnimLayer會被重新賦值為WindowState.mLayer,使得窗口回到其應有的位置。
動畫系統的工作原理將在4.5節詳細探討。
**注意** 矯正值為常數1000,也就出現一個隱藏的bug:當同主序的窗口的數量大于200時,APPLICATION窗口的mLayer值可能超過22000。此時,在對于mLayer值為21000的窗口應用矯正后,仍然無法保證動畫窗口位于同主序的窗口之上。不過超過200個應用窗口的情況非常少見,而且僅在動畫過程中才會出現bug,所以google貌似也懶得解決這個問題。
- 前言
- 推薦序
- 第1章 開發環境部署
- 1.1獲取Android源代碼
- 1.2Android的編譯
- 1.3在IDE中導入Android源代碼
- 1.3.1將Android源代碼導入Eclipse
- 1.3.2將Android源代碼導入SourceInsight
- 1.4調試Android源代碼
- 1.4.1使用Eclipse調試Android Java源代碼
- 1.4.2使用gdb調試Android C/C 源代碼
- 1.5本章小結
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java層中的Binder分析
- 2.2.1Binder架構總覽
- 2.2.2初始化Java層Binder框架
- 2.2.3窺一斑,可見全豹乎
- 2.2.4理解AIDL
- 2.2.5Java層Binder架構總結
- 2.3心系兩界的MessageQueue
- 2.3.1MessageQueue的創建
- 2.3.2提取消息
- 2.3.3nativePollOnce函數分析
- 2.3.4MessageQueue總結
- 2.4本章小結
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量鍵的處理流程
- 3.2.2通用的音量設置函數setStreamVolume()
- 3.2.3靜音控制
- 3.2.4音量控制小結
- 3.3音頻外設的管理
- 3.3.1 WiredAccessoryObserver 設備狀態的監控
- 3.3.2AudioService的外設狀態管理
- 3.3.3音頻外設管理小結
- 3.4AudioFocus機制的實現
- 3.4.1AudioFocus簡單的例子
- 3.4.2AudioFocus實現原理簡介
- 3.4.3申請AudioFocus
- 3.4.4釋放AudioFocus
- 3.4.5AudioFocus小結
- 3.5AudioService的其他功能
- 3.6本章小結
- 第4章 深入理解WindowManager-Service
- 4.1初識WindowManagerService
- 4.1.1一個從命令行啟動的動畫窗口
- 4.1.2WMS的構成
- 4.1.3初識WMS的小結
- 4.2WMS的窗口管理結構
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的顯示次序
- 4.3.1主序、子序和窗口類型
- 4.3.2通過主序與子序確定窗口的次序
- 4.3.3更新顯示次序到Surface
- 4.3.4關于顯示次序的小結
- 4.4窗口的布局
- 4.4.1從relayoutWindow()開始
- 4.4.2布局操作的外圍代碼分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期處理
- 4.4.5布局DisplayContent
- 4.4.6布局的階段
- 4.5WMS的動畫系統
- 4.5.1Android動畫原理簡介
- 4.5.2WMS的動畫系統框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口動畫
- 4.5.5交替運行的布局系統與動畫系統
- 4.5.6動畫系統總結
- 4.6本章小結
- 第5章 深入理解Android輸入系統
- 5.1初識Android輸入系統
- 5.1.1getevent與sendevent工具
- 5.1.2Android輸入系統簡介
- 5.1.3IMS的構成
- 5.2原始事件的讀取與加工
- 5.2.1基礎知識:INotify與Epoll
- 5.2.2 InputReader的總體流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的讀取與加工總結
- 5.3輸入事件的派發
- 5.3.1通用事件派發流程
- 5.3.2按鍵事件的派發
- 5.3.3DispatcherPolicy與InputFilter
- 5.3.4輸入事件的派發總結
- 5.4輸入事件的發送、接收與反饋
- 5.4.1深入理解InputChannel
- 5.4.2連接InputDispatcher和窗口
- 5.4.3事件的發送
- 5.4.4事件的接收
- 5.4.5事件的反饋與發送循環
- 5.4.6輸入事件的發送、接收與反饋總結
- 5.5關于輸入系統的其他重要話題
- 5.5.1輸入事件ANR的產生
- 5.5.2 焦點窗口的確定
- 5.5.3以軟件方式模擬用戶操作
- 5.6本章小結
- 第6章 深入理解控件系統
- 6.1 初識Android的控件系統
- 6.1.1 另一種創建窗口的方法
- 6.1.2 控件系統的組成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的創建與體系結構
- 6.2.2 通過WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 刪除窗口
- 6.2.5 WindowManager的總結
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的創建及其重要的成員
- 6.3.2 控件系統的心跳:performTraversals()
- 6.3.3 ViewRootImpl總結
- 6.4 深入理解控件樹的繪制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()與臟區域
- 6.4.3 開始繪制
- 6.4.4 軟件繪制的原理
- 6.4.5 硬件加速繪制的原理
- 6.4.6 使用繪圖緩存
- 6.4.7 控件動畫
- 6.4.8 繪制控件樹的總結
- 6.5 深入理解輸入事件的派發
- 6.5.1 觸摸模式
- 6.5.2 控件焦點
- 6.5.3 輸入事件派發的綜述
- 6.5.4 按鍵事件的派發
- 6.5.5 觸摸事件的派發
- 6.5.6 輸入事件派發的總結
- 6.6 Activity與控件系統
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的創建與顯示
- 6.7 本章小結
- 第7章 深入理解SystemUI
- 7.1 初識SystemUI
- 7.1.1 SystemUIService的啟動
- 7.1.2 狀態欄與導航欄的創建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的體系結構
- 7.2 深入理解狀態欄
- 7.2.1 狀態欄窗口的創建與控件樹結構
- 7.2.2 通知信息的管理與顯示
- 7.2.3 系統狀態圖標區的管理與顯示
- 7.2.4 狀態欄總結
- 7.3 深入理解導航欄
- 7.3.1 導航欄的創建
- 7.3.2 虛擬按鍵的工作原理
- 7.3.3 SearchPanel
- 7.3.4 關于導航欄的其他話題
- 7.3.5 導航欄總結
- 7.4 禁用狀態欄與導航欄的功能
- 7.4.1 如何禁用狀態欄與導航欄的功能
- 7.4.2 StatusBarManagerService對禁用標記的維護
- 7.4.3 狀態欄與導航欄對禁用標記的響應
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系統中的漫游過程
- 7.5.2 SystemUIVisibility發揮作用
- 7.5.3 SystemUIVisibility總結
- 7.6 本章小結
- 第8章 深入理解Android壁紙
- 8.1 初識Android壁紙
- 8.2深入理解動態壁紙
- 8.2.1啟動動態壁紙的方法
- 8.2.2壁紙服務的啟動原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁紙的銷毀
- 8.2.5 理解Engine的回調
- 8.3 深入理解靜態壁紙-ImageWallpaper
- 8.3.1 獲取用作靜態壁紙的位圖
- 8.3.2 靜態壁紙位圖的設置
- 8.3.3 連接靜態壁紙的設置與獲取-WallpaperObserver
- 8.4 WMS對壁紙窗口的特殊處理
- 8.4.1 壁紙窗口Z序的確定
- 8.4.2 壁紙窗口的可見性
- 8.4.3 壁紙窗口的動畫
- 8.4.4 壁紙窗口總結
- 8.5 本章小結