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>