#### 1.WindowToken的意義
為了搞清楚WindowToken的作用是什么,看一下其位于WindowToken.java中的定義。雖然它沒有定義任何函數,但其成員變量的意義卻很重要。
- WindowToken將屬于同一個應用組件的窗口組織在了一起。所謂的應用組件可以是Activity、InputMethod、Wallpaper以及Dream。在WMS對窗口的管理過程中,用WindowToken指代一個應用組件。例如在進行窗口ZOrder排序時,屬于同一個WindowToken的窗口會被安排在一起,而且在其中定義的一些屬性將會影響所有屬于此WindowToken的窗口。這些都表明了屬于同一個WindowToken的窗口之間的緊密聯系。
- WindowToken具有令牌的作用,是對應用組件的行為進行規范管理的一個手段。WindowToken由應用組件或其管理者負責向WMS聲明并持有。應用組件在需要新的窗口時,必須提供WindowToken以表明自己的身份,并且窗口的類型必須與所持有的WindowToken的類型一致。從上面的代碼可以看到,在創建系統類型的窗口時不需要提供一個有效的Token,WMS會隱式地為其聲明一個WindowToken,看起來誰都可以添加個系統級的窗口。難道Android為了內部使用方便而置安全于不顧嗎?非也,addWindow()函數一開始的mPolicy.checkAddPermission()的目的就是如此。它要求客戶端必須擁有SYSTEM\_ALERT\_WINDOW或INTERNAL\_SYSTEM\_WINDOW權限才能創建系統類型的窗口。
#### 2.向WMS聲明WindowToken
既然應用組件在創建一個窗口時必須指定一個有效的WindowToken才行,那么WindowToken究竟該如何聲明呢?
在SampleWindow應用中,使用wms.addWindowToken()函數聲明mToken作為它的令牌,所以在添加窗口時,通過設置lp.token為mToken向WMS進行出示,從而獲得WMS添加窗口的許可。這說明,只要是一個Binder對象(隨便一個),都可以作為Token向WMS進行聲明。**對于WMS的客戶端來說,Token僅僅是一個Binder對象而已**。
為了驗證這一點,來看一下addWindowToken的代碼,如下所示:
**WindowManagerService.java::WindowManagerService.addWindowToken()**
```
@Override
publicvoid addWindowToken(IBinder token, int type) {
// 需要聲明Token的調用者擁有MANAGE_APP_TOKENS的權限
if(!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addWindowToken()")) {
thrownew SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap){
......
// 注意其構造函數的參數與addWindow()中不同,最后一個參數為true,表明這個Token
// 是顯式申明的
wtoken= new WindowToken(this, token, type, true);
mTokenMap.put(token,wtoken);
......
}
}
```
使用addWindowToken()函數聲明Token,將會在WMS中創建一個WindowToken實例,并添加到mTokenMap中,鍵值為客戶端用于聲明Token的Binder實例。與addWindow()函數中隱式地創建WindowToken不同,這里的WindowToken被聲明為顯式的。隱式與顯式的區別在于,當隱式創建的WindowToken的最后一個窗口被移除后,此WindowToken會被一并從mTokenMap中移除。顯式創建的WindowToken只能通過removeWindowToken()顯式地移除。
addWindowToken()這個函數告訴我們,WindowToken其實有兩層含義:
- 對于顯示組件(客戶端)而言的Token,是任意一個Binder的實例,對顯示組件(客戶端)來說僅僅是一個創建窗口的令牌,沒有其他的含義。
- 對于WMS而言的WindowToken這是一個WindowToken類的實例,保存了對應于客戶端一側的Token(Binder實例),并以這個Token為鍵,存儲于mTokenMap中。客戶端一側的Token是否已被聲明,取決于其對應的WindowToken是否位于mTokenMap中。
* * * * *
**注意** 在一般情況下,稱顯示組件(客戶端)一側Binder的實例為Token,而稱WMS一側的WindowToken對象為WindowToken。但是為了敘述方便,在沒有歧義的前提下不會過分仔細地區分這兩個概念。
* * * * *
接下來,看一下各種顯示組件是如何聲明WindowToken的。
##### (1) Wallpaper和InputMethod的Token
Wallpaper的Token聲明在WallpaperManagerService中。參考以下代碼:
**WallpaperManagerService.java::WallpaperManagerService.bindWallpaperComponentLocked()**
```
BooleanbindWallpaperComponentLocked(......) {
......
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
......
mIWindowManager.addWindowToken(newConn.mToken,
WindowManager.LayoutParams.TYPE_WALLPAPER);
......
}
```
WallpaperManagerService是Wallpaper管理器,它負責維護系統已安裝的所有的Wallpaper并在它們之間進行切換,而這個函數的目的是準備顯示一個Wallpaper。newConn.mToken與SampleWindow例子一樣,是一個簡單的Binder對象。這個Token將在即將顯示出來的Wallpaper被連接時傳遞給它,之后Wallpaper即可通過這個Token向WMS申請創建繪制壁紙所需的窗口了。
>[info] **注意** :WallpaperManagerService向WMS聲明的Token類型為TYPE\_WALLPAPER,所以,Wallpaper僅能本分地創建TYPE\_WALLPAPER類型的窗口。
相應的,WallpaperManagerService會在detachWallpaperLocked()函數中取消對Token的聲明:
**WallpaperManagerService.java::WallpaperManagerService.detachWallpaperLocked()**
```
booleandetachWallpaperLocked(WallpaperData wallpaper){
......
mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
......
}
```
再此之后,如果這個被detach的Wallpaper想再要創建窗口便不再可能了。
WallpaperManagerService使用WindowToken對一個特定的Wallpaper做出了如下限制:
- Wallpaper只能創建TYPE\_WALLPAPER類型的窗口。
- Wallpaper顯示的生命周期由WallpaperManagerService牢牢地控制著。僅有當前的Wallpaper才能創建窗口并顯示內容。其他的Wallpaper由于沒有有效的Token,而無法創建窗口。
InputMethod的Token的來源與Wallpaper類似,其聲明位于InputMethodManagerService的startInputInnerLocked()函數中,取消聲明的位置在InputmethodManagerService的unbindCurrentMethodLocked()函數。InputMethodManagerService通過Token限制著每一個InputMethod的窗口類型以及顯示生命周期。
##### (2) Activity的Token
Activity的Token的使用方式與Wallpaper和InputMethod類似,但是其包含更多的內容。畢竟,對于Activity,無論是其組成還是操作都比Wallpaper以及InputMethod復雜得多。對此,WMS專為Activity實現了一個WindowToken的子類:AppWindowToken。
既然AppWindowToken是為Activity服務的,那么其聲明自然在ActivityManagerService中。具體位置為ActivityStack.startActivityLocked(),也就是啟動Activity的時候。相關代碼如下:
**ActivityStack.java::ActivityStack.startActivityLocked()**
```
private final void startActivityLocked(......) {
......
mService.mWindowManager.addAppToken(addPos,r.appToken, r.task.taskId,
r.info.screenOrientation, r.fullscreen);
......
}
```
startActivityLocked()向WMS聲明r.appToken作為此Activity的Token,這個Token是在ActivityRecord的構造函數中創建的。隨然后在realStartActivityLocked()中將此Token交付給即將啟動的Activity。
**ActivityStack.java::ActivityStack.realStartActivityLocked()**
```
final boolean realStartActivityLocked(......) {
......
app.thread.scheduleLaunchActivity(newIntent(r.intent), **r.appToken,**
System.identityHashCode(r), r.info,
newConfiguration(mService.mConfiguration),
r.compat, r.icicle, results, newIntents,!andResume,
mService.isNextTransitionForward(),profileFile, profileFd,
profileAutoStop);
......
}
```
啟動后的Activity即可使用此Token創建類型為TYPE\_APPLICATION的窗口了。
取消Token的聲明則位于ActivityStack.removeActivityFromHistoryLocked()函數中。
Activity的Token在客戶端是否和Wallpaper一樣,僅僅是一個基本的Binder實例呢?其實不然。看一下r.appToken的定義可以發現,這個Token的類型是IApplicationToken.Stub。其中定義了一系列和窗口相關的一些通知回調,它們是:
- windowsDrawn(),當窗口完成初次繪制后通知AMS。
- windowsVisible(),當窗口可見時通知AMS。
- windowsGone(),當窗口不可見時通知AMS。
- keyDispatchingTimeout(),窗口沒能按時完成輸入事件的處理。這個回調將會導致ANR。
- getKeyDispatchingTimeout(),從AMS處獲取界定ANR的時間。
AMS通過ActivityRecord表示一個Activity。而ActivityRecord的appToken在其構造函數中被創建,所以每個ActivityRecord擁有其各自的appToken。而WMS接受AMS對Token的聲明,并為appToken創建了唯一的一個AppWindowToken。因此,這個類型為IApplicationToken的Binder對象appToken粘結了AMS的ActivityRecord與WMS的AppWindowToken,只要給定一個ActivityRecord,都可以通過appToken在WMS中找到一個對應的AppWindowToken,從而使得AMS擁有了操縱Activity的窗口繪制的能力。例如,當AMS認為一個Activity需要被隱藏時,以Activity對應的ActivityRecord所擁有的appToken作為參數調用WMS的setAppVisibility()函數。此函數通過appToken找到其對應的AppWindowToken,然后將屬于這個Token的所有窗口隱藏。
* * * * *
**注意**: 每當AMS因為某些原因(如啟動/結束一個Activity,或將Task移到前臺或后臺)而調整ActivityRecord在mHistory中的順序時,都會調用WMS相關的接口移動AppWindowToken在mAppTokens中的順序,以保證兩者的順序一致。在后面講解窗口排序規則時會介紹到,AppWindowToken的順序對窗口的順序影響非常大。
* * * * *
- 前言
- 推薦序
- 第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 本章小結