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,V*NDialogs等
我們看到,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);
}
}</code></pre>
在這個方法中,注冊了一個對于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
};</code></pre>
前面我們已經看到,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);
}
}</code></pre>
這段代碼的說明如下:
- 為子類定義了一個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());
}
...
}</code></pre>
這段代碼說明如下:
- 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,敬請期待。
來自:http://qiangbo.space/2017-05-09/SystemUI_Intro/