Android SystemUI 介绍

Posted on May 9, 2017


接下来会有几篇文章来解析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);
    }
}

这段代码的说明如下:

  1. 为子类定义了一个start方法供子类完成初始化,这个方法是一个抽象方法,因此具体的子类必现实现。
  2. onConfigurationChanged是处理系统状态变化的回调,这里的状态变化包括:时区变更,字体大小变更,输入模式变更,屏幕大小变更,屏幕方向变更等等。具体请参见android.content.res.Configuration类。
  3. 系统中很多的模块都包含了dump方法。dump方法用来将模块的内部状态dump到输出流中,这个方法主要是辅助调试所用。开发者可以在开发过程中,通过adb shell执行dump来了解系统的内部状态。
  4. 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());
    }
    ...
}

这段代码说明如下:

  1. start方法由SystemUIApplication调用
  2. 在start方法中,创建并启动了一个ServiceMonitor对象,这个对象start之后会调用onNoService方法
  3. 调用createStatusBarFromConfig方法,根据配置文件中的信息来进行Status Bar的初始化
  4. 读取配置文件中实现类的类名。这个值的定义位于:frameworks/base/packages/SystemUI/res/values/config.xml中。在手机上其值是:com.android.systemui.statusbar.phone.PhoneStatusBar
  5. 通过类加载器加载对应的类
  6. 通过反射API创建对象实例(如果你对Java的反射接口不熟悉,请自行在网上搜索相关的资料)
  7. 最后调用实例的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,敬请期待。


 Contents