Android SystemUI 介绍
dau7e87l24j8
7年前
<h2>前言</h2> <p>系统界面是Android系统的一部分,系统上方的Status Bar,以及下方的Navigation Bar都属于系统界面。除此之外,近期任务界面,锁屏也都属于系统界面。可见,系统界面是用户交互最多的UI元素。</p> <p>在Android系统最近几年的更新中,几乎每个版本都会对SystemUI做较大的改动。在接下来的几篇文章中,我们来了解一下Android SystemUI的相关功能和实现。</p> <h2>SystemUI整体介绍</h2> <h2>SystemUI简介</h2> <p>AOSP源码中,包含了两类Android应用程序:</p> <ul> <li>一类是系统的内置应用,这些应用提供了手机的基本功能。包括:Launcher,系统设置,电话,相机,相册等。它们位于 /packages/apps/ 目录下。理论上,这些应用都是可以被第三方应用所代替的,例如:你完全可以安装一个第三方的电话,相机,相册,而不使用系统的,这也是Android系统最为灵活的地方。(注:系统设置通常无法被第三方代替,因为它需要非常高的系统权限。)</li> <li>另外一类应用,则是属于Framework的一部分,这些应用是无法被第三方应用所代替的。它们位于 /frameworks/base/packages/ 目录下。包括:BackupRestoreConfirmation,DocumentsUI,PrintSpooler,SettingsProvider,SystemUI,V*NDialogs等</li> </ul> <p>我们看到,SystemUI便属于后者。</p> <p>接下来我们专门讲解SystemUI,因此摘录的源码绝大部分都位于/frameworks/base/packages/SystemUI/目录下。</p> <p>SystemUI中包含了非常多的组件,包括下面这些:</p> <ul> <li><strong>Status Bar</strong> 系统上方的状态栏</li> <li><strong>Navigator Bar</strong> 系统下方的导航栏</li> <li><strong>Keyguard</strong> 锁屏界面</li> <li><strong>PowerUI</strong> 电源界面</li> <li><strong>Recents Screen</strong> 近期任务界面</li> <li><strong>VolumeUI</strong> 音量调节对话框</li> <li><strong>Stack Divider</strong> 分屏功能调节器</li> <li><strong>PipUI</strong> 画中画界面</li> <li><strong>Screenshot</strong> 截屏界面</li> <li><strong>RingtonePlayer</strong> 铃声播放器界面</li> <li><strong>Settings Activity</strong> 系统设置中用到的一些界面,例如:NetworkOverLimitActivity,UsbDebuggingActivity等。</li> </ul> <p>下图展示了Android 7.1系统上四个场景下的SystemUI,分别是:</p> <ul> <li>锁屏界面</li> <li>解锁后的界面</li> <li>近期任务界面</li> <li>下拉的通知栏(展开了Quick Settings区域)</li> </ul> <p><img src="https://simg.open-open.com/show/5653db09dbeab33b9510890fe4babd37.png"></p> <h2>SystemUI的初始化</h2> <p>SystemUI是我们交互最多的UI元素,对它的基本功能我们就不多做介绍了。这里我们直接接触实现,看一下SystemUI是如何进行初始化的。</p> <p>整个SystemUI由一个Application的子类 - SystemUIApplication - 进行初始化,Application对应了整个应用程序的全局状态。</p> <p>系统会保证,Application对象一定是应用进程中第一个实例化的对象。并且,Application的onCreate方法一定早于应用中所有的Activity,Service,BroadcastReceiver(但是不包含ContentProvider)创建之前被调用。</p> <p>SystemUIApplication的onCreate方法代码如下:</p> <pre> <code class="language-java">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); } }</code></pre> <p>在这个方法中,注册了一个对于Intent.ACTION_BOOT_COMPLETED的广播接收器,这是系统启动完成之后会发送的一个广播。在收到这个广播之后,对mServices数组中的每一个对象调用onBootCompleted回调。</p> <p>我们知道,Android是一个多用户的操作系统。因此在SystemUIApplication中,将组件分为两类:</p> <ul> <li>一类是所有用户共用的SystemUI服务,例如:电源界面,Status Bar界面等</li> <li>另一类每个用户独有的服务,这类服务目前只有两个,它们是:近期任务和画中画界面</li> </ul> <p>下面这两个数组记录了这两个分类:</p> <pre> <code class="language-java">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 };</code></pre> <p>前面我们已经看到,SystemUI中包含了很多类型的界面。这些界面有一些共同的地方,例如它们都需要:</p> <ul> <li>处理模块的初始化</li> <li>处理系统的状态变化(例如旋转屏,时区变更等)</li> <li>执行dump</li> <li>处理系统启动完成的事件</li> </ul> <p>为了为系统界面组件处理这些共同的事件定下一个基础的结构,在SystemUI应用中,有一个名称也为SystemUI的抽象类,在这个类中,定义了几个方法让子类覆写。</p> <p>这个类的定义如下:</p> <pre> <code class="language-java">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); } }</code></pre> <p>这段代码的说明如下:</p> <ol> <li>为子类定义了一个start方法供子类完成初始化,这个方法是一个抽象方法,因此具体的子类必现实现。</li> <li>onConfigurationChanged是处理系统状态变化的回调,这里的状态变化包括:时区变更,字体大小变更,输入模式变更,屏幕大小变更,屏幕方向变更等等。具体请参见 android.content.res.Configuration 类。</li> <li>系统中很多的模块都包含了dump方法。dump方法用来将模块的内部状态dump到输出流中,这个方法主要是辅助调试所用。开发者可以在开发过程中,通过adb shell执行dump来了解系统的内部状态。</li> <li>onBootCompleted是系统启动完成的回调方法</li> </ol> <p>这里定义的onConfigurationChanged和onBootCompleted都是由SystemUIApplication负责回调的。</p> <p>SystemUI中包含的系统界面类型很多,因此SystemUI类的子类也很多,它们如下图所示:</p> <p><img src="https://simg.open-open.com/show/c51a71aa4a057cf6469eb3a35ea28f46.png"></p> <p>从这些类的名称上你应该大概能猜测到它们的作用。我们无法详细讲解每一个组件的详细逻辑,我们会尽可能选择其中最主要的一些进行讲解。</p> <h2>System Bar的初始化</h2> <p>下面,我们以最常见的System Bar(Status Bar和Navigation Bar合称System Bar)为例,看一下它是如何初始化的。</p> <p>SystemUIApplication负责了所有SystemUI组件的初始化,这其中就包括 com.android.systemui.statusbar.SystemBars 。 SystemBars主要代码如下所示:</p> <pre> <code class="language-java">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()); } ... }</code></pre> <p>这段代码说明如下:</p> <ol> <li>start方法由SystemUIApplication调用</li> <li>在start方法中,创建并启动了一个ServiceMonitor对象,这个对象start之后会调用onNoService方法</li> <li>调用createStatusBarFromConfig方法,根据配置文件中的信息来进行Status Bar的初始化</li> <li>读取配置文件中实现类的类名。这个值的定义位于:frameworks/base/packages/SystemUI/res/values/config.xml中。在手机上其值是: com.android.systemui.statusbar.phone.PhoneStatusBar</li> <li>通过类加载器加载对应的类</li> <li>通过反射API创建对象实例(如果你对Java的反射接口不熟悉,请自行在网上搜索相关的资料)</li> <li>最后调用实例的start方法对其进行初始化。如果是在手机设备,这里调用的就是 PhoneStatusBar.start 方法</li> </ol> <p>com.android.systemui.statusbar.phone.PhoneStatusBar是手机上Status Bar的实现。而在Tv和Car上,其Status Bar的实现类将是TvStatusBar和CarStatusBar,它们都是BaseStatusBar的子类。在SystemUI类图中,我们已经看到过这些子类了。</p> <p>PhoneStatusBar的内部初始化逻辑我们将在下一篇文章中详细讲解。</p> <p>有些读者可能会好奇,这里为什么要将类名配置在资源文件中,然后通过反射来创建对象实例。而为什么不直接通过类的构造函数进行初始化呢?</p> <p>答案是:这里将类名配置在资源文件中,那么对于Tv和Car这些不同的平台,可以不用修改任何的代码,只需要修改配置文件,便替换了系统中状态栏的实现,由此减少了模块间的耦合,也减少了系统的维护成本。</p> <p>这一点,在我们自己平时的设计和开发过程中是非常值得学习的,即: 对于那些平台相关的逻辑,尽量的放到代码之外的配置文件中进行控制,这样可以减少通过修改代码来改变实现,从而降低维护的成本。</p> <p>这里我们小节一下SystemUI的启动过程:</p> <p><img src="https://simg.open-open.com/show/2ccb3f2e7884be3132714bbbef7537b8.png"></p> <p>在对SystemUI有一个整体了解之后,我们会逐步讲解其中的主要组件。</p> <p>在下一篇文章中,我们会详细讲解SystemBar,敬请期待。</p> <p> </p> <p>来自:http://qiangbo.space/2017-05-09/SystemUI_Intro/</p> <p> </p>