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 详细知识图谱
<think>好的,我现在要解决用户提到的Android SystemUI锁屏界面未根据壁纸颜色进行反色的问题。首先,我需要理解问题的具体情况。用户希望在锁屏界面上的状态栏或文字颜色能够根据壁纸的颜色自动调整,例如深色壁纸显示浅色文字,浅色壁纸显示深色文字,以确保可读性。这可能涉及到Android系统的动态颜色适配功能。 接下来,我应该回忆Android系统中处理锁屏界面UI的相关组件。根据用户提供的引用[^1],锁屏状态栏由KeyguardStatusBarView控制,特别是updateLayoutParamsForCutout方法负责刘海屏的适配。这说明锁屏界面的布局样式调整可能在这个类或其相关部分进行。 动态颜色适配通常涉及到获取壁纸的主色,然后根据颜色的亮度(luminance)来决定使用深色还是浅色文本。Android可能有相关的API可以获取壁纸的颜色信息,比如WallpaperColors类。需要检查KeyguardStatusBarView或其关联的代码中是否有获取WallpaperColors并应用颜色的逻辑。 如果现有代码中没有根据壁纸颜色调整样式的部分,可能需要添加相应的逻辑。可能的步骤包括: 1. **监听壁纸变化**:注册一个壁纸变化的监听器,当壁纸更换时触发颜色计算。 2. **计算颜色亮度**:提取壁纸的主色,计算其亮度,判断是否属于深色或浅色。 3. **应用反色样式**:根据计算结果,动态设置状态栏文本、图标等的颜色。 查看引用[^2]中的代码,用户可能尝试通过自动化测试脚本(如使用UI Automator)来模拟点击操作,但这与动态颜色适配无关。需要专注于SystemUI的源码修改。 可能的代码修改位置是KeyguardStatusBarViewController或相关的Presenter类,其中处理视图的更新。在updateLayoutParamsForCutout方法附近,可能可以添加颜色调整的逻辑。或者,在KeyguardStatusBarView的onAttachedToWindow方法中初始化颜色监听。 需要注意的是,动态颜色适配可能需要考虑API版本兼容性,尤其是Android 8.0(API 26)引入的WallpaperColors类。如果目标系统版本较低,可能需要其他方法获取壁纸颜色。 另外,测试时需确保修改后的颜色不会影响原有布局,比如刘海屏的适配是否仍然正确。同时,考虑用户是否启用了深色模式,可能需要结合系统主题设置。 总结解决方案步骤: - 在SystemUI的Keyguard模块中添加壁纸颜色监听。 - 提取主色并计算亮度,决定使用反色。 - 更新状态栏的文本图标颜色。 - 处理壁纸变化事件,实时更新UI。 最后,需要验证修改后的效果,确保不同壁纸下颜色适配正确,并且不会引起性能问题或UI错乱。</think>针对Android SystemUI锁屏界面未根据壁纸颜色反色的问题,可通过以下步骤实现动态颜色适配: ### 解决方案步骤 1. **获取壁纸主色** ```java WallpaperManager wallpaperManager = WallpaperManager.getInstance(context); WallpaperColors colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK); Color primaryColor = colors.getPrimaryColor(); ``` 使用`WallpaperColors`类提取锁屏壁纸主色[^1]。 2. **计算颜色亮度** $$ luminance = 0.299 \times R + 0.587 \times G + 0.114 \times B $$ 当亮度值$luminance < 128$时使用白色文字,否则使用黑色文字。 3. **修改状态栏颜色适配逻辑** ```java // 在KeyguardStatusBarView.java中 void updateTextColor(Color wallpaperColor) { float luminance = calculateLuminance(wallpaperColor); int textColor = (luminance < 128) ? Color.WHITE : Color.BLACK; mClockView.setTextColor(textColor); mBatteryLevel.setTextColor(textColor); } ``` 4. **添加壁纸变化监听** ```java wallpaperManager.addOnColorsChangedListener((colors, which) -> { if ((which & WallpaperManager.FLAG_LOCK) != 0) { updateTextColor(colors.getPrimaryColor()); } }, new Handler(Looper.getMainLooper())); ``` ### 关键实现细节 - **刘海屏兼容**:在`updateLayoutParamsForCutout()`方法中同步更新颜色参数,确保刘海区域文字可见性[^1] - **性能优化**:采用缓存机制存储颜色计算结果,避免频繁触发颜色分析 - **主题适配**:需同时检测系统深色模式状态`(config.uiMode & Configuration.UI_MODE_NIGHT_MASK)`
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值