16、多用户与账户系统:驾驶员与乘客模式、用户切换、账户同步机制

用户场景:车不再只是“你的”

我记得第一次接触 Automotive 多用户系统时,心里想的是:“这不就是 Android 平板上那个多用户功能吗?” 结果一深入才发现,完全不是一回事。

在车上,用户场景复杂得多。你想想看,一辆车可能早上是爸爸开着上班,中午是妈妈接孩子,晚上又变成全家出游。每个人上车时,都希望座椅、后视镜、音乐偏好、导航历史是自己的那套。更别提还有代驾、洗车工这种临时用户。

所以,Android Automotive 的多用户系统,核心要解决三个问题:

  • 身份识别:谁在开车?谁在副驾?
  • 数据隔离:你的导航记录,别让副驾看到。
  • 账户同步:换个车,你的 Spotify 歌单还在。

用户类型:驾驶员、乘客、访客

Android Automotive 把用户分成了几类。我习惯用“角色”来理解它们:

用户类型 典型场景 数据持久化
驾驶员(Driver) 车主本人驾驶 全持久化,含账户同步
乘客(Passenger) 副驾或后排乘客 临时数据,下车可清除
访客(Guest) 代驾、洗车、试驾 不保存任何个人数据
系统用户(System) 后台服务、系统UI 系统级数据

嗯,这里要注意:驾驶员和乘客在系统层面其实是同一个 Android 用户 ID,但通过 UserManagerisPrimary()isGuest() 来区分角色。我曾在项目中踩过一个坑——乘客模式下,某些后台服务误以为自己是主用户,结果把导航数据写到了驾驶员账户里。

核心原则:驾驶员永远是“主用户”(Primary User),乘客是“副用户”(Secondary User)。系统 UI 会根据当前用户角色,动态调整可操作的功能。

用户切换:不是简单的“点一下头像”

在手机上切换用户,无非是锁屏界面点一下。但在车上,切换用户涉及大量硬件状态迁移。我个人建议把切换流程拆成三个阶段:

  1. 冻结阶段:暂停当前用户的所有前台应用,保存座椅、空调、音量等硬件状态。
  2. 切换阶段:加载目标用户的配置文件,恢复硬件状态。
  3. 激活阶段:启动目标用户的 Launcher 和关键应用(如导航、媒体)。

我曾经在项目里遇到一个奇葩问题:切换用户时,蓝牙连接没有及时断开重连。结果新用户上车,手机连的还是旧用户的蓝牙,音乐直接播到了旧用户的手机上。解决方案是在切换阶段强制断开所有非系统蓝牙连接,等新用户解锁后再重新扫描。

避坑指南:用户切换时,务必把 CarOccupantZone 的配置一起切换。否则可能出现“驾驶员座椅调到乘客位置”的尴尬。

账户同步机制:跨车、跨设备

账户同步,说白了就是让用户的 Google 账户(或车厂自有账户)把数据带到云端,再同步到其他设备或车辆。Android Automotive 用的是标准的 AccountManager + SyncAdapter 框架,但有几个车机特有的坑:

  • 网络不稳定:车机经常处于地下车库或信号弱区,同步必须支持断点续传。
  • 多账户冲突:同一辆车可能绑定多个 Google 账户(比如家庭共享),同步时要注意数据归属。
  • 隐私限制:乘客账户默认不允许同步到云端,除非用户明确授权。

我建议的同步策略是这样的:

// 伪代码:车机账户同步策略
if (user.isDriver()) {
    // 驾驶员:全量同步,包括导航历史、音乐偏好、联系人
    syncAdapter.syncAll(account);
} else if (user.isPassenger()) {
    // 乘客:仅同步媒体播放列表和音量设置
    syncAdapter.syncPartial(account, SYNC_MEDIA_ONLY);
} else {
    // 访客:不同步任何数据
    syncAdapter.cancelSync(account);
}

你可能会问:“为什么乘客不同步导航历史?” 嗯,隐私问题。副驾可能只是临时搭车,你不想让车记住他去了哪里。Android Automotive 在框架层就限制了乘客账户的同步范围,开发者不需要自己实现。

SVG 流程图:多用户切换与同步核心逻辑

多用户切换与账户同步流程 用户上车 / 车辆启动 识别用户角色(Driver/Passenger/Guest) 是驾驶员? 全量同步 + 硬件恢复 仅媒体同步 / 不同步 用户模式激活完成

代码实战:监听用户切换

在实际开发中,我们经常需要监听用户切换事件,以便更新 UI 或重新加载数据。我习惯在 CarService 中注册一个 UserSwitchObserver

// 注册用户切换监听
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
um.addUserSwitchListener(new UserManager.UserSwitchListener() {
    @Override
    public void onUserSwitched(int newUserId) {
        // 新用户已激活,重新加载用户偏好
        Log.d("AutoUser", "用户切换到: " + newUserId);
        // 注意:此时硬件状态可能还未完全恢复
        // 建议延迟 500ms 再执行硬件相关操作
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            restoreHardwareState(newUserId);
        }, 500);
    }
});
注意onUserSwitched 回调是在 binder 线程中触发的,不要在这里直接操作 UI。另外,我遇到过某些车机在切换用户时,CarOccupantZone 的配置还没更新,导致读取到的座椅位置还是旧用户的。建议在回调中加一个 CarOccupantZone 的重新查询。

账户同步的“最后一公里”

账户同步的难点往往不在技术,而在体验。举个例子:用户换了新车,登录 Google 账户后,导航历史需要多久才能同步下来?如果同步太慢,用户会以为数据丢了。

我个人的做法是:在用户登录成功后,立即触发一次强制同步,并且在前台显示一个轻量级的进度条。同时,把最常用的数据(比如最近 10 个导航目的地)放在同步队列的最前面。这样用户一登录,马上就能看到自己常用的地址,体验会好很多。

另外,别忘了处理同步冲突。比如用户在车上删除了一个收藏地址,但手机端还没同步。这时候车机应该以本地修改为准,还是以云端为准?我建议采用“最后修改时间戳”策略,谁晚听谁的。如果时间戳相同,则保留本地版本。

小技巧:在 SyncAdapteronPerformSync() 中,可以通过 Bundle extras 传递一个 SYNC_PRIORITY 参数,让高优先级数据先同步。代码示例:
extras.putInt("SYNC_PRIORITY", 1); // 1 为最高优先级

总结一下

多用户与账户系统,说白了就是让车认识“谁在开”,并且把每个人的数据伺候好。驾驶员和乘客的隔离是安全底线,账户同步是体验天花板。我见过不少车厂在用户切换时只做了 UI 层面的“换皮肤”,底层数据还是一锅粥,结果就是各种隐私泄露和配置错乱。

嗯,最后说一句:如果你在开发中遇到用户切换后导航卡死的问题,先检查一下 UserManagergetUserSwitchability() 返回值。有些系统限制了后台切换,需要你在 AndroidManifest 中声明 MANAGE_USERS 权限。


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