1.为什么需要Systemui?
Systemui就是状态栏,状态栏可以说是操作系统上不可或缺的一部分,例如:Windows系统底部的状态栏,Ubuntu系统顶部的状态栏。同样,Android作为一个操作系统,状态栏也是其系统的一个重要组成部分。既然各个系统上都配备了状态栏,那么它究竟有何作用呢?其实,状态栏的作用可以分为三大块:
1)导航功能。针对桌面系统来说,无论用户当前处于任何界面,都可以通过状态栏导航到其它应用;而对与Android系统,只能通过状态栏跳转到Settings及Notification所指定的Activity。
2)状态的显示。例如:蓝牙的连接状态,音量的状态等。
3)通知的显示。例如:短信,邮件等通知信息的显示。另外,还有很多厂商在状态栏中定制了快速开关,如蓝牙,wifi的打开和关闭。
2.Systemui的创建
由于Systemui需要显示在应用的上层,所以需要通过WindowManager.addView()将Systemui所对应的view添加到界面;其次,Systemui需要常驻内存,所以需要通过Service来控制。
从上面的时序图可以看到,Systemui的创建是由SystemServer发起的。SystemServer中显示的启动了SystemUIService。接下来我们来看一下SystemUIService中都有什么。
finalObject[] SERVICES = newObject[] {
0, //system bar or status bar, filled in below.
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
};
1)首先,其中定义了一个Object数组,用来存放接下来要启动的对象。其中 PowerUI和 RingtonePlayer都继承了SystemUI这个抽象类,并实现了其中的抽象方法start()。其实PowerUI就是当设备电量不足时弹出的界面,而RingtonePlayer则是通过音量键条件音量时弹出的音量条件进度条。它们两个的实现都比较简单,就是在收到对应广播的时候显示View。
2)其中还有一个SERVICES[0],这个才是重点,在SystemUIService的onCreate()方法中有如下代码。
SERVICES[0] =wm.hasSystemNavBar()
? R.string.config_systemBarComponent
: R.string.config_statusBarComponent
它会根据设备是否具有导航栏来判断当前设备是平板还是手机,并将对应的SystemUI子类赋值给SERVICES[0]。
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>
<string name="config_systemBarComponent" translatable="false">com.android.systemui.statusbar.tablet.TabletStatusBar</string>
这里我们使用PhoneStatusBar,接下来执行的就是PhoneStatusBar的start()方法。
@Override
public voidstart() {
//TODO: getthe boolean value from FW API not from system properties
mCurrentFontScale =getSystemFontScale();
mDisplay =((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
mWindowManager =IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
super.start();// calls createAndAddWindows()
addNavigationBar();
if(ENABLE_INTRUDERS) addIntruderView();
// Lastly, call to theicon policy to install/update all the icons.
mIconPolicy = newPhoneStatusBarPolicy(mContext);
}
看到上面的方法,是不是惊呆了?就这几行代码,SystemUI就被创建出来了?怎么连布局文件都没看到呢?不要慌,接下来我们才要进入主题。
super.start();// calls createAndAddWindows()
看到这行注释了吧,创建并添加Windows,是调用了父类的start()方法,不用说,找父类去。
接下来我们转到BaseStatusBar的start()方法中,代码老多了,不过一半的代码都是变量的初始化操作,真正需要我们关心的代码其实也就两行。
mBarService.registerStatusBar(mCommandQueue,iconList, notificationKeys, notifications,switches, binders);
(这里我们只讲Systemui的创建,上面那段是与状态图标显示相关的,我们一会儿再讲)
createAndAddWindows();
与Systemui创建相关的就是createAndAddWindows();但BaseStatusBar中并没有该方法,而是调用了其子类PhoneStatusBar的createAndAddWindows();细心的同学就有疑问了,为什么我们不在这些操作都放到PhoneStatusBar中,而非要这样调来调去呢?其实原因很简单,因为这部分的代码会被TableStatusBar复用,所以才这样设计。
废话不多说,我们重新回到PhoneStatusBar中,通过createAndAddWindows()执行到了addStatusBarWindow(),里面用调用了makeStatusBarView(),状态栏的布局文件就是在该方法中被加载进来的。
mStatusBarWindow =(StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
从上面的代码可以看到,Systemui中的总布局文件就是 super_status_bar.xml。当布局文件被加载后,就在addStatusBarWindow()中通过WindowManager将systemui显示到界面上。
WindowManagerImpl.getDefault().addView(mStatusBarWindow, lp);
至此,Systemui的创建就完成了。
3.状态栏图标的添加
当插入耳机的时候,在状态栏上会出现一个耳机图标;当bt打开时,在状态栏也会出现对应的图标,我们暂且称这些图标为状态栏图标。而usb连接时的提示,短信来的提示等,虽然也在状态栏添加了图标,但是它与前面的耳机图标是有区别的,我们暂且称呼这些图标为Notification图标。接下来,我们分析一下状态栏图标的添加流程(更新,删除流程类似)。
我们先来想一想,Systemui怎么知道bt是否打开,耳机是否插入了?没错,就是通过广播,所以,Systemui中的状态栏图标的更新就是通过广播机制实现的,这个接收器就注册在PhoneStatusBarPolicy中,这个类的逻辑比较简单,这里不再赘述,下面我们就以bt图标的添加为例来分析一下状态栏图标的更新流程。
当phoneStatusBarPolicy被new出来的时候,会将bt的图标添加到状态栏,并将其置为不可见,当收到bt打开或关闭的广播后会去更新bt图标的状态
mService.setIcon("bluetooth",bluetoothIcon, 0, null);
mService.setIconVisibility("bluetooth", mBluetoothEnabled);
可以看到,图标是通过mService的setIcon添加进去的,而mService是StatusBarManager的引用,通过StatusBarManager的setIcon最终会转到StatusBarManagerService的setIcon。
public voidsetIcon(String slot, String iconPackage, inticonId, int iconLevel,StringcontentDescription) {
enforceStatusBar();
synchronized (mIcons) {
int index= mIcons.getSlotIndex(slot);
if (index< 0) {
throw newSecurityException("invalid status bar icon slot: " +slot);
}
StatusBarIcon icon = newStatusBarIcon(iconPackage, iconId, iconLevel, 0,contentDescription);
mIcons.setIcon(index,icon);
if (mBar != null) {
try {
mBar.setIcon(index,icon);
} catch(RemoteException ex) {}
}
}
}
在这个方法中需要注意mIcons这个变量,这个变量在StatusBarManagerService实例化的时候进行了初始化操作。
mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
其中config_statusBarIcons是config.xml中的一个String数组,所有想要添加到状态栏的icon都必须在该数组中添加相应条目,数组内容如下(由于里面所包含item比较多,没有全部贴上)。
<item><xliff:g id="id">ime</xliff:g></item>
<item><xliff:gid="id">sync_failing</xliff:g></item>
<item><xliff:gid="id">sync_active</xliff:g></item>
<item><xliff:gid="id">gps</xliff:g></item>
<item><xliff:gid="id">bluetooth</xliff:g></item>
接下来我们来分析同步代码块儿中的内容,mIcons.getSlotIndex(slot)会查找mIcons中是否包含要添加的icon(即config.xml中是否添加了对应的item),如果没有,则抛出异常。这里bluetooth在config.xml中已经配置了,所以可以执行下去,mIcons.setIcon(index,icon)会把该icon对应的StatusBarIcon对象添加到StatusBarIconList中的一个数组里,StatusBarIconList中维护着两个数组:
privateString[] mSlots; //代表config_statusBarIcons字串数组
privateStatusBarIcon[] mIcons; //代表字串数组中每个条目所对应的icon信息
最后,调用mBar.setIcon(index, icon)去将图标添加到状态栏。不用说了,接下来大家肯定要问mBar是什么,我们看它的定义:
volatile IStatusBarmBar
volatile关键字是与线程同步相关的,这里不做解释,通过声明知道,mBar是 IStatusBar类型的。但mBar是在哪里初始化呢?搜索后发现在registerStatusBar方法中有mBar = bar。这就回到上节我们遗留下的那段代码,在BaseStatusBar中的start()方法里有这样一段代码:
mCommandQueue = newCommandQueue(this,iconList);
mBarService.registerStatusBar(mCommandQueue,iconList, notificationKeys, notifications,switches, binders);
StatusBarManagerService中的mBar其实就是BaseStatusBar中的mCommandQueue,而mBar.setIcon(index, icon)也就是调用CommandQueue的setIcon(),接下来就是通过Handler进行的一系列跳转了
case OP_SET_ICON: {
StatusBarIcon icon = (StatusBarIcon)msg.obj;
StatusBarIcon old = mList.getIcon(index);
if (old== null) {
mList.setIcon(index,icon);
mCallbacks.addIcon(mList.getSlot(index),index, viewIndex, icon);
} else {
mList.setIcon(index,icon);
mCallbacks.updateIcon(mList.getSlot(index),index, viewIndex,old, icon);
}
break;
}
从上面可以看到,如果icon不存在则添加,如果存在则更新。
这里的mList和mCallbacks都是通过CommandQueue的构造方法传递过来的,mCallbacks就是BaseStatusBar。
publicCommandQueue(Callbacks callbacks, StatusBarIconList list) {
mCallbacks =callbacks;
mList =list;
}
因此icon的添加和更新都会转到BaseStatusBar中执行,但BaseStatusBar中并没有对应的方法,所以就交给了其父类PhoneStatusBar执行。在PhoneStatusBar中的addIcon()执行mStatusIcons.addView(view, viewIndex, newLinearLayout.LayoutParams(mIconSize, mIconSize))就会将icon添加到状态栏。
4.Notification的显示
当用户收到短信的时候,会在状态栏出现一条提示信息,然后会显示一个未读短信的图标,这个提示信息就是Notification。
Notification的显示比状态栏图标的显示要简单点儿。任何应用都可以发送Notification,状态栏会将其所发送的Notification显示出来,Notification的添加和删除是由NotificationManagerService来管理的,状态栏只是负责显示而已,我们这里只讲状态栏。
从上面的流程图可以看到,NotificationManager通过notifiy()来发送Notification,在NotificationManagerService中会根据不同的条件执行Notification的添加,更新和删除操作,这里只说添加,更新和删除的流程类似。
mStatusBar.addNotification(n);
这里的mStatusBar不用说还是CommandQueue,也就是BaseStatusBar,在BaseStatusBar中调用了父类的addNotification(),最终又调用了BaseStatusBar的addNotificationViews(),在该方法内部执行了inflateViews将Notification显示了出来。