在前面的视频、文章中咱们介绍完了整个车载Android运用开发所需要的根底知识:

  1. 【视频文稿】车载Android运用开发与剖析 – 走进车载操作体系 –
  2. 【视频文稿】车载Android运用开发与剖析 – AOSP的下载与编译 –
  3. 【视频文稿】车载Android运用开发与剖析 – 开发体系运用 –
  4. 【视频文稿】车载Android运用开发与剖析 – AIDL实践与封装(上) –
  5. 【视频文稿】车载Android运用开发与剖析 – AIDL实践与封装(下) –

本期内容开始,咱们将介绍原生Android Automotive中车载运用的完结方式和它的原理。首要要介绍的就是车载运用开发中十分重要的一个体系运用,Android体系的UI – SystemUI

因为原生Android体系的SystemUI代码量很大、内容也十分庞杂,这儿我会挑选出对车载SystemUI开发具有参阅含义的模块进行介绍,大约会有4-5期的内容,首要分为以下几个模块:

  1. SystemUI「功用」和「源代码结构」

  2. SystemUI 「导航栏」与「状况栏」的完结原理,

  3. SystemUI「告诉栏」与「方便控制」的完结原理

  4. SystemUI「近期使命」的完结原理

那么本期内容,咱们先来剖析 SystemUI「功用」与「源代码结构」。阅览本期内容你能够得到以下的收成:

  1. 了解什么是SystemUI

  2. 了解SystemUI中首要完结了哪些功用

  3. 了解SystemUI源码的结构

  4. 了解SystemUI是怎么被体系发动的,以及它的初始化时序。

SystemUI 简介

在Android体系中SystemUI是一个体系级的APP,它供给了体系的用户界面,由system_server进程发动。

SystemUI自身不属于system_server进程,它是一个独立的进程。它的HMI包括了状况栏、导航栏、告诉栏、锁屏、近期使命等等。

SystemServer是一个由Zogyte进程发动的程序,它担任发动和办理Android体系中的各种核心服务。 例如:ActivityManagerService和PackageManagerService,这些服务也都运转在system_server进程中,为Android体系供给各种功用和接口,供运用程序和其他服务调用。咱们常说的Android Framework其实就是由这些Service组成的。

SystemUI 功用介绍

这部分将首要介绍那些对咱们定制自己的SystemUI时有参阅价值的模块。

  • 状况栏

StatusBar,担任显现时刻,电量,信号,告诉等状况信息。

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • 导航栏

NavigationBar,显现返回,主页,最近使命等按钮。在车载Android中,咱们多数时分会称为Dock栏(DockBar)。一般担任显现车控、主页、蓝牙电话等常用功用的方便控制和入口。

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • 告诉栏

NotificationPanel,显现、控制告诉的界面。实践的车载项目中告诉栏往往会和【音讯中心】合并成一个独立的APP。

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • 方便控制

QuickSettings,这个面板能够让用户快速地调整一些常用的设置,例如亮度、飞行形式、蓝牙等。QS面板有多种状况,包括初级展开面板(Quick Quick Settings,QQS)和完整QS面板(Quick Settings,QS)。用户能够经过下拉告诉栏来打开或封闭QS面板。

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • 其他功用

一些体系级的对话框、弹窗、动画、屏保等,这些内容相对比较简略,不再介绍了。而锁屏、媒体控制等一些功用,车载SystemUI开发时涉及的不多,也相同不再介绍。

SystemUI 源码结构

SystemUI的源码方位取决于你运用的Android版本和设备类型,本视频基于Android 13的源码进行剖析。

SystemUI 源码方位与结构

Android 13的SystemUI的源码坐落**frameworks/base/packages/SystemUI**目录下。

SystemUI的源码首要由Java和XML文件组成,其间Java文件完结了SystemUI的各种功用和逻辑,XML文件界说了SystemUI的界面和资源。SystemUI的源码还包含了一些测验,东西,文档等辅佐文件。SystemUI的源码结构如下:

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • animation: 包含了一些动画相关的类和资源。

  • checks: 包含了一些代码检查和格式化的东西。

  • compose: 包含了一些运用Jetpack Compose编写的界面组件。

  • customization: 包含了一些用于定制SystemUI的类和资源。

  • docs: 包含了一些文档和阐明文件。

  • monet: 包含了一些用于完结Material主题的类和资源。

  • plugin: 包含了一些用于完结插件化功用的类和接口。

  • plugin_core: 包含了一些用于支撑插件化功用的根底类和接口。

  • res: 包含了一些通用的资源文件,例如布局,图片,字符串等。

  • res-keyguard: 包含了一些用于锁屏界面的资源文件。

  • res-product: 包含了一些用于特定产品或设备的资源文件。

  • screenshot: 包含了一些用于截屏功用的类和资源。

  • scripts: 包含了一些用于编译或运转SystemUI的脚本文件。

  • shared: 包含了一些用于共享给其他运用或模块的类和接口。

  • src: 包含了SystemUI的首要源码文件,依照功用或模块进行分类,例如statusbar, navigationbar, notification, keyguard, recents等。

  • src-debug: 包含了一些用于调试或测验SystemUI的源码文件。

  • src-release: 包含了一些用于发布或优化SystemUI的源码文件。

  • tests: 包含了一些用于测验或验证SystemUI的源码文件。

  • tools: 包含了一些用于开发或剖析SystemUI的东西文件。

  • unfold: 包含了一些用于支撑折叠屏设备的类和资源。

CarSystemUI 源码结构

车载SystemUI的源码坐落 **/packages/apps/Car/SystemUI**目录下,CarSystemUI是对SystemUI的重用和扩展。CarSystemUI的源码结构如下:

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

  • res: 包含了一些通用的资源文件,例如布局,图片,字符串等。

  • res-keyguard: 包含了一些用于锁屏界面的资源文件。

  • samples:包含CarSystemUI的换肤资源,首要是利用了Android的RRO机制。

  • src: 包含了CarSystemUI的首要源码文件,依照功用或模块进行分类,例如statusbar, navigationbar, notification, keyguard, recents等。这些文件中有一些是对SystemUI中同名文件的修正或扩展,有一些是新增的文件,用于完结车载设备特有的功用或逻辑。

    • car: 包含了一些用于支撑车载设备特有的功用或逻辑的类和资源,例如CarSystemUIFactory, CarNavigationBarController, CarStatusBarController等。
    • wm: 包含了一些用于办理窗口形式和布局的类和资源,例如SplitScreenController, PipController, TaskStackListenerImpl等。
    • wmshell: 包含了一些用于供给窗口外壳功用的类和资源,例如WmShellImpl, WmShellModule, WmShellStartableModule等。
    • 其他子目录和文件:除了以上三个子目录外,其他子目录和文件基本上与SystemUI中的相同或类似,仅仅有一些针对车载设备的修正或扩展。例如,StatusBar类在车载设备上不显现电池图标,而是显现汽油图标。
  • tests: 包含了一些用于测验或验证CarSystemUI的源码文件

修正、编译 SystemUI

在Android源码的根目录下履行mm SystemUI,这会编译SystemUI模块及其依靠项。如果你修正了其他模块,例如frameworks/base,也能够履行mm framework-minus-apex来编译framework模块。

编译完结后,能够运用adb指令将新的SystemUI.apk推送到设备中,并重启SystemUI进程。详细的指令如下:

adb root
adb remount
adb push out/target/product/emulator_x86/system_ext/priv-app/CarSystemUI/CarSystemUI.apk /system_ext/priv-app/CarSystemUI/
adb shell ps -lef | grep systemui
adb shell kill <pid>

如果履行remount指令模拟器呈现read only的提示,需要先封闭模拟器,运用下面的指令发动模拟器。

emulator -writable-system -netdelay none -netspeed full
adb root
adb remount
adb reboot // 重启模拟器

SystemUI 的发动时序

SystemUI的发动时序是指SystemUI作为一个体系运用在Android体系发动过程中的加载、初始化流程。

SystemUI 发动流程

当Android体系发动完结后,system_server进程会经过ActivityManagerService发动一个名为com.android.systemui.SystemUIService的服务,这个服务是SystemUI的入口类,它承继了Service类。

SystemServer的源码方位:/frameworks/base/services/java/com/android/server/SystemServer.java

  mActivityManagerService.systemReady(() -> {
            Slog.i(TAG, "Making services ready");
            //...
            t.traceBegin("StartSystemUI");
            try {
                startSystemUi(context, windowManagerF);
            } catch (Throwable e) {
                reportWtf("starting System UI", e);
            }
            t.traceEnd();
        }, t);

从这儿咱们能够看出,SystemUI实质就是一个Service,经过Pm获取到的Component是com.android.systemui/.SystemUIService。startSystemUi代码细节如下:

private static void startSystemUi(Context context, WindowManagerService windowManager) {
        PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
        Intent intent = new Intent();
        intent.setComponent(pm.getSystemUiServiceComponent());
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();
}

以上就是SystemUI的发动流程,接下来咱们继续看SystemUI是怎么初始化的。

SystemUI 初始化流程

SystemUI的初始化流程分为以下几步:

  1. Application初始化

SystemUIApplication源码方位:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

SystemUI发动后,首要会调用Application的onCreate办法,并在onCreate办法中对SystemUI进行初始化。这儿我把它分为四个部分的内容。

  • 榜首部分
@Override
public void onCreate() {
    super.onCreate();
    Log.v(TAG, "SystemUIApplication created.");
    // TimingsTraceLog 是一个用于盯梢代码履行时刻的东西类,它能够在traceview中看到。
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);
    log.traceBegin("DependencyInjection");
    // 此行用于设置Dagger的依靠注入,并应保持在onrecate办法的顶部。
    mInitializer = mContextAvailableCallback.onContextAvailable(this);
    mSysUIComponent = mInitializer.getSysUIComponent();
    // BootCompleteCacheImpl 是一个用于缓存 BOOT_COMPLETED 播送的完结类。
    mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
    log.traceEnd();
    // 设置主线程Looper的traceTag,这样就能够在traceview中看到主线程的音讯处理状况了。
    Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
    // 设置一切服务承继的运用程序主题。请注意,仅在清单中设置运用程序主题仅适用于活动。请将其与在那里设置的主题同步。
    setTheme(R.style.Theme_SystemUI);
    ...见第二部分
}

榜首部分内容不多,首要是经过Dagger拿到SystemUI中的一些创建好的组件,同时设定一些调试东西。

  • 第二部分

首要判别当时进程是否属于体系用户。然后根据SF GPU上下文优先级设置设定SystemUI的烘托器的上下文优先级,最后敞开SystemServer的binder调用trace盯梢。

@Override
public void onCreate() {
    super.onCreate();
    ...见榜首部分
    // 判别当时进程是否是体系进程。如果是体系进程,那么就注册 BOOT_COMPLETED 播送接收器。
    if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
        // 创建 BOOT_COMPLETED 播送接收器的意图过滤器。
        IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
        bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        // 如果SF GPU上下文优先级设置为实时,则SysUI应以高优先级运转。优先级默以为中等。
        int sfPriority = SurfaceControl.getGPUContextPriority();
        Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority);
        if (sfPriority == ThreadedRenderer.EGL_CONTEXT_PRIORITY_REALTIME_NV) {
            Log.i(TAG, "Setting SysUI's GPU Context priority to: "+ ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
            // 设置SysUI的GPU上下文优先级为高。
            ThreadedRenderer.setContextPriority(ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
            // ThreadedRenderer能够简略了解为一个烘托器,它能够在后台线程中烘托视图层次结构。优先级越高,烘托速度越快。
        }
        // 在system_server上为源自SysUI的调用启用trace盯梢
        try {
            ActivityManager.getService().enableBinderTracing();
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to enable binder tracing", e);
        }
        ...见第三部分
    } else {
        ...见第四部分
    }
}

ThreadedRenderer能够简略了解为一个烘托器,它能够在后台线程中烘托视图层次结构。优先级越高,烘托速度越快。关于它详细效果能够参阅: 了解Android硬件加速的小白文 –

Process.myUserHandle()能够获取当时进程的用户类型。如果是从事移动端APP开发,很少会涉及Android体系的多用户机制。可是因为轿车是一种具有共享特点的东西,会存在多个家庭成员运用一辆车的状况,所以Android多用户在车载Android开发中较为常见。

当咱们在体系设置中的「体系」「多用户」增加一个新用户并切换到这个新用户时,实践上会再发动一个SystemUI进程,新的SystemUI进程的用户ID会从U1X开始,原始的SystemUI的用户ID则始终是U0

车载Android应用开发与分析 - SystemUI 「功能」与「源码结构」分析

有关Android的多用户,能够参阅官方材料:支撑多用户 – Android,之后我也会独自写篇博客阐述Android体系的多用户机制。

  • 第三部分

注册监听开机播送,并在SystemUIService发动后,再告诉SystemUI中的其它组件「体系发动完结」。

        // 注册 BOOT_COMPLETED 播送接收器。
        registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (mBootCompleteCache.isBootComplete()) return;
                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
                unregisterReceiver(this);
                mBootCompleteCache.setBootComplete();
                // 判别SystemUIService是否发动
                if (mServicesStarted) {
                    final int N = mServices.length;
                    for (int i = 0; i < N; i++) {
                    // 告诉SystemUI中各个组件,体系发动完结。
                        mServices[i].onBootCompleted();
                    }
                }
            }
        }, bootCompletedFilter);
        // Intent.ACTION_LOCALE_CHANGED 是体系言语发生变化时发送的播送。
        IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
        registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                    if (!mBootCompleteCache.isBootComplete()) return;
                    // 更新SystemUi告诉通道的称号
                    NotificationChannels.createAll(context);
                }
            }
        }, localeChangedFilter);
  • 第四部分

如果当时用户非体系用户那么调用startSecondaryUserServicesIfNeeded办法。

    } else {
        // 咱们不需要为正在履行某些使命的子进程初始化组件。例如:截图进程等
        String processName = ActivityThread.currentProcessName();
        ApplicationInfo info = getApplicationInfo();
        if (processName != null && processName.startsWith(info.processName + ":")) {
            return;
        }
        // 对于第二个用户,boot-completed永远不会被调用,因为它已经在发动时为主SystemUI进程播送了
        // 对于需要每个用户初始化SystemUI组件的组件,咱们现在为当时非体系用户发动这些组件。
        startSecondaryUserServicesIfNeeded();
    }

startSecondaryUserServicesIfNeeded办法也是经过startServicesIfNeeded办法来初始化SystemUI中的功用组件。详细是怎么初始化,咱们之后再来剖析。

void startSecondaryUserServicesIfNeeded() {
    // 对startables进行排序,以便咱们取得确认的次序。
    Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(Comparator.comparing(Class::getName));
    sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
    startServicesIfNeeded(sortedStartables, "StartSecondaryServices", null);
}

到这儿,咱们简略总结一下SystemUIApplication中其实最首要的作业,其实只有两个:

① 在体系用户空间中监听开机播送,并告诉 SystemUI 的功用组件。

② 在非体系用户空间中,直接初始化 SystemUI 的功用组件。

  1. 发动 SystemUIService

当Application完结初始化之后,紧接着,SystemUIService就会被发动。

SystemUIService源码方位:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java

SystemUIService在onCreate()办法中会调用((SystemUIApplication) getApplication()).startServicesIfNeeded()办法

@Override
public void onCreate() {
    super.onCreate();
    // Start all of SystemUI
    ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    ...
}

这儿可能有个疑问:为什么不把startServicesIfNeeded的相关逻辑写在Service中,非要写到Application中?

是因为,当时用户不是体系用户时,startSecondaryUserServicesIfNeeded也需要去调用startServicesIfNeeded办法进行组件初始化,所以爽性把一切的初始化逻辑都写到Application中了。

public void startServicesIfNeeded() {
    // vendorComponent 是一个字符串,它的值是:com.android.systemui.VendorServices
    // com.android.systemui.VendorServices 是一个空类,它的效果是在SysUI发动时,发动一些第三方服务。
    final String vendorComponent = mInitializer.getVendorComponent(getResources());
    // 对startables进行排序,以便咱们取得确认的次序
    // TODO: make #start idempotent and require users of CoreStartable to call it.
    Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
            Comparator.comparing(Class::getName));
    sortedStartables.putAll(mSysUIComponent.getStartables());
    sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
    startServicesIfNeeded(sortedStartables, "StartServices", vendorComponent);
}
  • Android 13以前

这个办法会根据配置文件config_systemUIServiceComponentsconfig_systemUIServiceComponentsPerUser中的界说运用反射来创建、发动一系列SystemUI的服务,例如StatusBar, NavigationBar, NotificationPanel, Keyguard等。这些服务每一个都扩展自一个名为SystemUI的接口。

SystemUI会为他们供给了一个Context,并为他们供给onConfigurationChanged和onBootCompleted的回调。这些服务是SystemUI的首要组件,担任供给各种功用和界面。

  • Android 13今后

增加了一个新的vendorComponent,vendorComponent 是一个字符串,它的值是:com.android.systemui.VendorServices。VendorServices承继自CoreStartable可是内部没有任何完结,google的设计目的是,在SysUI发动时,能够用来发动一些第三方服务。

Android 13以前每个SystemUI服务还会依靠于Dependency类供给的自界说依靠注入,来获取一些跨过SystemUI生命周期的目标。可是Android 13之后,SystemUI功用组件的创建和依靠注入都是Dagger自动完结。

private void startServicesIfNeeded(Map<Class<?>, Provider<CoreStartable>> startables,String metricsPrefix,String vendorComponent) {
    if (mServicesStarted) {
        return;
    }
    mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)];
    if (!mBootCompleteCache.isBootComplete()) {
        // 检查BOOT_COMPLETED是否已经发送。如果是这样,咱们不需要等候它。
        // see ActivityManagerService.finishBooting()
        if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
            mBootCompleteCache.setBootComplete();
            if (DEBUG) {
                Log.v(TAG, "BOOT_COMPLETED was already sent");
            }
        }
    }
    mDumpManager = mSysUIComponent.createDumpManager();
    Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + ".");
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);
    log.traceBegin(metricsPrefix);
    int i = 0;
    for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
        String clsName = entry.getKey().getName();
        int j = i;  // Copied to make lambda happy.
        // timeInitialization 记载初始化的时刻
        timeInitialization(clsName,
                () -> mServices[j] = startStartable(clsName, entry.getValue()),
                log,
                metricsPrefix);
        i++;
    }
    if (vendorComponent != null) {
        timeInitialization(
                vendorComponent,
                () -> mServices[mServices.length - 1] =
                        startAdditionalStartable(vendorComponent),
                log,
                metricsPrefix);
    }
    for (i = 0; i < mServices.length; i++) {
        if (mBootCompleteCache.isBootComplete()) {
            mServices[i].onBootCompleted();
        }
        mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
    }
    mSysUIComponent.getInitController().executePostInitTasks();
    log.traceEnd();
    mServicesStarted = true;
}

有关Android13 的SystemUI中Dagger是怎么运用的。能够阅览官方文档:frameworks/base/packages/SystemUI/docs/dagger.md

咱们再来小结一下SystemUIService的初始化流程,能够概括为以下四步:

①调用SystemUIApplication中的startServicesIfNeeded办法

②startServicesIfNeeded办法经过Dagger获取到创建好的SystemUI的功用组件,并根据包名、类名进行排序。

③顺次调用SystemUI功用组件的start()办法,并记载耗时。

④当接收到BOOT_COMPLETED播送或检查SystemProperty中已经完结开机,则顺次调用 SystemUI 功用组件的onBootCompleted()完结 SystemUI 的初始化。

总结

本期内容咱们简略介绍了Android体系中SystemUI的功用、源码结构以及发动时序。

最近无论是视频还是博客更新的都很慢,原因其实我在B站发了动态阐明,因为裁员,接下来相当一段时刻不得不多花点时刻在作业上了。

好,感谢你的阅览,期望对你有所帮助,咱们下期内容再见。