SystemUI介绍
SystemUI摘要
在Android系统中SystemUI是以应用的形式运行在Android系统当中,即编译SystemUI模块会生产APK文件,源代码路径在frameworks/base/packages/SystemUI/,安装路径system/priv-app/-SystemUI。
什么是SystemUI
在前文1.1章节中可知,SystemUI是一个普通的APK文件,即是一个普通的APP,但是,手机使用者看见的所有SystemUI的内容都不像是一个APP,为什么?既然是一个应用,就是完成特定的功能,SystemUI主要完成的功能有:
- (1)Status bars
- (2)Navigation bars
- (3)Notification
- (4)Lockscreen
- (5)Quick settings
- (6)Overview(recent task switcher)
- (7)VolumeUI
SystemUI的SERVICES
音量控制
音量控制简介
如图章节1.2中的VolumeUI所示,当用户操作音量键时,会弹出相应的UI显示,并可以设置音量大小和情景模式。VolumeUI的主要代码在SystemUI/volume下。在不同模式下,音量键触发的UI显示样式不一样,分别是通话、铃声(通知)、音乐、闹铃、蓝牙输出等,如下图 9-13
音量控制SERVICE的初始化
在第二章节中SystemUI的启动过程提到,SystemUI的所有Service通过SystemUI类的start()方法启动,并且通过图7可以知道,volume service的VolumeUI继承了SystemUI类,所以start()实质是执行VolumeUI中的方法,如下图:
如上图中的代码,首先读取VolumeUI的开关,如果mEnabled为true,则调用initPanel()方法实例化UI等控件元素(如图15),实例化VolumeController控制器(如图17),调用putComponent()保存对象实例,调用updateController()设置控制器(如图18)。
上图中主要是new一个VolumePanel对象,VolumePanel是Handler的子类,且又是VolumeUI的Pannel,因此,VolumePanel负责绘制VolumeUI的内容和控制VolumeUI的显示。先看看VolumeUI的创建过程:
从上图中的代码可见,VolumeUI是以Dialog的形式显示UI,VolumePanel的实例化过程创建Dialog实例和初始化ZenModePanel,到此VolumePanel将会待命。上文中提到VolumePanel同时是Handler的子类,一旦VolumePanel收到相关的Message时,将会处理UI的显示和关闭。
上图是VolumeController的实现代码,主要实现对VolumeUI的Panel的控制,例如上图中的volumeChanged()控制Panel的显示和变化,dismissNow()控制Panel的关闭。那么VolumeController是被谁管控呢?如下图:
图中可以看到先通过设置Provider读取是否允许systemui控制volume,如果允许,则设置通过AudioManager的实例设置VolumeController到AudioService(读者如果不了解这个过程,可以自行阅读Android Audio策略)。至此,VolumeUI的初始化全部完成。 通过本节的学习,VolumeUI的架构如下图:
控制音量过程
当SystemUI的VolumeUI当前不是活动窗口时,一般情况下,音量的设置是通过音量键进行操作,当用户操作音量键时,如果用户不拦截音量键事件,那么默认音量键的事件将会在Window中被消化,Window将捕获到的音量键事件通过Binder机制将音量变化信息传送到MediaSessionService,MediaSessionService同样通过Binder机制接着传送到AudioService,最后AudioService也同样通过Binder机制把信息传送给SystemUI(VolumeUI),VolumeUI将会作出相应的变化。下面将详细了解这个过程: 当手机设备当前活动窗口在Laucher桌面,Laucher没有对音量键事件作拦截操作,音量键事件将会在PhoneWindow中被消化。在Android的单次点击事件中,分down和up两种事件,分别被分发到PhoneWindow的onKeyUp()和onKeyDown()方法中,如下图20-21:
图20是消化down事件,图21是消化up事件,但音量键还有上音量键(+)和下音量键(-),从这两张图可以看到,KEYCODE_VOLUME_UP没有作任何处理,上音量键(+)的事件会在下音量键中消化(-), 在down和up事件中都是调用sendAdjustVolumeBy()同一个方法,传递三个参数,第一个参数是指定音量类型,mVolumeControlStream为默认值,取值Integer.MIN_VALUE,图20和图21相同,第二个是delta,音量控制类型,即增加或减少,图20传递direction,图21传递0,direction取值1或-1,即1:增加、0:不变、-1:减少,第三个参数是flags,控制VolumeUI显示,每个参数具体的控制的实现代码将在下文中描述。继续跟踪流程,Laucher进程通过Binder机制把信息传送到MediaSessionService,如下图:
上图中有获取MediaSessionRecord的对象来控制音量,这里的session变量的值是null,如果读者对此感兴趣可自行阅读相关资料。继续看dispatchAjustVolumeLocked()方法:
图23中可以知道参数只是多了一个packageName,其它的都是图20或图21中的参数值。接着往下看:
在图20或图21中有描述suggestedStreamType的值是Integer.MIN_VALUE,在上图中通过getActiveStreamType()方法对值进行转换,变成streamType,它的取值可能是0到10,分别控制不同类型的音量,如3.1.1章节中所以,本例子streamType的值是2,即调整的是铃声(通知)的音量。接着又调用了adjustStreamVolume()方法,如下:

adjustStreamVolume()方法对direction和streamType的合法进行校验,direction取值-1到1,streamType取值0到10。之后通过mStreamState获取oldIndex、newIndex和index值,其中oldindex和index作为sendVolumeUpdate()方法的参数,将会影响音量变化的广播和AudioProfile,关于AudioProfile读者感兴趣可以自行学习。图中还可以看到这里还设置了HDMI接口输出。继续看sendVolumeUpdate()方法:
在sendVolumeUpdate()方法中处理了几个事件,一个postVolumeChanged()方法,最终通知SystemUI,后面赘述。接着发送注册到AudioManager.VOLUME_CHANGED_ACTION的action的广播,通知音量改变并携带音量大小的原值oldIndex和新值index,最后通知AudioProfile。继续看postVolumeChanged()方法:
这里通过mControlle对象调用volumeChanged(),mController实质是一个什么类的实例,回顾3.1.2章节中的图18,updateController()方法设置了VolumeController的实例,因此mController正是VolumeController在AudioService中的句柄,通过Binder机制,把音量变化的信息从AudioService传输到SystemUI进程。转移到SystemUI,如下图:
从AudioService回调到volumeChanged()方法,接着调用mPanel的postVolumeChanged()方法,mPanel在前文3.1.2章节的图15中有描述,是VolumePanel的实例,前文中提到,VolumePanel是Handler的子类,也是VolumeUI的Panel,下面结合代码分析VolumePanel的具体功能:
上文提到VolumePanel是Handler的子类,图29中VolumePanel将发送MSG_VOLUME_CHANGED的Message到自身持有的线程,接着看MSG_VOLUME_CHANGED的代码:
上图中直接又调用了onShowVolumeChanged()方法,顾名思义是显示音量变化的UI,后面接着继续调用resetTimeout()方法,先跟踪onShowVolumeChanged()方法:
上图中首先调用getStreamVolume()方法获取对应streamType当前的音量值,通过StreamControl匹配streamType的UI,StreamControl是一个容器,在VolumeUI的初始化时被实例化,装载不同streamType的UI配置,并保存到mStreamControls数组对象中,因此streamType的值确定了StreamControl的类型,StreamControl确定了Dialog显示的UI类型。确定StreamControl的类型后,调用updateSliderProgress()方法更新界面控件,最后调用mDialog.show()方法绘制界面。至此,从按下音量键到调用mDialog.show(),设备对点击事件作出相应,并显示相应的UI到界面上。 回顾图31,resetTimeout()方法的实现如下:
这里会延时发送一个空消息到VolumePanel,what为MSG_TIMEOUT,mTimeoutDelay的值为3000,跟踪MSG_TIMEOUT的处理过程:
很简单,实现的功能就是在VolumeUI显示后,延时3秒自动把VolumeUI关闭。 到此SystemUI的VolumeUI service分析完毕,VolumeUI的流程简单清晰,代码简洁,阅读方便。回顾VolumeUI整个控制流程,可用下图总结:
上图中PhoneWindow是当前活动窗口的进程的PhoneWindow实例捕获音量键事件,当前活动窗口的可以通过自身的PhoneWindow对象实例设置streamType,即控制音量键事件触发的VolumeUI类型。更多车机开发学习资料;可参考《车载技术开发手册》里面记录了大部分车机进阶文档。需要可以点击查看。
结尾
当应用程序拦截音量键事件,那么PhoneWindow将无法捕获到音量键事件,此时音量键事件将不遵行上图的流程。如果当前活动窗口时SystemUI,则直接由SystemUI所在进程的活动窗口的PhoneWindow对象实例获取到音量键事件,这时音量键事件的转发和处理和在其它进程(如Laucher)中略有不同。
网友评论