13、车载SystemUI定制:从架构到实战

SystemUI,说白了就是车载系统里用户看得见、摸得着的那层皮。状态栏、导航栏、空调面板、座椅调节……这些都属于SystemUI的范畴。我刚开始接触Android Automotive时,以为SystemUI就是个普通的系统应用,后来才发现——它其实是系统框架的一部分,跟普通App的权限、生命周期完全不一样。

这一章,我会带你从架构入手,再深入到具体的定制点。嗯,咱们一步步来。

13.1 SystemUI架构与组件

SystemUI在Android Automotive里,是一个独立的进程,叫com.android.systemui。它不像普通App那样有Activity,而是通过SystemUIApplication启动一系列Service和BroadcastReceiver。

它的核心组件包括:

  • StatusBar:顶部状态栏,显示时间、信号、通知等。
  • NavigationBar:底部导航栏,包含返回、Home、最近任务(车载里通常简化)。
  • Keyguard:锁屏界面。
  • NotificationPanel:通知面板和快捷设置。
  • VolumeDialog:音量调节弹窗。
  • 车载特有组件:空调控制、座椅调节、驾驶模式切换等。

我建议你先把SystemUI的源码拉下来,看看SystemUIApplication.java里的startServicesIfNeeded()方法。它会根据系统配置,启动不同的Service。比如车载环境下,会启动CarSystemUI相关的服务。

核心思路:SystemUI的定制,本质上是替换或扩展这些Service和对应的布局文件。

下面这张图,是我画的一个简化版架构图,帮你理清关系:

车载SystemUI架构(简化) Android Framework(SystemServer、WindowManager、StatusBarManager等) 提供系统服务接口,如通知管理、窗口管理、输入事件分发 SystemUI进程(com.android.systemui) SystemUIApplication → 启动各Service StatusBar(状态栏) 时间、信号、通知图标 布局:status_bar.xml NavigationBar(导航栏) 返回、Home、最近任务 布局:navigation_bar.xml 车载特有组件 空调控制、座椅调节 布局:car_controls.xml res/layout/status_bar.xml res/layout/navigation_bar.xml res/layout/car_controls.xml

13.2 状态栏(StatusBar)定制

状态栏在车载里,跟手机不太一样。手机状态栏通常很窄,显示一堆图标。车载状态栏往往更宽,而且会加入一些车辆特有的信息,比如续航里程、驾驶模式。

我个人的习惯是,先找到StatusBar.java这个入口类。它继承自BaseStatusBar,负责创建和绑定状态栏的视图。

定制状态栏,主要做三件事:

  1. 修改布局:找到status_bar.xml,添加或删除控件。
  2. 修改数据源:比如添加一个显示电量的自定义View,需要注册对应的广播或回调。
  3. 修改交互逻辑:比如点击状态栏某个区域,弹出空调控制面板。

举个例子,我想在状态栏右侧加一个「空调快捷按钮」。我会这么做:

// 1. 在 status_bar.xml 中添加一个 ImageButton
<ImageButton
    android:id="@+id/ac_quick_btn"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/ic_ac"
    android:background="@null"
    android:onClick="onAcQuickClick"/>

// 2. 在 StatusBar.java 中绑定点击事件
public void onAcQuickClick(View v) {
    // 启动空调控制面板
    startActivity(new Intent(this, AcControlActivity.class));
}

小技巧:车载状态栏的布局文件,通常放在packages/SystemUI/res-car/layout/目录下。别找错了地方。

我曾经在项目里遇到一个问题:状态栏的字体颜色在白天和晚上需要自动切换。嗯,这个其实不难,用DayNight主题就能搞定。但要注意,车载的亮度传感器数据跟手机不太一样,需要从CarSensorManager获取。

13.3 导航栏(NavigationBar)定制

导航栏在车载里,往往被简化了。手机上的三个虚拟按键(返回、Home、最近任务),在车载里可能只剩下两个,甚至一个。有些车厂直接把导航栏做成一个悬浮的「Home」按钮,其他操作都交给方向盘。

导航栏的核心类是NavigationBarFragment.java。它负责加载布局,并处理按键事件。

定制导航栏,我建议你关注这几个点:

  • 按键数量:通过config_showNavigationBar等系统配置控制。
  • 按键图标:替换drawable目录下的图标资源。
  • 按键长按事件:比如长按Home键启动语音助手。
  • 导航栏隐藏策略:车载里通常不隐藏,但有些场景(比如全屏视频)需要自动隐藏。

看一段代码,如何自定义一个「返回」按键的长按事件:

// 在 NavigationBarFragment.java 中
private void initBackButton() {
    mBackButton = mView.findViewById(R.id.back);
    mBackButton.setOnLongClickListener(v -> {
        // 启动语音助手
        Intent intent = new Intent("com.android.car.action.VOICE_ASSIST");
        startActivity(intent);
        return true;
    });
}

注意:导航栏的按键事件,可能会跟系统的KeyEvent冲突。比如你拦截了返回键的长按,那系统默认的长按行为(比如进入分屏)就失效了。我曾经因为这个被测试提过bug,后来加了条件判断,只在特定场景下拦截。

13.4 车载特有UI元素

这部分是车载SystemUI跟手机最大的区别。空调控制、座椅调节、驾驶模式切换……这些UI元素,手机里根本没有。

它们通常以两种形式出现:

  • 内嵌在SystemUI中:比如在状态栏下方,直接显示一个空调温度条。
  • 作为独立窗口弹出:比如点击空调按钮,弹出一个半透明的控制面板。

我建议你采用第二种方式。为什么呢?因为空调控制面板通常比较复杂,有温度、风量、风向、座椅加热等多个控件。如果内嵌在SystemUI里,会让SystemUI的布局变得臃肿,而且容易跟其他UI元素重叠。

下面是一个空调控制面板的示例布局:

<!-- car_ac_control.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="#E0000000">

    <!-- 温度调节 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button android:id="@+id/temp_down" android:text="-" />
        <TextView android:id="@+id/temp_value" android:text="24°C" />
        <Button android:id="@+id/temp_up" android:text="+" />
    </LinearLayout>

    <!-- 风量调节 -->
    <SeekBar
        android:id="@+id/fan_speed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="7" />

    <!-- 座椅加热 -->
    <ToggleButton
        android:id="@+id/seat_heat"
        android:textOn="加热开"
        android:textOff="加热关" />
</LinearLayout>

这个面板,我会通过WindowManagerTYPE_SYSTEM_ALERT类型弹出。这样它就能覆盖在所有应用之上,而且不影响SystemUI本身的布局。

关键点:车载特有UI元素,一定要考虑驾驶安全。比如空调控制面板,最好在车辆行驶时自动收起,或者只显示简化版。我见过有些车厂的做法是:车速超过20km/h,空调面板自动隐藏,只保留一个温度数字。

座椅调节的UI,跟空调类似。但要注意,座椅调节通常需要跟硬件通信。比如调节座椅前后位置,需要调用CarSeatManager的API。这部分属于车载HAL层的内容,SystemUI只负责UI展示和用户交互,具体的硬件控制逻辑,应该封装在独立的Service里。

嗯,说到这里,我想起一个坑。有一次,我在SystemUI里直接调用了CarSeatManager的接口,结果发现SystemUI进程崩溃后,座椅调节功能也失效了。后来我把硬件控制逻辑移到了一个独立的CarService里,SystemUI只通过Binder调用它。这样即使SystemUI重启,座椅调节功能也不会受影响。

最后,总结一下车载SystemUI定制的核心思路:

组件 核心类/文件 定制要点
StatusBar StatusBar.java, status_bar.xml 布局修改、数据源扩展、交互逻辑
NavigationBar NavigationBarFragment.java, navigation_bar.xml 按键数量、图标、长按事件、隐藏策略
空调控制 AcControlActivity.java, car_ac_control.xml 独立窗口弹出、驾驶安全限制、硬件通信
座椅调节 SeatControlActivity.java, car_seat_control.xml UI交互、CarSeatManager调用、进程隔离

车载SystemUI的定制,说白了就是「理解架构、找到入口、按需修改」。你想想看,只要把SystemUIApplication的启动流程搞清楚了,剩下的就是布局和逻辑的增删改查。嗯,多动手试试,很快就能上手。

公众号:蓝海资料掘金营,微信deep3321