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和对应的布局文件。
下面这张图,是我画的一个简化版架构图,帮你理清关系:
13.2 状态栏(StatusBar)定制
状态栏在车载里,跟手机不太一样。手机状态栏通常很窄,显示一堆图标。车载状态栏往往更宽,而且会加入一些车辆特有的信息,比如续航里程、驾驶模式。
我个人的习惯是,先找到StatusBar.java这个入口类。它继承自BaseStatusBar,负责创建和绑定状态栏的视图。
定制状态栏,主要做三件事:
- 修改布局:找到
status_bar.xml,添加或删除控件。 - 修改数据源:比如添加一个显示电量的自定义View,需要注册对应的广播或回调。
- 修改交互逻辑:比如点击状态栏某个区域,弹出空调控制面板。
举个例子,我想在状态栏右侧加一个「空调快捷按钮」。我会这么做:
// 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>
这个面板,我会通过WindowManager以TYPE_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的启动流程搞清楚了,剩下的就是布局和逻辑的增删改查。嗯,多动手试试,很快就能上手。