SystemUI架构分析
- SystemUI架构分析
- 前言
- 1SystemUI介绍
- 1SystemUI摘要
- 2什么是SystemUI
- 2SystemUI的启动过程
- 3SystemUI的SERVICES
- 1音量控制
- 11音量控制简介
- 12音量控制SERVICE的初始化
- 13控制音量过程
- 2RingtonePlayer
- 3电源管理
- 4任务管理
- 5存储通知
- 6锁屏
- 7通知栏
- 4总结
前言
本文描述Android系统中一个核心应用SystemUI,详细赘述SystemUI中几大模块功能的实现过程。由于作者水平有限,如发现本文中错误的地方,欢迎指正。
1、SystemUI介绍
1.1、SystemUI摘要
在Android系统中SystemUI是以应用的形式运行在Android系统当中,即编译SystemUI模块会生产APK文件,源代码路径在frameworks/base/packages/SystemUI/,安装路径system/priv-app/-SystemUI。
1.2、什么是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
2、SystemUI的启动过程
正如1.2中所述,SystemUI任何内容都不像一个APP,自然它的启动也不像大多APP一样启动一个Activity。SystemUI顾名思义就是全局UI,必须在开机过程中完成启动,并不可退出。
回顾Android系统开机的过程,会创建server进程维护系统各种服务,当服务启动完成后调用systemReady方法(如果读者不了解这个过程自行学习Android Boot Flow),如下图:
继续跟踪startSystemUi()方法的实现如下图:
上图中可以看到通过熟悉的startServiceAsUser()方法启动SystemUI中的SystemUIService。由APP的启动过程可知(如果读者不了解这个过程可自行学习),Android系统会给应用创建独立的进程,并实例化Application对象,在SystemUI源码中的AndroidManifest.xml文件可以看到下图的配置:
上图中在application标签中指定了SystemUIApplication对象,因此在启动SystemUI应用时会创建SystemUIApplication对象并回调onCreate()方法,如下图:
onCreate()中注册ACTION_BOOT_COMPLETED广播,并调用mServices.onBootCompleted()方法,将在后面的内容中赘述该方法。应用成功启动后便可执行上文中提到的SystemUIService服务,并回调onCreate()方法,如下图:
上图中通过获取应用的Application实例SystemUIApplication对象,调用startServiceIfNeeded(),如下图:
上图中读者可能会问:mService的实质是什么?回顾1.2章节中的内容,SystemUI主要分几大模块,即在SystemUI中每个模块是一个Service,这样一来,各个模块就非常独立。上图中用Java映射机制把每个Service的对象实例化,并调用start()方法启动各个服务,start()方法的具体实现在分析各个服务时再赘述。先了解mService实质都包含哪些具体“对象”,如下图:
如上图所示,SystemUI需要启动的Service包括KeyguardViewMediator、Recent、VolumeUI、SystemBars、StorageNotification、PowerUI、RingtongPlayer共7个,每个service的具体实现和功能将在下文中描述。在图6中可以看到,实例化每个对象是向上转型为SystemUI对象,即图7中的所有service统一继承了SystemUI类,如下图:
SystemUI类提供start()、onConfigurationChanged()、dump()等重要方法,每个方法在各个service中实现不一样,下面将一一描述每个service在SystemUI类中的方法的实现。
至此,SystemUI的启动基本完成,从上文可知,SystemUI是系统中非常核心的应用,在Android系统开机过程中Server进程直接发起SystemUI启动,SystemUI也是固化程序,在保证系统正常运行发挥了非常重要的作用。
3、SystemUI的SERVICES
3.1、音量控制
3.1.1、音量控制简介
如图章节1.2中的VolumeUI所示,当用户操作音量键时,会弹出相应的UI显示,并可以设置音量大小和情景模式。VolumeUI的主要代码在SystemUI/volume下。在不同模式下,音量键触发的UI显示样式不一样,分别是通话、铃声(通知)、音乐、闹铃、蓝牙输出等,如下图 9-13
3.1.2、音量控制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的架构如下图:
3.1.3、控制音量过程
当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)中略有不同,读者感兴趣可以自行比较,本文不再论述。
3.2、RingtonePlayer
RingtonePlayer在SystemUI中扮演播放者的角色,代码在SystemUI/media/目录下,RingtonePlayer的代码很少,功能很简单,同VolumeUI,RingtonePlayer继承SystemUI类,SystemUI应用启动时调用start()方法:
Start()方法很简洁,通过AudioService的句柄mAudioService的setRingtonePlayer()方法设置mCallback,mCallback实质是IRingtonePlayer对象的实例,如下图:
IRingtonePlayer有play()、stop()等方法,分别是播放音乐和停止音乐。关于RingtonePlayer的作用,读者可以参考下图:
SystemUI启动后通过Binder机制把IRingtonePlayer的句柄设置到AudioServie,其它应用便可通过AudioService获取IRingtonePlayer的句柄,在通过Binder机制操作RingtonePlayerService去播放声音文件。如果读者感兴趣可自行深入学习Ringtone策略和架构。
3.3、电源管理
电源管理即PowerUI,负责监控电源的变化和通知,源码路径在SystemUI/power。同样PowerUI继承SystemUI类,start()方法如下:
上图可以知道,start()方法注册鉴定Setting.Global.LOW_POWER_MODE_TRIGGER_LEVEL的变化,接着调用mReceiver.init()方法,如下:
图 39注册一个action包含Intent.ACTION_BATTERY_CHANGED的广播接收器,监听电量的变化。然后调用updateSaverMode()方法:
如上图,updateSaverMode()的最终实现也是很简单,根据不同状态显示不同的notification。这就是PowerUI主要的功能,关于PowerUI本文就描述到这里,如果读者对PowerUI的更多细节感兴趣,可参考相关资料。
3.4、任务管理
任务管理即Recents,代码路径在SystemUI/recents。Recents是手机设备基本和常用的功能,主要功能表现为:展示所有任务,可以进行任务切换,可以进行任务的清除。同样Recents是SystemUI类的子类,同样启动时从start()方法开始启动。
Recents的start()方法很简洁,主要是sAlternateRecents.onStart()方法,sAlternateRecent是AlternateRecentsComponent的实例,AlternateRecentsComponent作为一个组件服务,担负着管理Recents的变化过程,AlternateRecentsComponent是单例设计模式,但一般sAlternateRecent只创建一次,过程如下:
图 42通过getRecentsComponent()方法new一个AlternateRecentsComponent的对象实例,forceInitialize值是false,AlternateRecentsComponent实例化是会执行图43中的RecentsTaskLoader.initialize()方法实例化RecentsTaskLoader,RecentsTaskLoader是Recents读取器。接着又new一个SystemServicesProxy的实例,mSystemServicesProxy具有ActivityManage和PackageManage的功能,在读取Recents发挥了很重要的作用。回头看RecentsTaskLoader.initialize()的方法:
在RecentsTaskLoader.initialize()的方法中,除了实例化RecentsTaskLoader自己的同时,在RecentsTaskLoader的构造函数中同时实例化比较重要的两个变量,一个是TaskResourceLoadQueue,Recents资源队列,另外一个是TaskResourceLoader,Recents的资源读取器。下面根据SystemUI(Recents)的启动过程了解这些类的作用。
在大多设备当中,通过长按HOME键打开任务管理器,HOME长按事件触发由输入输出事件派发者派发到系统进程(system_process)后被WindowManagerService拦截派发到PhoneWindowManager,如图:
上图中handleLongPressOnHome()将事件进一步发送到StatusBarManagerService,如图:
StatusBarManagerService通过CommandQueue发送到SystemUI进程,关于StatusBarManagerService和CommandQueue在StatusBar service中论述。最后发送到AlternateRecentsComponent,如图:
到图47,事件经过了几个进程,终于到准备启动任务管理器的界面了,topTask是ActivityManager.RunningTaskInfo的实例,即正在系统运行的Activity,第二参数值为true。RecentsActivity启动回调onCreate()方法:
onCreate()方法完成了实例化RecentsTaskLoader、SystemServicesProxy、RecentsConfiguration,在前面的内容已提到,RecentsTaskLoader.initialize()方法同时实例化TaskResourceLoader和TaskResourceLoadQueue,然后注册一个监听Intent.ACTION_SCREEN_OFF action的广播接收器,即当屏幕熄灭后关闭RecentsActivity。RecentsActivity还复写了onStart()方法:
上图AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true)通知StatusBar RecentsActivity已经起来,StatusBar需要配合做相应的调整,之后调用updateRecentsTasks(),由于篇幅问题,下文将重点描述Tasks的管理,不再跟随代码的执行流程。
RecentsTasks的管理过程主要包括读取task,读取task资源,显示在RecentsActivity上,清除task。主要包括下面这些类:
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.ActivityInfoHandle
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader. TaskResourceLoadQueue
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader. TaskResourceLoader
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.Options
RecentsTaskLoader是一个管理者,RecentsTaskLoader持有TaskResourceLoader,TaskResourceLoadQueue等实例,可以创建RecentsTaskLoadPlan实例,所以RecentsTaskLoader实质并没有去取读取task和task资源,只是负责发起读取task任务。ActivityInfoHandle 唯一持有一个AcitivityInfo的对象,保存Activity的信息,Activity的信息会在RecentsTaskLoadPlan中被查询。TaskResourceLoader 负责task的资源读取,和RecentsTaskLoader一样,它是一个管理者,实质实现读取task资源的是TaskResourceLoadQueue 。RecentsTaskLoadPlan对象实例也是被RecentsTaskLoader持有,实质上RecentsTaskLoadPlan是task读取的参与者和实现者,Options 是RecentsTaskLoadPlan的内部类,主要功能是控制RecentsTaskLoadPlan的读取task的条件。RecentsTaskLoadPlan读取task主要分两个阶段,第一个个阶段是
上图中loader是RecentsTaskLoader的实例,调用preloadTasks()方法,即task读取的第一阶段,plan是RecentsTaskLoadPlan的实例,第二个参数mConfig.launchedFromHome是boolean值,作用是isTopTaskHome,在启动RecentsActivity之前被赋值。这个阶段主要完成对task的读取。第二个阶段是:
上图中loadTasks()方法就是启动RecentsTaskLoadPlan的第二个阶段,同上图50,loader是RecentsTaskLoader的实例,第一个参数this是Context的实例,plan是RecentsTaskLoadPlan的实例,loadOpts是Options的实例,loadOpts在这里的作用是控制RecentsTaskLoadPlan读取task的规则,第二个阶段必须基于第一个阶段,也就是说在调用loadTasks()方法前,必须先执行preloadTasks()方法,loadTasks()基于preloadTasks()中读取到的task,从task中读取task的资源。
这两个阶段紧密连连,有先有后,完成不同的功能,下文将具体描述这两个阶段的过程。Loader. preloadTasks()实质调用RecentsTaskLoadPlan的如下方法
在这里会实例化一个TaskStack的对象实例,TaskStack封装了包含List类型的类,TaskStack包含两种类型的List,一种是存储所有的task,一种是filter task list,同时提供一个TaskFilter工具类接口,过滤主要通过PakcageName作为匹配。然后调用preloadRawTasks()方法实例化mRawTasks,mRawTasks是一个ListActivityManager.RecentTaskInfo>实例,mRawTasks存储了RecentTaskInfo类型的实例,所以mRawTasks是读取task的关键,看mRawTasks的实例化过程
上图中mSystemServicesProxy是SystemServicesProxy的实例,调用图中的方法,再往下看
上图中mAm是ActivityManager的实例,getRecentTasksForUser()方法实质是通过binder调用了远程的ActivityManagerService的方法,关于getRecentTasksForUser()这个方法在ActivityManagerService中的实现,读者可以自行阅读。在这里得到一个ListActivityManager.RecentTaskInfo>的对象实例tasks,RecentTaskInfo实质包含什么数据呢?如图
上图中是RecentTaskInfo的变量,得到这些数据,回到前面图52的地方,看下图
上图中获取到RecentTaskInfo后,把信息重新打包封装到TaskKey中,然后读取Activity的名字,Acitivity的图标,和thumbnail,然后把数据保存到TaskStack中。到此,第一个阶段就完成了,在这个阶段完成了读取task的基本信息RecentTaskInfo,然后读取Activity的基本信息ActivityInfo,把数据保存到TaskStack中。
回到图50,第一阶段完成后,接着就行图51中的第二阶段loader.loadTasks(),如下图
上图可以看到,这里获取icon和thumbnail都是在第一个阶段就已经完成了动作,在这里在根据inRunningTask等条件再次刷新数据罢了。
到这里task和task资源读取完成,上上文中提到TaskResourceLoadQueue和TaskResourceLoader,这两个类在RecentsTaskLoadPlan的两个阶段都有使用,用于异步读取Activity Icon,TaskResourceLoadQueue会管理这些任务。
所有的Task数据到这里已准备就绪,接下下就是把数据显示在Activity上(屏幕上),对于数据怎样绑定到View,本文不再论述。
3.5、存储通知
存储通知即StorageNotification service,在SystemUI中主要完成对存储器变化的通知,即USB连接的变化,存储器的变化SystemUI发送相应的Notification。StorageNotification的启动如下:
StorageNotification的启动很简单,创建一个StorageNotification-EventListener的实例,把StorageNotificationEventListener通过StorageManager注册到到MountService。当USB连接或存储器发生变化是远程回调到StorageNotificationEventListener的方法,如下图:
USB变化回调到onUsbMassStorageConnectionChangedAsync()进行处理,存储器发生变化回调到onStorageStateChangedAsync()处理,就是在发送notification通知USB和存储器的变化。这个模块本文就论述到这里,在SystemUI中StorageNotification结构清晰,功能简单,读者感兴趣可以自行了解。
3.6、锁屏
锁屏(Keyguard)service在SystemUI是一个比较特殊的模块,特殊在于SystemUI启动的service只是一个信息传递者,也就是KeyguardViewMediator,并没有做锁屏或解屏的实质操作。在这里,涉及到三个比较关键的类是:
/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
KeyguardViewMediator和KeyguardService在源码中位于同一个GIT库(SystemUI)中,而KeyguardUpdateMonitor则位于KeyGuard库中。在KeyguardViewMediator的初始化中主要做了三件事,如图
实例化KeyguardUpdateMonitor的实例mUpdateMonitor,KeyguardUpdateMonitor负责更新已经锁屏界面上的内容,如时间,当然,KeyguardUpdateMonitor只是一个信息传递着,实际去刷新界面的是StatusBar模块。Keyguard模块通知StatusBar刷新解密那通过KeyguardUpdateMonitorCallback这个类就行远程回调,该类的实例在StatusBar模块启动时通过KeyguardService获取到IKeyguardService的远端实例,通过IKeyguardService远程调用IKeyguardService的addStateMonitorCallback()方法实例化KeyguardUpdateMonitorCallback对象,如下图
SystemUI启动的Keyguard模块并没有真正的去操作锁屏界面,而是作为一个信息传递着把信息传递给StatusBar模块。这个模块本文就介绍到这里。
3.7、通知栏
通知栏(SystemBars)service是SystemUI中最重要的service,代码量最多,最复杂的,界面结构也复杂。根据前面的内容可知,启动SystemBars是通过调用start()方法,如下图:
这里实质是回调到到SystemBars的onNoService()方法(这里涉及到安全设置启动的模式不一样,本文只讨论正常模式),最后是调用SystemBars的createStatusBarFromConfig()方法
上图可以看到,从string资源文件里面读取class name,通过java的映射机制实例化对象,然后调用start()方法启动,class name的值如下图:
该配置文件在SystemUI/res/values/config.xml中。所以实质是PhoneStatusBar调用了start()方法。
SystemBars模块本文分两个阶段论述,第一阶段是SystemBars的初始化过程,第二阶段是Notification的显示过程。第一阶段主要涉及的类是:
SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
PhoneStatusBar的父类是BaseStatusBar继承于SystemUI,上文提到,SystemBars调用PhoneStatusBar中的start()方法,下面跟随代码看看这个过程。
如上图,调用父类中的start()方法,即BaseStatsuBar中的start()方法。然后调用addNavigationBar()方法实例化导航条,这里不再赘述此功能。继续看BaseStatsuBar中的方法。
如上图BaseStatsuBar中的start()方法,实例化一个StatusBarIconList的对象,此处iconList对象是“空值”,然后通过IStatusBarService的实例mBarService对象注册到StatusBarManager¬Service。mCommandQueue是CommandQueue的实例,在Status¬Bar-ManagerService的远程回调,实现StatusBarManagerService和SystemUI的通信。然后调用createAndAddWindows()方法,方法初始化status bar,notification,quick settings等的View控件。在这里,还需要注意NotificationListenerService的实例mNotificationListener的registerAsSystemService()方法,该方法主要实现StatusBarManagerService和SystemUI的notification的控制通道,也就是说,StatusBarManagerService收到notification变化时,通过此通道通知SystemUI显示notification的变化。下文将介绍notification从StatusBarManagerService到SystemUI的过程。
一个APP需要显示notification首先需要实例化一个NotificationManager的对象,然后调用NotificationManager的方法notify()方法把创建好的Notification对象作为参数传进去。
上图中可以看到一个service的对象调用了enqueueNotification-WithTag()方法,该方法实质是远程调用NotificationManagerService中的enqueueNotificationWithTag()方法,该方法如下:
这里会把NotificationManager传递过来的Notification对象进行很多处理,比如变换成NotificationRecord,实质就是把Notification缓存下来。在上图的这个过程,还有一些其它的处理逻辑,在这里就不详细说明了,读者可以自行了解。上图中在代码的末尾调用了buzzBeepBlinkLocked()方法,该方法主要处理Notification的声音和震动的逻辑,本文也不再详述。接着看mListeners调用了notifyPostedLocked()方法,此方法最终会执行到如下图的代码
首先留意上图中final INotificationListener listener = (INotificationListener)info.service;这句代码,info.service返回一个INotificationListener的实例对象,该对象在上文中图66中的mNotificationListener.registerAsSystemService()方法进行设置,所以listener.onNotificationPosted()方法实质是远程回调SystemUI中的方法,如下图
如上图,这里又调用了Notification.Builder.rebuild()方法,该方法主要把通过Binder机制传递过来的数据重新组装一些显示View所需要的数据,如notification的布局文件等等。重新组装notification数据后,调用NotificationListenerService.this.onNotificationPosted()方法,然后代码会执行到BaseStatusBar中的如下代码
上图中,代码运行又回到熟悉的BaseStatusBar.java类中,从APP调用NotificationManager的notify()方法到BaseStatusBar的addNotification()或updateNotification()方法,经历了一个复杂的过程,涉及的模块多,模块交互复杂。到此,本文就不再往下详情说明Notification到达SystemUI的处理过程了,读者感兴趣可自行阅读代码。
4、总结
本文详细描述了SystemUI中KeyguardViewMediator、Recents、VolumeUI、SystemBars、StorageNotification、PowerUI、RingtonePlayer 7个模块(SERVICE),其中SystemBars是SystemUI中起到中枢作用的一个模块,因为这个模块和其他模块交互最紧密,而且SystemUI中大多数UI的View都是在SystemBars中初始化和控制显示。不过,SystemUI的功能不止本文中说论述到的,SystemUI还有例如情景模式控制、流量警告和常用的屏幕截屏等功能,本文不再去说明它们,读者可自行去研究SystemUI中的每个功能。