系统应用定制:Launcher 定制、SystemUI 修改、设置应用扩展
说实话,车载系统里最让用户有感知的,就是这几个系统应用。Launcher 好不好用、SystemUI 顺不顺手、设置菜单合不合理,直接决定了用户对整台车的评价。我当年刚入行时,以为这些应用就是改改布局、换换图标,结果第一个项目就被狠狠教育了一顿——嗯,这里面的坑,比想象中深得多。
Launcher 定制:不只是换个壁纸
Launcher 是车载系统的门面。用户一上车,第一眼看到的就是它。但车载 Launcher 和手机 Launcher 完全是两码事。
核心差异在哪?
- 交互方式:手机靠触控,车载靠旋钮、触摸板、语音。焦点移动逻辑要重写。
- 安全限制:行驶中不能看视频、不能操作复杂菜单。Launcher 要感知车辆状态。
- 多窗口:车载经常要分屏显示,导航+音乐+空调同时出现。
我在项目中遇到过最典型的场景:客户要求 Launcher 首页显示导航卡片、音乐卡片、天气卡片,还要支持拖拽排序。听起来简单?但车载的焦点导航和手机完全不同。手机靠手指点哪是哪,车载要用物理旋钮一圈一圈转过去。
关键点:车载 Launcher 的焦点管理,必须基于 RecyclerView 或 ListView 的 focusSearch() 重写。不要指望系统默认的焦点逻辑能搞定。
举个例子,我们当时要实现一个卡片式 Launcher,每张卡片是一个 CardView,里面嵌套了 TextView、ImageView。结果焦点经常跳到卡片内部的子 View 上,用户旋钮一转,焦点莫名其妙消失了。
// 重写焦点搜索,确保焦点只在卡片级别移动
@Override
public View focusSearch(View focused, int direction) {
// 如果焦点在卡片内部,先找到所属的卡片
View cardView = findCardParent(focused);
if (cardView != null) {
// 基于卡片位置计算下一个焦点
int nextPosition = calculateNextPosition(cardView, direction);
return getChildAt(nextPosition);
}
return super.focusSearch(focused, direction);
}
另外,车载 Launcher 还有一个隐藏需求——冷启动速度。用户点火后,Launcher 必须在 2 秒内显示出来。我见过一个项目,Launcher 启动时去加载网络图片、解析 JSON,结果黑屏了 5 秒。后来改成预加载 + 本地缓存,才勉强达标。
我的建议:Launcher 的首页数据,用 SharedPreferences 或 Room 本地持久化。网络请求异步执行,不要阻塞 UI 线程。启动时先显示骨架屏,数据到了再刷新。
SystemUI 修改:状态栏、导航栏、通知中心
SystemUI 是 Android 系统里最复杂的应用之一。它管理着状态栏、导航栏、锁屏、通知面板、音量面板等等。车载场景下,SystemUI 的修改量往往比 Launcher 还大。
常见的定制需求:
- 状态栏:显示车速、油量、温度、信号强度。图标位置和大小要重新排。
- 导航栏:去掉返回键,增加 Home 键和自定义快捷键。
- 通知中心:只显示高优先级通知,行驶中禁止展开。
- 音量面板:增加媒体、导航、电话、语音助手的独立音量控制。
我记得有一次,客户要求在状态栏右侧显示一个「驾驶模式」图标,点击后弹出模式选择菜单。听起来简单?但 SystemUI 的架构是高度模块化的,状态栏的图标由 StatusBarIconController 管理,点击事件要走 StatusBar 的 onClick 分发。
我当时踩了一个坑:直接在 StatusBar 里加了一个 ImageView,结果系统 OTA 升级后,Google 改了 SystemUI 的布局文件,我的代码全废了。
避坑指南:修改 SystemUI 时,尽量用 CollationManager 或 DynamicSystemUI 的方式注入自定义 View,不要直接改 status_bar.xml。我曾经因为直接改布局文件,导致后续三个版本都要手动合并代码。
来看一个实际例子:我们要在状态栏添加一个「蓝牙连接状态」图标。正确的做法是注册一个 BroadcastReceiver,监听 BluetoothDevice.ACTION_ACL_CONNECTED 和 ACTION_ACL_DISCONNECTED,然后通过 StatusBarIconController 动态添加或移除图标。
// 动态添加状态栏图标
public class BluetoothStatusController {
private Context mContext;
private StatusBarIconController mIconController;
private String mSlot = "bluetooth_status";
public void addBluetoothIcon() {
StatusBarIcon icon = new StatusBarIcon(
mContext.getPackageName(),
mContext,
R.drawable.ic_bluetooth_connected,
0, 0, 0
);
mIconController.setIcon(mSlot, icon);
}
public void removeBluetoothIcon() {
mIconController.removeIcon(mSlot);
}
}
另外,车载 SystemUI 还有一个特殊要求——安全模式。车辆行驶时,导航栏的某些按钮要禁用,通知面板不能下拉。这些逻辑要写在 KeyguardViewMediator 或 StatusBarWindowManager 里,判断车辆速度或档位状态。
核心思路:SystemUI 的修改,本质上是「监听车辆状态 + 动态更新 UI」。不要写死任何逻辑,所有状态都通过 CarService 或 Vehicle HAL 获取。
设置应用扩展:从简单列表到复杂配置
设置应用(Settings)是 Android 系统里代码量最大的应用之一。车载场景下,设置应用要增加很多车相关的配置项,比如:
- 车辆设置:灯光、门锁、空调、座椅
- 驾驶辅助:车道保持、自适应巡航
- 账户管理:登录车厂账号、同步个人设置
- 系统更新:OTA 升级、应用管理
扩展设置应用,最直接的方式是添加新的 Fragment 和 PreferenceScreen。但这里有一个架构问题:Settings 应用本身是 Google 维护的,每次 AOSP 更新都会有大改动。如果你直接改 Settings 的源码,后续合并代码会非常痛苦。
我个人的习惯是:用插件化或模块化的方式扩展 Settings。具体来说,就是在 Settings 里预留一个「车辆设置」入口,点击后跳转到我们自己的 VehicleSettingsActivity,这个 Activity 完全独立于 Settings 应用,不依赖 Settings 的代码。
// 在 Settings 的 AndroidManifest.xml 中注册
<activity
android:name=".VehicleSettingsActivity"
android:exported="true"
android:label="@string/vehicle_settings">
<intent-filter>
<action android:name="android.settings.VEHICLE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
// 在 Settings 的 DashboardFragment 中添加入口
Preference vehiclePref = new Preference(context);
vehiclePref.setKey("vehicle_settings");
vehiclePref.setTitle(R.string.vehicle_settings);
vehiclePref.setIntent(new Intent("android.settings.VEHICLE_SETTINGS"));
getPreferenceScreen().addPreference(vehiclePref);
这样做的好处很明显:Google 更新 Settings 时,我们只需要保证入口代码能编译通过,车辆设置部分的代码完全不受影响。
另外,车载设置应用还有一个特殊需求——多用户配置。一辆车可能被多个家庭成员使用,每个人的座椅位置、后视镜角度、空调偏好都不一样。设置应用要支持用户切换时,自动加载对应的配置。
我的经验:多用户配置用 UserManager 的 getUserProfiles() 获取用户列表,然后用 ContentProvider 存储每个用户的设置项。切换用户时,通过 CarService 下发配置到对应的 ECU。
知识体系总览
下面这张图,是我自己总结的系统应用定制核心逻辑。你看一眼,基本就知道该从哪里下手了。
你看,这三个应用虽然功能不同,但底层都依赖 CarService 和 Vehicle HAL。说白了,系统应用定制就是「UI 层 + 车辆状态层」的组合。UI 层负责展示和交互,车辆状态层负责获取车速、档位、灯光等信息。
最后说一句:做车载系统应用,千万别想着一次性搞定所有需求。我见过太多团队,一开始就想做一个「完美」的 Launcher,结果开发周期拖了半年,最后被客户骂得狗血淋头。我的建议是:先跑通核心流程,再逐步迭代。Launcher 先保证能显示、能点击、能响应车辆状态;SystemUI 先保证状态栏和导航栏能用;设置应用先保证车辆设置能保存和加载。其他的,都是锦上添花。