3. Android Auto 核心概念:模板化设计、App与Car的通信机制、安全驾驶约束

好,咱们今天聊点硬核的。Android Auto 开发,说白了就是一套「戴着镣铐跳舞」的活儿。你想想看,车在高速上跑着,驾驶员分心一秒钟都可能出事。所以 Google 从一开始就把「安全」刻在了这套系统的骨子里。

我个人习惯把 Android Auto 的核心拆成三块来理解:模板化设计通信机制安全约束。这三块搞明白了,你写出来的 App 才能在车上跑得稳、跑得安全。

3.1 模板化设计:为什么你不能随心所欲画界面?

做过手机 App 开发的兄弟都知道,咱们想怎么画 UI 就怎么画。但在车上不行。Android Auto 强制你使用它提供的模板(Template)。

为什么会这样?

我刚开始接触这个的时候也挺纳闷的。后来在项目里踩了一次坑才明白——车载屏幕的交互逻辑跟手机完全不同。驾驶员只能用余光扫一眼屏幕,手指头也不能精准点按。所以 Google 把界面抽象成了几个固定的模板,每个模板都经过严格的可用性测试。

常用的模板有这么几种:

模板名称 用途 我常用的场景
MessageTemplate 显示消息列表,支持回复 聊天 App 的消息预览
MediaTemplate 媒体浏览,网格或列表 音乐专辑、播客列表
PlaceListMapTemplate 地点列表 + 地图预览 导航 App 的搜索结果
PaneTemplate 分栏布局,左右或上下 设置页面、详情页

每个模板都有一套固定的布局规则。比如 MessageTemplate,它规定标题区只能放一行文字,操作按钮最多三个。你不能自己加个花里胡哨的动画进去——嗯,想加也加不了,SDK 就不让你那么干。

核心原则:你写的不是 UI 代码,你写的是「数据 + 行为」。模板负责渲染,你负责填充。

来看一段实际代码。这是用 MessageTemplate 展示一条消息的样子:

// 构建一条消息
CarMessage message = new CarMessage.Builder("msg_001")
    .setTitle("张三")
    .setText("晚上一起吃饭?")
    .setCreationTime(System.currentTimeMillis())
    .setRead(false)
    .build();

// 构建模板
MessageTemplate template = new MessageTemplate.Builder(message)
    .setTitle("新消息")
    .addAction(new Action.Builder(Action.TYPE_REPLY)
        .setTitle("回复")
        .build())
    .build();

// 提交给车载屏幕
screenManager.pushTemplate(template, ScreenManager.TOP);

你看,代码里完全没有布局文件,没有 XML。你只需要告诉模板「我要显示什么内容、提供什么操作」,剩下的交给系统。

我的小技巧:开发阶段可以在模拟器里多切换几种模板试试。有时候 MediaTemplate 的网格模式比列表模式更适合你的内容。别问我怎么知道的——有一次我把播客列表硬塞进网格模板,结果标题全被截断了,用户根本看不清。

3.2 App 与 Car 的通信机制:谁在跟谁说话?

好,界面搞定了。那你的 App 怎么跟车机「对话」呢?

这里要引入一个关键角色——CarAppService。它是你的 App 在车机端的「代言人」。手机上的 App 和车机上的界面,其实是两个进程。它们通过 Binder 通信。

我画了一张图,帮你理清这个关系:

手机端(Phone) 你的 App(主进程) ↓ 继承 CarAppService CarAppService(服务进程) Binder 通信 车机端(Head Unit) CarScreen(界面渲染) ↓ 管理模板栈 ScreenManager(导航控制) 通信流程 ① App 启动 → 系统自动绑定 CarAppService ② CarAppService 创建 ScreenManager → 推送模板到车机 ③ 用户操作(点击/滑动)→ 回调到 CarAppService → 更新数据

这张图你看懂了吗?说白了就是:你的 App 在手机后台跑着,CarAppService 负责跟车机「搭桥」。车机上的界面其实是一个「远程窗口」,它显示的内容完全由你的模板数据决定。

这里有个关键点:CarAppService 是一个 Service。它不是在 Activity 里启动的,而是由 Android Auto 系统在手机连接到车机时自动绑定。所以你的 App 不需要有任何界面在手机上显示——嗯,很多音乐 App 就是这么干的,手机黑屏,车机亮着。

注意:CarAppService 运行在单独的进程中。这意味着你不能直接访问主进程的 Application 或 Activity 里的数据。跨进程通信需要用 Binder 或者 ContentProvider。我曾经在这里栽过跟头——直接传了一个 Activity 引用过去,结果崩溃得一塌糊涂。

3.3 安全驾驶约束:哪些事绝对不能做?

终于聊到重点了。Android Auto 的安全约束,说白了就是一句话:别让驾驶员分心

Google 制定了一套严格的规则,违反的话你的 App 根本过不了审核。我列几个最关键的:

  • 禁止文本输入:驾驶员不能打字。所有输入必须通过语音(Google Assistant)或预设的快捷回复。
  • 限制列表滚动:列表项不能太多,而且滚动时必须有明显的视觉反馈。我建议列表项控制在 5-7 条以内。
  • 禁止视频播放:行驶中不能播放视频。只有停车状态下才能显示视频内容。
  • 操作按钮大小:所有可点击区域必须 ≥ 48dp。太小了驾驶员点不准。
  • 阅读时间限制:任何文字内容必须在 5 秒内能被扫读完。长篇大论?不行。

我曾经做过一个新闻 App,想在车上显示长文章。结果审核直接被拒了。后来改成只显示标题和摘要,点击后语音朗读全文——这才过审。

避坑指南:如果你在 App 里用了 WebView,那基本可以放弃了。Android Auto 不允许任何 WebView 内容。所有界面必须用原生模板。

还有一个容易被忽略的点——音频焦点。你的 App 播放音乐时,如果导航 App 开始播报路况,你的音乐必须自动降低音量(俗称「 duck 」)。这是通过 AudioManager 的音频焦点机制实现的。

// 请求音频焦点
AudioAttributes attrs = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_MEDIA)
    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
    .build();

AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
    .setAudioAttributes(attrs)
    .setOnAudioFocusChangeListener(focusChange -> {
        if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
            // 降低音量,比如降到 30%
            mediaPlayer.setVolume(0.3f, 0.3f);
        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
            // 恢复音量
            mediaPlayer.setVolume(1.0f, 1.0f);
        }
    })
    .build();

audioManager.requestAudioFocus(focusRequest);

这段代码看起来简单,但实际调试时很容易出问题。我建议你在模拟器里同时跑两个 App(比如一个音乐 App 和一个导航 App),手动触发音频焦点变化,看看你的 App 反应对不对。

我的经验:安全约束不是限制你,而是保护你。你想想看,如果用户在开车时因为你的 App 设计不当出了事故,那责任可就大了。所以,老老实实遵守规则,对大家都好。

最后总结一下今天的内容。模板化设计让你不用操心布局细节,通信机制让你理解 App 和车机怎么协作,安全约束则是你必须要守的底线。这三块搞明白了,Android Auto 开发的大门就算正式向你敞开了。

好,今天就聊到这儿。下一章咱们会深入讲 CarAppService 的生命周期管理,到时候我会分享一个我在实际项目中遇到的「服务被系统杀死」的坑——嗯,挺有意思的。


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