第7章 Android Auto通讯应用开发:消息模板、语音输入处理、通话状态管理

通讯应用在车载场景下,说实话,是最刚需的一类。你想想看,开车的时候来消息了,总不能低头看手机吧?Android Auto 给了我们一套完整的模板机制,让消息的展示、回复、语音输入、通话管理都能在安全的前提下完成。这一章,我就带你把这些核心能力一一拆解。

7.1 消息模板:让通知更“车载化”

Android Auto 的消息模板,说白了就是一套专门为驾驶场景设计的 UI 组件。它跟手机上的通知不一样,不能有复杂的布局,也不能有需要精确点击的小按钮。

我个人习惯把消息模板分为三类:

  • 简单通知:只显示发件人和消息内容,用户只能“已读”或“忽略”
  • 可回复通知:除了显示消息,还提供预设回复或语音输入入口
  • 群组通知:多条消息聚合显示,避免刷屏

来看一个最简单的消息模板实现:

// 构建一个可回复的消息通知
val message = MessagingStyle.Message(
    "今晚七点老地方见",
    System.currentTimeMillis(),
    "张三"
)

val style = MessagingStyle(
    getPerson("当前用户"),
    "张三"
).addMessage(message)

val intent = PendingIntent.getActivity(this, 0, 
    Intent(this, MainActivity::class.java), 
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)

val notification = NotificationCompat.Builder(this, CHANNEL_ID)
    .setStyle(style)
    .setContentIntent(intent)
    .setSmallIcon(R.drawable.ic_message)
    .setCategory(NotificationCompat.CATEGORY_MESSAGE)
    .build()

// 别忘了设置 CarAppExtender
val carExtender = CarAppExtender.Builder()
    .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
    .build()
notification.extend(carExtender)

嗯,这里要注意一个坑。我在项目中遇到过,setContentIntent 如果不加 FLAG_IMMUTABLE,在 Android 12 以上会直接崩溃。这个 flag 是 Google 后来强制要求的,很多老代码都没加。

核心要点:消息模板的 MessagingStyle 必须包含至少一条消息,否则 Android Auto 不会显示。另外,Person 对象建议用 Uri 指定头像,这样车载屏幕上能显示联系人头像,体验更好。

7.2 语音输入处理:让用户“动口不动手”

车载场景下,语音输入是王道。Android Auto 提供了两种语音输入方式:一种是系统自带的语音识别,另一种是自定义的 VoiceInteractionService

我建议大部分开发者直接用系统提供的 RemoteInput,简单可靠:

// 创建语音回复的 RemoteInput
val remoteInput = RemoteInput.Builder("key_voice_reply")
    .setLabel("说出你的回复")
    .setChoices(arrayOf("好的", "收到", "稍后回复"))
    .build()

// 构建回复 Action
val replyAction = NotificationCompat.Action.Builder(
    R.drawable.ic_reply,
    "回复",
    replyPendingIntent
).addRemoteInput(remoteInput)
 .build()

// 添加到通知
notificationBuilder.addAction(replyAction)

为什么会这样设计?因为 RemoteInput 会自动唤起车载的语音识别界面,用户说完话后,结果会通过 Intent 回传给你。你不需要自己处理麦克风权限、音频流这些麻烦事。

我曾经踩过一个坑:setChoices 里的预设回复,在 Android Auto 上最多只能显示 3 个,多了会被截断。而且这些预设回复要简短,最好不超过 5 个字,否则屏幕上一行显示不全。

小技巧:语音输入的回传结果,建议做一次简单的文本清洗。比如去掉首尾空格、把“嗯”“啊”这类语气词过滤掉。我在项目里就遇到过用户说“嗯...好的”,结果回复出去变成了“嗯...好的”,看着特别不专业。

7.3 通话状态管理:别让电话打断驾驶

通话状态管理,说白了就是让车载系统知道:现在有没有电话进来?用户在通话中吗?通话结束后要恢复什么状态?

Android Auto 通过 TelecomManagerConnectionService 来管理通话。我一般会维护一个通话状态机,包含以下状态:

状态 含义 触发动作
IDLE 空闲,无通话 恢复媒体播放
RINGING 来电响铃 暂停媒体,显示来电界面
ACTIVE 通话中 保持媒体暂停,显示通话时长
HOLD 通话保持 可恢复媒体播放(低音量)
DISCONNECTED 通话结束 恢复媒体播放,清理通话界面

来看一个通话状态切换的代码片段:

class CallStateManager(private val context: Context) {
    
    private var currentState: CallState = CallState.IDLE
    
    fun onCallStateChanged(state: Int, phoneNumber: String?) {
        when (state) {
            TelecomManager.EXTRA_STATE_RINGING -> {
                currentState = CallState.RINGING
                // 暂停媒体播放
                pauseMedia()
                // 显示来电通知
                showIncomingCallNotification(phoneNumber)
            }
            TelecomManager.EXTRA_STATE_OFFHOOK -> {
                currentState = CallState.ACTIVE
                // 通话中,保持媒体暂停
                keepMediaPaused()
            }
            TelecomManager.EXTRA_STATE_IDLE -> {
                val previousState = currentState
                currentState = CallState.IDLE
                // 通话结束,恢复媒体
                if (previousState == CallState.ACTIVE || 
                    previousState == CallState.HOLD) {
                    resumeMedia()
                }
                // 清理通知
                clearCallNotification()
            }
        }
    }
}

你想想看,如果通话结束后没有恢复媒体播放,用户还得手动点一下播放按钮,这在开车时多危险。所以状态管理一定要做完整。

注意:在 Android 10 及以上版本,读取通话状态需要 READ_PHONE_STATE 权限,并且要在运行时动态申请。另外,如果你的应用不是默认的电话应用,TelecomManager 的部分接口可能无法正常工作。我建议用 PhoneStateListener 作为备选方案。

7.4 知识体系总览

这一章的内容比较多,我画了一张图帮你梳理一下整体逻辑:

Android Auto 通讯应用核心模块 消息模板 MessagingStyle 语音输入处理 RemoteInput 通话状态管理 TelecomManager 子类型 • 简单通知(已读/忽略) • 可回复通知(预设回复) • 群组通知(聚合显示) 处理流程 • 唤起语音识别界面 • 用户语音输入 • 结果回传 & 文本清洗 状态机 • IDLE → RINGING → ACTIVE • HOLD(通话保持) • DISCONNECTED(恢复媒体) 核心设计原则 1. 安全优先:所有交互必须支持免提操作,减少驾驶员分心 2. 状态一致:通话状态与媒体播放状态联动,避免冲突 3. 简洁高效:模板和回复内容要精简,适配车载屏幕尺寸

这张图把三大模块的关系和子内容都串起来了。消息模板负责展示,语音输入负责交互,通话状态管理负责整个生命周期的协调。三者缺一不可。

7.5 实战中的避坑指南

最后,我分享几个实战中遇到的坑,希望能帮你少走弯路:

  • 消息通知的优先级:Android Auto 上,IMPORTANCE_HIGH 的消息会弹出横幅通知,IMPORTANCE_DEFAULT 只会出现在通知列表里。我建议重要联系人的消息用高优先级,群消息用默认优先级。
  • 语音输入的延迟:车载语音识别通常有 1-2 秒的延迟。用户说完话后,不要立即关闭语音界面,给系统一点处理时间。我一般会设置一个 3 秒的超时等待。
  • 通话状态的边界情况:比如用户正在通话中,又来了第二个电话(呼叫等待)。这时候状态机会进入 HOLD 状态,原来的通话保持,新通话激活。这个逻辑一定要处理好,否则用户会莫名其妙地断线。
  • 测试环境:Android Auto 的通讯功能在模拟器上不太好测。我建议用真机 + 车载模拟器(Desktop Head Unit)来测试,或者直接上车实测。有些问题只有实际跑起来才能发现。

嗯,这一章的内容就到这里。消息模板、语音输入、通话状态管理,这三个能力是车载通讯应用的基石。把这三块吃透了,你的应用在车上跑起来就会很顺畅。


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