Android SystemUI 介紹

dau7e87l24j8 7年前發布 | 24K 次閱讀 安卓開發 Android開發 移動開發

前言

系統界面是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>

這段代碼的說明如下:

  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());
}
...

}</code></pre>

這段代碼說明如下:

  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,敬請期待。

 

來自:http://qiangbo.space/2017-05-09/SystemUI_Intro/

 

 本文由用戶 dau7e87l24j8 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!