14、通知与消息系统:车载通知规范、CarNotification、Heads-up通知、消息优先级管理

通知在手机上,说白了就是个“小红点”或者下拉栏里的一条消息。但在车上,通知直接关系到安全。你想想看,驾驶员正在高速上变道,突然一个全屏弹窗跳出来……那画面太美我不敢看。

所以Android Automotive OS对通知做了大量定制。我刚开始接触车载项目时,以为直接把手机上的通知搬过来就行。结果第一次路测就被安全团队怼了——“你这通知会分散驾驶员注意力”。嗯,从那以后我老老实实啃了一遍车载通知规范。

车载通知的核心原则

车载通知和手机通知最大的区别是什么?两个字:安全。具体来说,有这几个硬性要求:

  • 最小化干扰:通知不能遮挡驾驶相关信息(比如导航、车速)。
  • 可操作性:驾驶员必须在3秒内理解通知内容并做出决策。
  • 优先级分层:只有高优先级通知才能弹出Heads-up,其他乖乖待在通知栏里。
  • 语音优先:能语音播报的就不要用文字弹窗。

核心结论:车载通知的设计目标不是“让用户看到”,而是“让用户安全地看到”。

CarNotification:车载通知的专属API

Android Automotive OS提供了一个专门的API——CarNotification。它和手机上的NotificationCompat很像,但多了几个车载特有的参数。

我个人习惯在构建车载通知时,直接使用CarNotification.Builder。来看一个实际例子:

// 构建一个车载通知
CarNotification.Builder builder = new CarNotification.Builder(context, CHANNEL_ID)
    .setContentTitle("充电完成")
    .setContentText("您的车辆已充满电,可以出发了")
    .setSmallIcon(R.drawable.ic_ev_charge_done)
    .setPriority(CarNotification.PRIORITY_HIGH)
    .setCategory(CarNotification.CATEGORY_CHARGING)
    .setHeadsUp(true)  // 允许Heads-up弹出
    .setDuration(5000) // 持续5秒后自动消失
    .addAction(new CarNotification.Action.Builder(
        R.drawable.ic_unplug,
        "停止充电",
        pendingIntent
    ).build());

// 别忘了设置汽车专属的元数据
builder.setCarMetadata(new CarNotification.CarMetadata.Builder()
    .setSupportsVoiceReply(true)
    .setRequiresDriverAttention(false)
    .build());

notificationManager.notify(TAG, NOTIFICATION_ID, builder.build());

这里有个坑,我曾经踩过:setHeadsUp(true)并不保证一定会弹出Heads-up。系统还会根据当前驾驶状态、通知优先级、以及用户设置来决定是否真的弹出。说白了,你只是“申请”弹出,系统才是最终裁判。

Heads-up通知:什么时候该用?

Heads-up通知就是那种从顶部滑下来的半透明横幅。在车上,它比全屏弹窗安全得多,但也不能滥用。

根据Android Automotive的官方建议,只有以下场景才适合使用Heads-up:

  • 安全警告:比如“前方事故,请减速”
  • 车辆状态变化:比如“胎压过低”
  • 导航指令:比如“前方500米右转”
  • 来电:尤其是通过蓝牙连接的来电

其他通知,比如“天气更新”、“应用推荐”,老老实实待在通知栏里就好。你想想看,驾驶员正在找停车位,突然弹出一条“今天下午有雨”……这不是添乱吗?

我的经验:在开发阶段,可以用adb shell dumpsys notification查看当前通知的优先级和Heads-up状态。如果发现你的通知没有按预期弹出,先检查一下优先级是否设置正确。

消息优先级管理:谁该先说话?

车载通知的优先级分为四个等级,从高到低依次是:

优先级 常量 典型场景 是否允许Heads-up
紧急 PRIORITY_CRITICAL 碰撞预警、刹车故障 是(强制弹出)
PRIORITY_HIGH 导航指令、来电 是(需系统批准)
PRIORITY_DEFAULT 充电完成、媒体播放
PRIORITY_LOW 应用更新、订阅提醒

这里要注意的是,PRIORITY_CRITICAL是车载特有的,手机上没有。它可以让通知绕过“免打扰”模式,直接显示在屏幕上。但滥用这个优先级会被系统标记为“恶意应用”,甚至被用户卸载。我曾经见过一个第三方导航应用,把所有通知都设成CRITICAL,结果被车主投诉到应用商店下架了。

通知分组与折叠

在车上,通知栏的空间比手机更宝贵。所以Android Automotive OS默认会对同一应用的通知进行分组。比如你收到了5条微信消息,系统会折叠成一条“微信(5条消息)”。

要实现分组,你需要在构建通知时设置setGroup()

// 同一组通知使用相同的groupKey
String groupKey = "group_wechat";
CarNotification.Builder builder = new CarNotification.Builder(context, CHANNEL_ID)
    .setGroup(groupKey)
    .setGroupSummary(true)  // 这条作为组摘要
    .setContentTitle("微信")
    .setContentText("您有5条未读消息")
    .setSortKey(String.valueOf(System.currentTimeMillis()));

分组后的通知在车载UI上会显示为一个可展开的卡片。驾驶员可以语音命令“展开微信通知”来查看详情,而不需要动手操作。

驾驶模式下的通知行为

当车辆处于行驶状态时,系统会自动进入“驾驶模式”。在这个模式下,通知行为会发生以下变化:

  • 所有Heads-up通知的显示时间缩短到3秒(静止状态下是5秒)
  • 低优先级通知直接被静音,不显示也不震动
  • 通知中的操作按钮会被限制,只保留语音相关的操作
  • 文字内容超过两行会被截断,只显示“查看更多”提示

我记得有一次测试,发现我们的通知在驾驶模式下按钮全部消失了。查了半天,原来是没有设置setSupportsVoiceReply(true)。系统认为这个通知无法通过语音操作,干脆把按钮全砍了。嗯,这个坑大家注意一下。

知识体系总览

下面这张图总结了车载通知系统的核心逻辑和决策流程:

车载通知系统核心逻辑 通知来源 优先级判断 (CRITICAL / HIGH / DEFAULT / LOW) CRITICAL → 强制Heads-up HIGH → 申请Heads-up DEFAULT/LOW → 通知栏 驾驶模式过滤 (时长缩短/静音/截断) 最终展示给驾驶员

从这张图可以看出,一个通知从诞生到展示给驾驶员,经历了优先级判断、Heads-up申请、驾驶模式过滤三道关卡。每一关都可能改变通知的最终形态。

避坑指南

最后分享几个我实际项目中踩过的坑:

  • 不要依赖通知的默认优先级:如果不显式设置优先级,系统会默认为PRIORITY_DEFAULT。这意味着你的通知永远不会弹出Heads-up。我曾经有一个充电提醒功能,用户反馈“为什么插上充电器没反应?”——查了半天,就是忘了设优先级。
  • Heads-up的持续时间要合理:太短用户看不到,太长影响驾驶。我一般设3-5秒,具体看通知内容长度。
  • 语音回复的坑:如果通知支持语音回复,一定要在CarMetadata中设置setSupportsVoiceReply(true)。否则系统会认为这个通知“不可操作”,直接砍掉所有按钮。
  • 测试时模拟驾驶模式:用adb shell settings put global automotive_driving_state 1可以强制进入驾驶模式。我每次提交代码前都会跑一遍这个测试,确保通知在驾驶模式下表现正常。

重要提醒:不要在通知中显示需要驾驶员手动输入的内容,比如验证码、密码等。车载系统严禁任何形式的文本输入操作。如果必须输入,请使用语音或预设选项。

好了,关于车载通知的内容就讲到这里。记住一句话:在车上,通知不是用来“看”的,而是用来“安全地获取信息”的。把握好这个原则,你的车载通知设计就不会跑偏。

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