#### 1. 狀態欄窗口的創建
在7.1.2節所引用的BaseStatusBar.start()方法的代碼中調用了createAndAddWindows()方法進行狀態欄窗口的創建。很顯然,createAndAddWindow()由PhoneStatusBar或TabletStatusBar實現。以PhoneStatusBar為例,參考其代碼:
**PhoneStatusBar.java-->PhoneStatusBar.createAndAddWindow()**
```
public void createAndAddWindows() {
addStatusBarWindow(); // 直接調用addStatusBarWindow()方法
}
```
在addStatusBarWindow()方法中,PhoneStatusBar將會構建狀態欄的控件樹并通過WindowManager的接口為其創建窗口。
**PhoneStatusBar.java-->PhoneStatusBar.addStatusBarWindow()**
```
private void addStatusBarWindow() {
// **① 通過getStatusBarHeight()方法獲取狀態欄的高度**
finalint height = getStatusBarHeight();
// **② 為狀態欄創建WindowManager.LayoutParams**
finalWindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, // 狀態欄的寬度為充滿整個屏幕寬度
height, // 高度來自于getStatusBarHeight()方法
WindowManager.LayoutParams.TYPE_STATUS_BAR, // 窗口類型
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 狀態欄不接受按鍵事件
/* FLAG_TOUCHABLE_WHEN_WAKING這一標記將使得狀態欄接受導致設備喚醒的觸摸
事件。通常這一事件會在interceptMotionBeforeQueueing()的過程中被用于
喚醒設備(或從變暗狀態下恢復),而InputDispatcher會阻止這一事件發送給
窗口。*/
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
// FLAG_SPLIT_TOUCH允許狀態欄支持觸摸事件序列的拆分
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT); // 狀態欄的Surface像素格式為支持透明度
// 啟用硬件加速
lp.flags|= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
//StatusBar的gravity是LEFT和FILL_HORIZONTAL
lp.gravity = getStatusBarGravity();
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
// **③ 創建狀態欄的控件樹**
makeStatusBarView();
// **④ 通過WindowManager.addView()創建狀態欄的窗口**
mWindowManager.addView(mStatusBarWindow, lp);
}
```
此方法提供了很多重要的信息。
首先是狀態欄的高度,由getStatusBarHeight()從資源com.android.internal.R.dimen.status\_bar\_height中獲得。這一資源定義在frameworks\\base\\core\\res\\res\\values\\dimens.xml中,默認為25dip。此資源同樣在PhoneWindowManager中被用來計算作為布局準繩的八個矩形。
然后是狀態欄窗口的LayoutParams的創建。LayoutParams描述了狀態欄是怎樣的一個窗口。TYPE\_STATUS\_BAR使得PhoneWindowManager為狀態欄的窗口分配了較大的layer值,使其可以顯示在其他應用窗口之上。FLAG\_NOT\_FOCUSABLE、FLAG\_TOUCHABLE\_WHEN\_WAKING和FLAG\_SPLIT\_TOUCH則定義了狀態欄對輸入事件的響應行為。
注意 通過創建窗口所使用的LayoutParams來推斷一個窗口的行為十分重要。在分析一個需要創建窗口的模塊的工作原理時,從窗口創建過程往往是一個不錯的切入點。
另外需要知道的是,窗口創建之后,其LayoutParams是會發生變化的。以狀態欄為例,創建窗口時其高度為25dip,flags描述其不可接收按鍵事件。不過當用戶按下狀態欄導致卷簾下拉時,PhoneStatusBar會通過WindowManager.updateViewLayout()方法修改窗口的LayoutParams的高度為MATCH\_PARENT,即充滿整個屏幕以使得卷簾可以滿屏顯示,并且移除FLAG\_NOT\_FOCUSABLE,使得PhoneStatusBar可以通過監聽BACK鍵以收回卷簾。
在makeStatusBarView()完成控件樹的創建之后,WindowManager.addView()將根據控件樹創建出狀態欄的窗口。顯而易見,狀態欄控件樹的根控件被保存在mStatusBarWindow成員中。
createStatusBarView()負責從R.layout.super\_status\_bar所描述的布局中實例化出一棵控件樹。并從這個控件樹中取出一些比較重要的控件并保存在對應的成員變量中。因此從R.layout.super\_status\_bar入手可以很容易地得知狀態欄的控件樹的結構:
#### 2.狀態欄控件樹的結構
參考SystemUI下super\_status\_bar.xml所描述的布局內容,可以看到其根控件是一個名為StatusBarWindowView的控件,它繼承自FrameLayout。在其下的兩個直接子控件如下:
- @layout/status\_bar所描述的布局。這是用戶平時所見的狀態欄。
- PenelHolder:這個繼承自FrameLayout的控件是狀態欄的卷簾。在其下的兩個直接子控件@layout/status\_bar\_expanded以及@layout/quick\_settings分別對應于卷簾之中的通知列表面板以及快速設定面板。
在正常情況下,StatusBarWindowView中只有@layout/status\_bar所描述的布局是可見的,并且狀態欄窗口為com.android.internal.R.dimen.status\_bar\_height所定義的高度。當StatusBarWindowView截獲了ACTION\_DOWN的觸摸事件后,會修改窗口的高度為MATCH\_PARENT,然后將PenelHolder設為可見并跟隨用戶的觸摸軌跡,由此實現了卷簾的下拉效果。
說明 PenelHolder集成自FrameLayout。那么它如何做到在@layout/status\_bar\_expanded以及@layout/quick\_settings兩個控件之間進行切換顯示呢?答案就在第6章所介紹的ViewGroup. getChildDrawingOrder()方法中。此方法的返回值影響了子控件的繪制順序,同時也影響了控件接收觸摸事件的優先級。當PenelHolder希望顯示@layout/status\_bar\_expanded面版時,它在此方法中將此面版的繪制順序放在最后,使其在繪制時能夠覆蓋@layout/quick\_settings,并且優先接受觸摸事件。反之則將@layout/quick\_settings的繪制順序放在最后即可。
因此狀態欄控件樹的第一層結構如圖7-2所示。
:-: 
圖 7 - 2狀態欄控件樹的結構1
再看status\_bar.xml所描述的布局內容,其根控件是一個繼承自FrameLayout的名為StatusBarView類型的控件,makeStatusBarView()方法會將其保存為mStatusBarView。其直接子控件有三個:
- @id/notification\_lights\_out,一個ImageView,并且一般情況下它是不可見的。在SystemUIVisiblity中有一個名為SYSTEM\_UI\_FLAG\_LOW\_PROFILE的標記。當一個應用程序希望讓用戶的注意力更多地集中在它所顯示的內容時,可以在其SystemUIVisibility中添加這一標記。SYSTEM\_UI\_FLAG\_LOW\_PROFILE會使得狀態欄與導航欄進入低辨識度模式。低辨識度模式下的狀態欄將不會顯示任何信息,只是在黑色背景中顯示一個灰色圓點而已。而這一個黑色圓點即是這里的id/notification\_lights\_out。
- @id/status\_bar\_contents,一個LinearLayout,狀態欄上各種信息的顯示場所。
- @id/ticker,一個LinearLayout,其中包含了一個ImageSwitcher和一個TickerView。在正常情況下@id/ticker是不可見的。當一個新的通知到來時(例如一條新的短信),狀態欄上會以動畫方式逐行顯示通知的內容,使得用戶可以在無需下拉卷簾的情況下了解新通知的內容。這一功能在狀態欄中被稱之為Ticker。而@id/ticker則是完成Ticker功能的場所。makeStatusBarView()會將@id/ticker保存為mTickerView。
至此,狀態欄控件樹的結構可以擴充為圖7-3所示。
:-: 
圖 7 - 3狀態欄控件樹的結構2
再來分析@id/status\_bar\_contents所包含的內容。如前文所述,狀態欄所顯示的信息共有5種,因此@id/status\_bar\_contents中的子控件分別用來顯示這5種信息。其中通知信息顯示在@id/notification\_icon\_area里,而其他四種信息則顯示在@id/system\_icon\_area之中。
- @id/notification\_icon\_area,一個LinearLayout。包含了兩個子控件分別是類型為StatusBarIconView的@id/moreIcon以及一個類型為IconMerger的@id/notificationIcons。IconMerger繼承自LinearLayout。通知信息的圖標都會以一個StatusBarIconView的形式存儲在IconMerger之中。而IconMeger和LinearLayout的區別在于,如果它在onLayout()的過程中發現會其內部所容納的StatusBarIconView的總寬度超過了它自身的寬度,則會設置@id/moreIcon為可見,使得用戶得知有部分通知圖標因為顯示空間不夠而被隱藏。makeStausBarView()會將@id/notificationIcons保存為成員變量mNotificationIcons。因此當新的通知到來時,只要將一個StatusBarIconView放置到mNotificationIcons即可顯示此通知的圖標了。
- @id/system\_icon\_area,也是一個LinearLayout。它容納了除通知信息的圖標以外的四種信息的顯示。在其中有負責顯示時間信息的@id/clock,負責顯示電量信息的@id/battery,負責信號信息顯示的@id/signal\_cluster以及負責容納系統狀態區圖標的一個LinearLayout——@id/statusIcons。其中@id/statusIcons會被保存到成員變量mStatusIcons中,當需要顯示某一個系統狀態圖標時,將圖標放置到mStatusIcons中即可。
注意 @id/system\_icon\_area的寬度定義為WRAP\_CONTENT,而@id/notification\_icon\_area的weight被設置為1。在這種情況下,@id/system\_icon\_area將在狀態欄右側根據其所顯示的圖標個數調整其尺寸。而@id/notification\_icon\_area則會占用狀態欄左側的剩余空間。這說明了一個問題:系統圖標區將優先占用狀態欄的空間進行信息的顯示。這也是IconMerger類以及@id/moreIcon存在的原因。
于是可以將圖7-3擴展為圖7-4。
:-: 
圖 7 - 4狀態欄控件樹的結構3
另外,在@layout/status\_bar\_expanded之中有一個類型為NotificationRowLayout的控件@id/latestItems,并且會被makeStatusBarView()保存到mPile成員變量中。它位于下拉卷簾中,是通知信息列表的容器。
在分析控件樹結構的過程中發現了如下幾個重要的控件:
- mStatusBarWindow,整個狀態欄的根控件。它包含了兩棵子控件樹,分別是常態下的狀態欄以及下拉卷簾。
- mStatusBarView,常態下的狀態欄。它所包含的三棵子控件樹分別對應了狀態欄的三種工作狀態——低辨識度模式、Ticker以及常態。這三棵控件樹會隨著這三種工作狀態的切換交替顯示。
- mNotificationIcons,繼承自LinearLayout的IconMerger控件的實例,負責容納通知圖標。當mNotificationIcons的寬度不足以容納所有通知圖標時,會將@id/moreIcon設置為可見以告知用戶存在未顯示的通知圖標。
- mTickerView,實現了當新通知到來時的動畫效果,使得用戶可以在無需下拉卷簾的情況下了解新通知的內容。
- mStatusIcons,一個LinearLayout,它是系統狀態圖標區,負責容納系統狀態圖標。
- mPile,一個NotificationRowLayout,它作為通知列表的容器被保存在下拉卷簾中。因此當一個通知信息除了需要將其圖標添加到mNotificationIcons以外,還需要將其詳細信息(標題、描述等)添加到mPile中,使得用戶在下來卷簾中可以看到它。
對狀態欄控件樹的結構分析至此便告一段落了。接下來將從通知信息以及系統狀態圖標兩個方面介紹狀態欄的工作原理。希望讀者能夠理解本節所介紹的幾個重要控件所在的位置以及其基本功能,這將使得后續內容的學習更加輕松。
- 前言
- 推薦序
- 第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 本章小結