SystemUI(一)基于Android9.0SystemUI的启动与定制化

个人博客 https://fuusy.github.io/

众所周知SystemUI包含基本的StatusBar、VolumeBar、NavigationBar等部分,在手机开机时就已经为我们加载好,但是有时候会出现对StatusBar,DropList等进行定制化的任务,那么就需要了解SystemUI的启动流程,了解StatusBar,DropList等view是如何加载在系统界面上,下文是从SystemUI启动入口、SystemUI的加载机制以及以StatusBar为例来分析整个流程。下图为SystemUI启动的整个时序图:


image

一、SystemUI的启动入口

SystemUI的加载是在Android系统启动的时候,那么我们可以知道SystemUI的入口可能是在系统启动的流程中。通过调查,发现在SyetemServer进程中开始启动系统服务,如AMS,PMS,蓝牙,窗口管理服务等,其中就包括SystemUI,那么就来看看代码中是如何实现的。

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

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等系统所需的一些小的关键服务;

  • 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()来开启服务,只不过里面的参数不同,就不做详细的探讨,继续看第三个方法;

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

在第三个startOtherServices()方法中,除掉开启一些系统所需的服务外,最主要的核心在于startSystemUi()方法,里面传入systemContext和WindowManagerService两个参数,也就是说我们已经找到systemUI启动的入口,那么就继续进入到startSystemUi()方法中。

  • 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中查看详细的启动流程。

二、SystemUI开始加载

第一部分找到了SystemUIService的启动,那么就先进入到SystemUIService类中。如下所示:
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    ...
    }
}

在onCreate()中获取了SystemUIApplication并且调用了它的startServicesIfNeeded()方法,那么接着就进入到SystemUIApplication类中,在SystemUIApplication类中找到startServicesIfNeeded方法,如下。

  • 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放置在不同的位置上的,我们继续往下看。

三、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进程启动过程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容

  • 曾经历过一场自以为与众不同的婚姻,象许多高明的不高明的愛情故事里描写的那样,有快乐、有争吵、更有悲哀…… 回过头来...
    金城丁香开阅读 1,145评论 6 5