车载系统定制:SystemUI定制、Launcher开发、系统服务扩展、权限策略定制

说实话,车载系统定制这块,是Android Automotive开发中最有挑战性、也最有意思的部分。你想想看,手机上的Android,Google已经帮你把UI、交互、权限都定义好了。但在车上,每个车厂都有自己的设计语言,有自己的交互逻辑。我当年刚接触这个领域时,第一反应就是——这哪是定制,这简直是重写。

但别怕。今天我就把这四个核心模块拆开揉碎了讲给你听。每个模块我都会结合我踩过的坑、走过的弯路,让你少花点时间在试错上。

SystemUI定制:不只是换个皮肤

SystemUI是什么?说白了,就是状态栏、导航栏、锁屏、通知面板这些系统级UI。在手机上,这些组件基本是固定的。但在车上,情况完全不同。

举个例子。车载状态栏需要显示什么?车速、续航、空调温度、驾驶模式。这些信息手机上有吗?没有。所以你得自己加。

我习惯的做法是,先搞清楚车厂想要哪些信息,然后去SystemUI的源码里找到对应的布局文件。SystemUI的布局主要在packages/SystemUI/res/layout/目录下。比如状态栏,核心文件是status_bar.xml

核心思路:不要直接改系统源码,而是通过扩展点来定制。SystemUI提供了很多扩展接口,比如StatusBarStateControllerNotificationListener。用这些接口,你的代码和系统代码解耦,升级时也省心。

我曾经在一个项目中,车厂要求状态栏显示当前驾驶模式(经济、运动、雪地)。我一开始直接改了status_bar.xml,加了个TextView。结果系统一升级,我的修改全没了。后来我改用StatusBarStateController监听状态变化,再通过addCallback更新UI。嗯,这才是正确姿势。

定制SystemUI时,有几个关键点要注意:

  • 布局适配:车载屏幕比例多样,有16:9的,有21:9的,还有异形屏。建议用ConstraintLayout,别用绝对布局。
  • 交互方式:车上没有触摸屏?那就要考虑旋钮、语音、方向盘按键。SystemUI的焦点管理要重新设计。
  • 性能:车载硬件通常比手机差。SystemUI里的动画、模糊效果,能省则省。我见过一个项目,因为状态栏动画太卡,被客户直接打回。

Launcher开发:桌面就是你的门面

Launcher,也就是桌面。在手机上,Launcher就是放应用图标的地方。但在车上,Launcher是用户和车辆交互的第一入口。

车载Launcher和手机Launcher最大的区别是什么?

手机Launcher是应用启动器。车载Launcher是功能聚合器。用户上车,第一眼看到的不应该是应用图标,而应该是导航、音乐、空调这些核心功能。说白了,车载Launcher更像是一个仪表盘+控制中心的合体。

我开发车载Launcher时,一般会分三层:

  1. 底层:继承LauncherActivity或自己实现Activity,管理桌面生命周期。
  2. 中间层:实现LauncherAppState,管理应用列表、快捷方式、Widget。
  3. 上层:UI层,用Fragment或自定义View展示卡片、快捷入口。

这里有个坑。车载Launcher不能像手机那样,让用户随意拖拽图标。为什么?因为驾驶安全。用户应该在最短时间内找到并启动功能。所以,车载Launcher的布局通常是固定的,或者只允许有限的自定义。

我的建议:车载Launcher的核心是「快」。从点击到功能启动,不要超过2秒。如果做不到,就用预加载。我做过一个项目,导航卡片在Launcher启动时就预加载了地图数据,用户点击导航时几乎秒开。

代码层面,Launcher开发的核心是LauncherApps API。这个API可以查询已安装应用、监听应用变化、启动应用。示例代码如下:

// 获取LauncherApps实例
LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);

// 查询所有应用
List<LauncherActivityInfo> apps = launcherApps.getActivityList(null, userHandle);

// 监听应用变化
launcherApps.registerCallback(new LauncherApps.Callback() {
    @Override
    public void onPackageRemoved(String packageName, UserHandle user) {
        // 移除桌面图标
    }

    @Override
    public void onPackageAdded(String packageName, UserHandle user) {
        // 添加桌面图标
    }

    @Override
    public void onPackageChanged(String packageName, UserHandle user) {
        // 更新桌面图标
    }
});

嗯,代码看起来简单,但实际开发中,你要处理的事情远不止这些。比如,车载Launcher要不要支持分屏?要不要支持悬浮窗?这些都要和车厂反复沟通。

系统服务扩展:给系统加装「外挂」

系统服务,是Android系统的核心。比如WindowManagerService管理窗口,PackageManagerService管理应用。在车载场景下,你需要扩展这些服务,或者干脆自己写一个系统服务。

为什么要扩展系统服务?

举个例子。车厂想要一个「驾驶模式切换」的功能。当用户切换到运动模式时,系统要自动调整UI风格、限制某些应用、改变音量策略。这些逻辑放在哪里?放在App里?不行,因为App可能被杀死。放在SystemUI里?也不行,因为SystemUI只负责UI。正确的做法是,写一个系统服务,常驻内存,统一管理这些逻辑。

我写过不少系统服务,总结下来,步骤就这几步:

  1. 定义AIDL接口:服务对外暴露的方法,写在AIDL文件里。
  2. 实现服务类:继承SystemService,实现AIDL接口。
  3. 注册服务:在SystemServer.java中启动服务。
  4. 添加权限:在AndroidManifest.xml中声明服务权限。

我曾经在项目中扩展过AudioService,目的是实现「导航音压低媒体音」的功能。原生的AudioService不支持这种策略,所以我加了一个NavigationAudioController,监听导航状态,动态调整音频焦点。嗯,这个功能后来被车厂夸了好几次。

注意:扩展系统服务有风险。一个bug可能导致整个系统崩溃。我建议你在开发时,先用log把每一步都记录下来。出问题时,看日志比猜原因快得多。

权限策略定制:安全与便利的平衡

权限,是Android安全模型的核心。在手机上,权限管理已经很成熟了。但在车上,情况更复杂。

为什么?因为车载系统有多个用户:驾驶员、乘客、访客。每个用户的权限不一样。驾驶员可以访问车辆数据(车速、油耗),乘客只能使用娱乐功能,访客可能连WiFi都不能连。

我做过一个权限策略定制的项目,车厂要求:

  • 驾驶员:可以安装应用、访问车辆设置、使用导航。
  • 乘客:只能使用预装应用,不能安装新应用。
  • 访客:只能使用音乐和视频,不能访问任何车辆数据。

这个需求怎么实现?核心是修改PackageManagerServicePermissionManagerService

具体来说,我做了三件事:

  1. 定义角色:在UserManager中定义三个角色,每个角色对应一组权限。
  2. 修改权限检查:在PermissionManagerService中,根据当前用户角色,动态决定是否授予权限。
  3. 限制应用安装:在PackageManagerService中,只有驾驶员角色才能调用installPackage

代码层面,权限检查的核心是checkPermission方法。我重写了这个方法,加入了角色判断:

@Override
public int checkPermission(String permName, String pkgName, int userId) {
    // 获取用户角色
    int role = getUserRole(userId);
    
    // 根据角色决定权限
    if (role == ROLE_DRIVER) {
        // 驾驶员:正常检查
        return super.checkPermission(permName, pkgName, userId);
    } else if (role == ROLE_PASSENGER) {
        // 乘客:只允许基本权限
        if (isBasicPermission(permName)) {
            return PackageManager.PERMISSION_GRANTED;
        } else {
            return PackageManager.PERMISSION_DENIED;
        }
    } else {
        // 访客:几乎无权限
        return PackageManager.PERMISSION_DENIED;
    }
}

当然,这只是简化版。实际项目中,你还要考虑权限的继承、撤销、过期等问题。我建议你先把权限策略文档写清楚,再动手写代码。不然,改来改去,很容易出bug。

知识体系总览

说了这么多,你可能有点晕。没关系,我画了一张图,把这四个模块的关系理清楚了。

车载系统定制知识体系 SystemUI定制 Launcher开发 系统服务扩展 权限策略定制 状态栏 / 导航栏 锁屏 / 通知面板 扩展点 / 接口 卡片式布局 预加载 / 秒开 LauncherApps API AIDL接口定义 SystemService实现 注册 / 权限声明 角色定义 动态权限检查 安装限制 核心目标:安全、稳定、快速、可扩展

这张图把四个模块的关系和子项都列出来了。你可以看到,每个模块都有自己的核心任务,但它们又相互关联。比如,权限策略会影响Launcher能显示哪些应用,系统服务会影响SystemUI的行为。

好了,这一章的内容就到这里。车载系统定制是个大话题,每个模块都能单独写一本书。但掌握了这四个核心方向,你就抓住了车载定制的命脉。剩下的,就是在项目中慢慢积累了。