Android 8.0 SystemUI 源碼分析(二):啓動流程和初始化

本文首發微信公衆號:菜天Android

零、前言

上篇『圖文並茂的介紹:D』中我對 Android 8.0 中的SystemUI 作了簡要的介紹,自我感覺很不錯,又是思維導圖又是截圖的,覺得會對不瞭解的人會有幫助。但可能是太簡要了,被罵是水貨。qaq,呸呸呸!

這篇的話,將對SystemUI的啓動和大體的初始化作描述。篇幅應該比上篇多了些。哈哈。

老樣子,先上目錄,簡潔明瞭。

一、概述

由於需要實時反饋系統狀態,如藍牙開關、wifi開關、時間及相應用戶導航欄操作,SystemUI從系統一啓動就被帶起來了(SystemUI:我也不想啊!老累了!)。正常使用過程中的SystemUI,大多數功能模塊都是出於運行狀態,只有少數功能,比如:截屏功能,你長按電源+音量下鍵纔會咔嚓截屏。

這裏簡要說下系統啓動過程:由init進程->Zygote進程->SystemServer進程。

那init進程又是怎麼起來的?當你按下電源鍵,系統上電,從固定地址開始加載固化在ROM的Bootloader代碼到RAM中並執行,Bootloader引導程序負責將系統OS拉起。當系統OS被拉起,並完成一些列初始化和系統設置後,就會首先在系統文件中尋找“init”文件並啓動這個咱們用戶空間的第一個進程。

Emmm,扯遠了,回到主題。按照一開始的系統啓動過程,我們的SystemUI進程是在SystemServer的啓動過程中被帶起來。

從第一篇介紹我們知道,SystemUI有着很多的模塊且對應着相應的界面。這些模塊有些共同的地方,例如都需要:

  • 處理各自模塊的初始化
  • 處理系統的狀態變化
  • 執行dump
  • 系統啓動完成時,要處理相應邏輯

所以在代碼中體現爲:將這些共同點提取並抽象,形成SystemUI抽象類,結構如下。

簡單對這段代碼說明下:

  1. 爲子類定義了一個start方法供子類完成初始化,這個方法是一個抽象方法,因此具體的子類必現實現。
  2. onConfigurationChanged是處理系統狀態變化的回調,這裏的狀態變化包括:時區變更,字體大小變更,輸入模式變更,屏幕大小變更,屏幕方向變更等。
  3. 系統中很多的模塊都包含了dump方法。dump方法用來將模塊的內部狀態dump到輸出流中,這個方法主要是輔助調試所用。開發者可以在開發過程中,通過adb shell執行dump來了解系統的內部狀態。
  4. onBootCompleted是系統啓動完成的回調方法。

除了截屏服務,提及模塊均繼承抽象類SystemUI並在應用啓動時被分別初始化。從這種角度來看,SystemUI應用更像是這些功能模塊的容器。

值得一提的是,和Nougat相比,在Oreo中,抽象類SystemUI的兩個子類:BaseStatusBar和PhoneStatusBar被合併替換成了StatusBar.java。相信瞭解接觸過SystemUI模塊的老司機對PhoneStatusBar這個核心類一定很熟悉和,現在也塵歸塵、土歸土咯。

二、SystemUI啓動流程

SystemServer負責系統中各種重要服務的啓動,不巧,由於SystemUI的重要性,她也在被啓動之列,雖然是處於“Other”的地位~(SystemServer的代碼對系統服務類別大體分爲三類:Bootstrap->Core->Other,SystemUI的啓動就在Other中)。

在startOtherServices()中,通過調用AMS的systemReady()方法通知AMS準備就緒。systemReady()擁有一個名爲goingCallback的Runnable實例作爲參數,So,當AMS完成對systemReady()的處理後將會回調這一Runnable的run()方法。

private void startOtherServices() {
    ... //省略大概1000行
    mActivityManagerService.systemReady(() -> {
        Slog.i(TAG, "Making services ready");
        ...
        traceBeginAndSlog("StartSystemUI");
        try {
            startSystemUi(context, windowManagerF);
        } catch (Throwable e) {
            reportWtf("starting System UI", e);
        }
        ...
    }       
}

並在startSystemUi方法中,通過紅框中的組件名啓動了SystemUI中的SystemUIService服務

對於Android系統來說,當一個應用啓動,系統會保證其Application類是第一個被實例化的類,並且Application的onCreate方法,一定先於應用中所有的Activity,Service和BroadcastReceiver的創建。

SystemUI中,SystemUIApplication就是第一個被實例化的類。

在其中,定義了兩組服務:

  • 一類是所有用戶共用的SystemUI服務,例如:Status Bar
  • 一類是每個用戶獨有的服務

下面的兩個數組記錄了這兩組服務

前面也提到,SystemUI抽取了功能模塊的共性形成抽象類SystemUI.class,上圖中所有列出的類型,均是SystemUI的子類實現。

接着說,在SystemUIApplication中,onCreate方法被調用:主要註冊一個廣播接收器,用以接收BOOT_COMPLETED廣播,在接收到廣播後,調用各模塊的函數onBootCompleted。

你還記的SystemServer中啓動SystemUI的代碼不?那個目標是SystemUIService的Intent。

當SystemApplication家的onCreate執行完畢,就會去啓動這個SystemUIService。按照規矩,此服務onCreate方法在啓動時被調用。

不研究不知道,哇靠,這傢伙只是箇中轉代理(給別人一個啓動你的機會)而已~,不信你看,實際代碼只有下面一行,看下圖。

整個服務,真正幹活的只有紅框中的一句話。仔細瞅瞅,調用的是SystemUIApplication中的startServicesIfNeeded方法,轉了一圈又回來了。

這就是啓動各模塊的地方。

private void startServicesIfNeeded(Class<?>[] services) {
    if (mServicesStarted) {
        return;
    }

    if (!mBootCompleted) {
        // check to see if maybe it was already completed long before we began
        // see ActivityManagerService.finishBooting()
        if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
            // sys.boot_completed屬性值,在系統boot完成後,AMS會對其進行設置
            mBootCompleted = true;
            if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
        }
    }

    Log.v(TAG, "Starting SystemUI services for user " + 
            Process.myUserHandle().getIdentifier() + ".");
    final int N = services.length;
    for (int i = 0; i < N; i++) {
        Class<?> cl = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + cl);
        try {
            // SystemUIFactory類在6.0上還沒有,是7.0上出現的,目的是爲了提供可定製化的SystemUI
            Object newService = SystemUIFactory.getInstance().createInstance(cl);
            mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        }

        // 對數組中的每一個Service都進行初始化
        mServices[i].mContext = this;
        mServices[i].mComponents = mComponents;
        if (DEBUG) Log.d(TAG, "running: " + mServices[i]); 
        mServices[i].start();

        if (mBootCompleted) {
            mServices[i].onBootCompleted();
        }
    }
    ...
}

對於SystemUI App啓動,這裏總結了一張啓動時序圖給大家參考,如下所示:

進一步的話,就到了各個模塊獨自的初始化邏輯了。

這裏我們單獨對SystemBars的初始進行進一步的說明。


三、舉例說明:SystemBars

SystemBars主要包含了NavigationBar和StatusBar,不知道這兩個Bar對應位置的同學可以看下第一篇『圖文並茂的介紹:D』

Show the code:

public class SystemBars extends SystemUI {
    private static final String TAG = "SystemBars";    
    private static final boolean DEBUG = false;    
    private static final int WAIT_FOR_BARS_TO_DIE = 500; 
       
    // in-process fallback implementation, per the product config
    private SystemUI mStatusBar;    
    
    @Override
    public void start() {    
        if (DEBUG) Log.d(TAG, "start");        
        createStatusBarFromConfig();
    }    
     
    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {        
        if (mStatusBar != null) {            
            mStatusBar.dump(fd, pw, args);
        }
    }    
    
    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 = (SystemUI) 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());
    } 
 
    private RuntimeException andLog(String msg, Throwable t) {
        Log.w(TAG, msg, t);
        throw new RuntimeException(msg, t);
    }
}

這段代碼說明如下:

  1. start方法由SystemUIApplication調用
  2. 調用createStatusBarFromConfig方法,根據配置文件中的信息來進行Status Bar的初始化
  3. 讀取配置文件中實現類的類名。這個值於frameworks/base/packages/SystemUI/res/values/config.xml中定義。在手機中,其值是:com.android.systemui.statusbar.phone.StatusBar
  4. 通過類加載器加載對應的類
  5. 通過反射API創建對象實例
  6. 最後調用實例的start方法對其進行初始化。如果是手機設備,這裏就是StatusBar.start方法

爲什麼要讀取資源文件獲取類名並通過反射來創建實例呢?

好處是:

這裏將類名配置在資源文件中,那麼對於Tv和Car這些不同的平臺,可以不用修改任何的代碼,只需要修改配置文件,便替換了系統中狀態欄的實現,由此減少了模塊間的耦合,也減少了系統的維護成本。

值得我們借鑑。


好了,SystemUI的啓動到這裏就結束了,具體SystemBars以及StatusBar都做了些什麼,後續會進行跟進。


歡迎關注微信公衆號
菜天Android
主推 Android 乾貨文章
關注得 Android 詳細知識圖譜
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章