接下来会有几篇文章来解析Android系统的SystemUI功能,这里是第一篇入门介绍。
前言
系统界面是Android系统的一部分,系统上方的Status Bar,以及下方的Navigation Bar都属于系统界面。除此之外,近期任务界面,锁屏也都属于系统界面。可见,系统界面是用户交互最多的UI元素。
在Android系统最近几年的更新中,几乎每个版本都会对SystemUI做较大的改动。在接下来的几篇文章中,我们来了解一下Android SystemUI的相关功能和实现。
SystemUI整体介绍
SystemUI简介
AOSP源码中,包含了两类Android应用程序:
- 一类是系统的内置应用,这些应用提供了手机的基本功能。包括:Launcher,系统设置,电话,相机,相册等。它们位于
/packages/apps/
目录下。理论上,这些应用都是可以被第三方应用所代替的,例如:你完全可以安装一个第三方的电话,相机,相册,而不使用系统的,这也是Android系统最为灵活的地方。(注:系统设置通常无法被第三方代替,因为它需要非常高的系统权限。) - 另外一类应用,则是属于Framework的一部分,这些应用是无法被第三方应用所代替的。它们位于
/frameworks/base/packages/
目录下。包括:BackupRestoreConfirmation,DocumentsUI,PrintSpooler,SettingsProvider,SystemUI,VpnDialogs等
我们看到,SystemUI便属于后者。
接下来我们专门讲解SystemUI,因此摘录的源码绝大部分都位于/frameworks/base/packages/SystemUI/目录下。
SystemUI中包含了非常多的组件,包括下面这些:
- Status Bar 系统上方的状态栏
- Navigator Bar 系统下方的导航栏
- Keyguard 锁屏界面
- PowerUI 电源界面
- Recents Screen 近期任务界面
- VolumeUI 音量调节对话框
- Stack Divider 分屏功能调节器
- PipUI 画中画界面
- Screenshot 截屏界面
- RingtonePlayer 铃声播放器界面
- Settings Activity 系统设置中用到的一些界面,例如:NetworkOverLimitActivity,UsbDebuggingActivity等。
下图展示了Android 7.1系统上四个场景下的SystemUI,分别是:
- 锁屏界面
- 解锁后的界面
- 近期任务界面
- 下拉的通知栏(展开了Quick Settings区域)
SystemUI的初始化
SystemUI是我们交互最多的UI元素,对它的基本功能我们就不多做介绍了。这里我们直接接触实现,看一下SystemUI是如何进行初始化的。
整个SystemUI由一个Application的子类 - SystemUIApplication - 进行初始化,Application对应了整个应用程序的全局状态。
系统会保证,Application对象一定是应用进程中第一个实例化的对象。并且,Application的onCreate方法一定早于应用中所有的Activity,Service,BroadcastReceiver(但是不包含ContentProvider)创建之前被调用。
SystemUIApplication的onCreate方法代码如下:
public void onCreate() {
super.onCreate();
setTheme(R.style.systemui_theme);
SystemUIFactory.createFromConfig(this);
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mBootCompleted) return;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
unregisterReceiver(this);
mBootCompleted = true;
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
mServices[i].onBootCompleted();
}
}
}
}, filter);
} else {
startServicesIfNeeded(SERVICES_PER_USER);
}
}
在这个方法中,注册了一个对于Intent.ACTION_BOOT_COMPLETED的广播接收器,这是系统启动完成之后会发送的一个广播。在收到这个广播之后,对mServices数组中的每一个对象调用onBootCompleted回调。
我们知道,Android是一个多用户的操作系统。因此在SystemUIApplication中,将组件分为两类:
- 一类是所有用户共用的SystemUI服务,例如:电源界面,Status Bar界面等
- 另一类每个用户独有的服务,这类服务目前只有两个,它们是:近期任务和画中画界面
下面这两个数组记录了这两个分类:
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
Divider.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
com.android.systemui.tv.pip.PipUI.class,
com.android.systemui.shortcut.ShortcutKeyDispatcher.class,
com.android.systemui.VendorServices.class
};
private final Class<?>[] SERVICES_PER_USER = new Class[] {
com.android.systemui.recents.Recents.class,
com.android.systemui.tv.pip.PipUI.class
};
前面我们已经看到,SystemUI中包含了很多类型的界面。这些界面有一些共同的地方,例如它们都需要:
- 处理模块的初始化
- 处理系统的状态变化(例如旋转屏,时区变更等)
- 执行dump
- 处理系统启动完成的事件
为了为系统界面组件处理这些共同的事件定下一个基础的结构,在SystemUI应用中,有一个名称也为SystemUI的抽象类,在这个类中,定义了几个方法让子类覆写。
这个类的定义如下:
public abstract class SystemUI {
public Context mContext;
public Map<Class<?>, Object> mComponents;
public abstract void start(); ①
protected void onConfigurationChanged(Configuration newConfig) {} ②
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} ③
protected void onBootCompleted() {} ④
@SuppressWarnings("unchecked")
public <T> T getComponent(Class<T> interfaceType) {
return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
}
public <T, C extends T> void putComponent(Class<T> interfaceType, C component) {
if (mComponents != null) {
mComponents.put(interfaceType, component);
}
}
public static void overrideNotificationAppName(Context context, Notification.Builder n) {
final Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
context.getString(com.android.internal.R.string.android_system_label));
n.addExtras(extras);
}
}
这段代码的说明如下:
- 为子类定义了一个start方法供子类完成初始化,这个方法是一个抽象方法,因此具体的子类必现实现。
- onConfigurationChanged是处理系统状态变化的回调,这里的状态变化包括:时区变更,字体大小变更,输入模式变更,屏幕大小变更,屏幕方向变更等等。具体请参见android.content.res.Configuration类。
- 系统中很多的模块都包含了dump方法。dump方法用来将模块的内部状态dump到输出流中,这个方法主要是辅助调试所用。开发者可以在开发过程中,通过adb shell执行dump来了解系统的内部状态。
- onBootCompleted是系统启动完成的回调方法
这里定义的onConfigurationChanged和onBootCompleted都是由SystemUIApplication负责回调的。
SystemUI中包含的系统界面类型很多,因此SystemUI类的子类也很多,它们如下图所示:
从这些类的名称上你应该大概能猜测到它们的作用。我们无法详细讲解每一个组件的详细逻辑,我们会尽可能选择其中最主要的一些进行讲解。
System Bar的初始化
下面,我们以最常见的System Bar(Status Bar和Navigation Bar合称System Bar)为例,看一下它是如何初始化的。
SystemUIApplication负责了所有SystemUI组件的初始化,这其中就包括com.android.systemui.statusbar.SystemBars
。
SystemBars主要代码如下所示:
public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
private static final String TAG = "SystemBars";
private static final boolean DEBUG = false;
private static final int WAIT_FOR_BARS_TO_DIE = 500;
private ServiceMonitor mServiceMonitor;
private BaseStatusBar mStatusBar;
@Override
public void start() { ①
if (DEBUG) Log.d(TAG, "start");
mServiceMonitor = new ServiceMonitor(TAG, DEBUG,
mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
mServiceMonitor.start(); ②
}
@Override
public void onNoService() {
if (DEBUG) Log.d(TAG, "onNoService");
createStatusBarFromConfig(); ③
}
...
private void createStatusBarFromConfig() {
if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
final String clsName = mContext.getString(R.string.config_statusBarComponent); ④
if (clsName == null || clsName.length() == 0) {
throw andLog("No status bar component configured", null);
}
Class<?> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName); ⑤
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
mStatusBar = (BaseStatusBar) cls.newInstance(); ⑥
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
mStatusBar.start(); ⑦
if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
...
}
这段代码说明如下:
- start方法由SystemUIApplication调用
- 在start方法中,创建并启动了一个ServiceMonitor对象,这个对象start之后会调用onNoService方法
- 调用createStatusBarFromConfig方法,根据配置文件中的信息来进行Status Bar的初始化
- 读取配置文件中实现类的类名。这个值的定义位于:frameworks/base/packages/SystemUI/res/values/config.xml中。在手机上其值是:
com.android.systemui.statusbar.phone.PhoneStatusBar
- 通过类加载器加载对应的类
- 通过反射API创建对象实例(如果你对Java的反射接口不熟悉,请自行在网上搜索相关的资料)
- 最后调用实例的start方法对其进行初始化。如果是在手机设备,这里调用的就是
PhoneStatusBar.start
方法
com.android.systemui.statusbar.phone.PhoneStatusBar是手机上Status Bar的实现。而在Tv和Car上,其Status Bar的实现类将是TvStatusBar和CarStatusBar,它们都是BaseStatusBar的子类。在SystemUI类图中,我们已经看到过这些子类了。
PhoneStatusBar的内部初始化逻辑我们将在下一篇文章中详细讲解。
有些读者可能会好奇,这里为什么要将类名配置在资源文件中,然后通过反射来创建对象实例。而为什么不直接通过类的构造函数进行初始化呢?
答案是:这里将类名配置在资源文件中,那么对于Tv和Car这些不同的平台,可以不用修改任何的代码,只需要修改配置文件,便替换了系统中状态栏的实现,由此减少了模块间的耦合,也减少了系统的维护成本。
这一点,在我们自己平时的设计和开发过程中是非常值得学习的,即:对于那些平台相关的逻辑,尽量的放到代码之外的配置文件中进行控制,这样可以减少通过修改代码来改变实现,从而降低维护的成本。
这里我们小节一下SystemUI的启动过程:
在对SystemUI有一个整体了解之后,我们会逐步讲解其中的主要组件。
在下一篇文章中,我们会详细讲解SystemBar,敬请期待。