Android P下SystemUI的啓動與定製化

衆所周知SystemUI包含基本的StatusBar、VolumeBar、NavigationBar等部分,在手機開機時就已經爲咱們加載好,可是有時候會出現對StatusBar,DropList等進行定製化的任務,那麼就須要瞭解SystemUI的啓動流程,瞭解StatusBar,DropList等view是如何加載在系統界面上,下文是從SystemUI啓動入口、SystemUI的加載機制以及以StatusBar爲例來分析整個流程。下圖爲SystemUI啓動的整個時序圖: java

1、SystemUI的啓動入口

SystemUI的加載是在Android系統啓動的時候,那麼咱們能夠知道SystemUI的入口多是在系統啓動的流程中。經過調查,發如今SyetemServer進程中開始啓動系統服務,如AMS,PMS,藍牙,窗口管理服務等,其中就包括SystemUI,那麼就來看看代碼中是如何實現的。android

/frameworks/base/services/java/com/android/server/SystemServer.javabootstrap

public final class SystemServer {
   ...
   
    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }
        ...
        
    private void run() {
             ...
    
        // Initialize native services.
        System.loadLibrary("android_servers");

        // Check whether we failed to shut down last time we tried.
        // This call may not return.
        performPendingShutdown();

        // Initialize the system context.
        createSystemContext();

        // Create the system service manager.
        mSystemServiceManager = new SystemServiceManager(mSystemContext);
        mSystemServiceManager.setStartInfo(mRuntimeRestart,
                mRuntimeStartElapsedTime, mRuntimeStartUptime);
        LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
        // Prepare the thread pool for init tasks that can be parallelized
        SystemServerInitThreadPool.get();

        // Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }
        
        ...
    }

複製代碼

在SystemServer中能夠發現啓動SystemServer的是zygote進程,這個不屬於本文範疇先不作探討。在SystemServer的Main函數中,調用了run(),那麼跟進到run方法中(上述代碼省略了一部分,只保留主線),首先初始化了native services和system context,接着建立一個SystemServiceManager對象用於後續系統服務的啓動和管理。初始化完成,接下來就開始系統服務的啓動,這裏調用了startBootstrapServices()、startCoreServices()、startOtherServices()三個方法,從名字來看,分別是啓動引導service、啓動中心service、啓動其餘的services,這三個方法就是開啓不一樣的系統服務的入口,那就分別進入到三個方法中。數組

  • startBootstrapServices()
private void startBootstrapServices() {
        ...
     
        Installer installer = mSystemServiceManager.startService(Installer.class);

        mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
      
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);

        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
        
        mSystemServiceManager.startService(LightsService.class);
        
        mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
        ...

    }
    
複製代碼

在startBootstrapServices()方法中,能夠發現mSystemServiceManager.startServic()爲核心所在,方法中傳入不一樣的service做爲參數,以實現不一樣services的開啓,包括AMS,PMS,LightsService等系統所需的一些小的關鍵服務;bash

  • startCoreServices()
/**
     * Starts some essential services that are not tangled up in the bootstrap process.
     */
    private void startCoreServices() {
        traceBeginAndSlog("StartBatteryService");
        // Tracks the battery level.  Requires LightService.
        mSystemServiceManager.startService(BatteryService.class);
        traceEnd();

        // Tracks application usage stats.
        traceBeginAndSlog("StartUsageService");
        mSystemServiceManager.startService(UsageStatsService.class);
        mActivityManagerService.setUsageStatsManager(
                LocalServices.getService(UsageStatsManagerInternal.class));
        traceEnd();

        // Tracks whether the updatable WebView is in a ready state and watches for update installs.
        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
            traceBeginAndSlog("StartWebViewUpdateService");
            mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
            traceEnd();
        }

        ...
    }
    
複製代碼

一樣的,代碼中以mSystemServiceManager.startService()來開啓服務,只不過裏面的參數不一樣,就不作詳細的探討,繼續看第三個方法;app

  • startOtherServices()
private void startOtherServices() {
        ...
        traceBeginAndSlog("StartSystemUI");
            try {
                startSystemUi(context, windowManagerF);
            } catch (Throwable e) {
                reportWtf("starting System UI", e);
            }
    
    }
            
複製代碼

在第三個startOtherServices()方法中,除掉開啓一些系統所需的服務外,最主要的核心在於startSystemUi()方法,裏面傳入systemContext和WindowManagerService兩個參數,也就是說咱們已經找到systemUI啓動的入口,那麼就繼續進入到startSystemUi()方法中。ide

  • startSystemUi()
static final void startSystemUi(Context context, WindowManagerService windowManager) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();
    }

複製代碼

在上面代碼中能夠看見建立了一個Intent,而後經過設置組件名稱來開啓SystemUIService,至此,SystemUI才只是找到啓動的入口,對於系統啓動徹底完成,須要進入到SystemUIService中查看詳細的啓動流程。函數

2、SystemUI開始加載

第一部分找到了SystemUIService的啓動,那麼就先進入到SystemUIService類中。以下所示: /frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.javaui

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    ...
    }
}
複製代碼

在onCreate()中獲取了SystemUIApplication而且調用了它的startServicesIfNeeded()方法,那麼接着就進入到SystemUIApplication類中,在SystemUIApplication類中找到startServicesIfNeeded方法,以下。this

  • startServicesIfNeeded()
public class SystemUIApplication extends Application implements SysUiServiceProvider {
    ...
    
    public void startServicesIfNeeded() {
        String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
        startServicesIfNeeded(names);
    }
    
    ...
}
複製代碼

startServicesIfNeeded()方法中,首先建立了一個包含services的名字的數組,接着將獲取的數組做爲參數調用startServicesIfNeeded(String[] services)方法,這裏先不看這個方法內部的構造,先來看看上面數組獲取的都有哪些services,根據代碼中提供的id R.array.config_systemUIServiceComponents咱們在xml中找到config.xml這個文件,其中發現了一些數據,以下: /frameworks/base/packages/SystemUI/res/values/config.xml

<string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.Dependency</item>
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.SystemBars</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
    </string-array>
複製代碼

在上面這些item中,能夠發現都是一些咱們所熟悉的類,例如VolumeUI、SystemBars、PowerUI、KeyboardUI等,也就是咱們手機界面常看見的系統音量,鎖屏,狀態欄等,而這些UI正是SystemUI的構造部分。

在startServicesIfNeeded()方法中先將這些小部件集合在一塊兒,而後調用startServicesIfNeeded(String[] services),那麼咱們能夠猜想接下來是否是就要開始分別加載這些小部件而且將他們放置在相應的位置上。

  • startServicesIfNeeded(String[] services);
private void startServicesIfNeeded(String[] services) {
        private SystemUI[] mServices;
        ...
        mServices = new SystemUI[services.length];

        ...
        final int N = services.length;
        for (int i = 0; i < N; i++) {
            String clsName = services[i];
            
            Class cls;
            try {
                cls = Class.forName(clsName);
                mServices[i] = (SystemUI) cls.newInstance();
            } catch(ClassNotFoundException ex){
                throw new RuntimeException(ex);
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }

            mServices[i].mContext = this;
            mServices[i].mComponents = mComponents;
            if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
            mServices[i].start();
            ...
        }

}
複製代碼

首先建立了一個SystemUI數組,這個是用來裝載systemUI上各個小部件,接着遍歷了在startServicesIfNeeded()方法中獲取的services數組,經過反射的方式,獲取各個不一樣的systemUI的對象,最後分別調用他們的start()方法。例如循環第六次獲取到的是VolumeUI的對象,最後便調用的是VolumeUI的start()方法。

到這裏,正如上面的猜想,SystemUI開始加載不一樣位置的UI,而每一個UI內部是如何加載,如何將view放置在不一樣的位置上的,咱們繼續往下看。

3、StatusBar的加載與定製化

因爲SystemUI所包含的部分不少,這裏就以加載狀態欄StatusBar爲例。上述遍歷獲取到了SystemBar對象,並開始調用它的start(),那麼咱們就進入到SystemBar類中查看它的start()。

/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java

public class SystemBars extends SystemUI {
...
    @Override
    public void start() {
        if (DEBUG) Log.d(TAG, "start");
        createStatusBarFromConfig();
    }
    ...
     private void createStatusBarFromConfig() {
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
       
        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());
    }
}
複製代碼

start方法中調用了createStatusBarFromConfig(),接着進入到createStatusBarFromConfig中,在這裏,第一眼感受代碼有點熟悉,回想一下和上面SystemUIApplication類中startServicesIfNeeded(String[] services)加載不一樣systemUI的方法很像,都是使用了反射的手法,一樣的首先經過id 查找到config.xml文件裏的name,以下: /frameworks/base/packages/SystemUI/res/values/config.xml

<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
複製代碼

從這個string name能夠發現最後啓動的是StatusBar,也就是調用StatusBar的start()方法。

public class StatusBar extends SystemUI{

    @Override
    public void start() {
	...
	//第一步
	createAndAddWindows();
	...
    }
	
    public void createAndAddWindows() {
	//第二步
        addStatusBarWindow();
    }
	...
    private void addStatusBarWindow() {
	//第三步
        makeStatusBarView();
        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
        ...
	//第五步
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
    }
	...
    protected void makeStatusBarView() {
        final Context context = mContext;
        ...
	//第四步
        inflateStatusBarWindow(context);
    }
     	...
    //加載Layout,初始化StatusBarWindow
    protected void inflateStatusBarWindow(Context context) {
        mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
                R.layout.super_status_bar, null);
    }
}
複製代碼

在StatusBar類中省略了大量代碼,只保留了StatusBar加載的主要流程,從Start()方法中調用createAndAddWindows()接着再調用addStatusBarWindow(),緊接着在makeStatusBarView()方法中經過inflate加載layout的方式初始化StatusBarWindow,將super_status_bar.xml裏所設計的樣式加載到StatusBar界面上。

也就是說,當咱們碰到須要定製化SystemUI的狀況下,能夠本身自定義一個layout.xml,而後在這裏替換掉源文件。到了第5步是在初始化StatusBarWindow後,經過StatusBarWindowManager的add()方法,將statusBarView加載到系統界面上,並設置了statusBar的高度。那麼咱們就進入StatusBarWindowManager類的add()方法,以下所示:

public class StatusBarWindowManager{
	...
/**
     * Adds the status bar view to the window manager.
     *
     * @param statusBarView The view to add.
     * @param barHeight The height of the status bar in collapsed state.
     */
    public void add(View statusBarView, int barHeight) {

        // Now that the status bar window encompasses the sliding panel and its
        // translucent backdrop, the entire thing is made TRANSLUCENT and is
        // hardware-accelerated.
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                barHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
        mLp.token = new Binder();
        mLp.gravity = Gravity.TOP;
        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mStatusBarView = statusBarView;
        mBarHeight = barHeight;
        mWindowManager.addView(mStatusBarView, mLp);
        mLpChanged = new WindowManager.LayoutParams();
        mLpChanged.copyFrom(mLp);
    }
}

複製代碼

能夠發現StatusBar的加載真正在於WindowManager的處理,先設置好WindowManager.LayoutParams的寬高,層級TYPE,Flag等參數,而後將設置好的LayoutParams和上面傳進來的mStatusBarView做爲參數,調用addView()方法使View加載到相應的位置上。那麼咱們反過來思考下,若是將mStatusBarView換成咱們自定義的View,那麼結果會是什麼樣?

至此,StatusBar在SystemUI上的加載也就結束了,一樣的道理,VolumeBar,NavigationBar等SystemUI其餘部分的加載也和StatusBar的加載基本一致,這裏就再也不作分析。理清了上述代碼,再回到文章開頭看那張時序圖,就能夠清楚的知道SystemUI的啓動流程,在此基礎上,對SystemUI的定製化任務也就變得明朗起來。

參考資料:

Android系統啓動流程(三)解析SyetemServer進程啓動過程

相關文章
相關標籤/搜索