#### 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所示。
:-: ![](http://img.blog.csdn.net/20150814134219928?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 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所示。
:-: ![](http://img.blog.csdn.net/20150814134232090?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 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。
:-: ![](http://img.blog.csdn.net/20150814134243958?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 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 本章小结