22、CarPlay与Android Auto:协议适配、应用投射、UI映射

说实话,做车载多媒体开发,你迟早要面对这两个东西——CarPlay和Android Auto。很多刚入行的朋友觉得它们就是「手机投屏」,其实没那么简单。我当年第一次对接CarPlay时,也被它的协议栈绕得晕头转向。

今天我们就来拆解一下。说白了,CarPlay和Android Auto都是把手机的应用和服务,投射到车机屏幕上。但它们的实现方式、协议栈、UI映射逻辑,差别还挺大的。

协议适配:底层通信的差异

先聊协议层。CarPlay用的是苹果自家的iAP2协议(iPod Accessory Protocol 2),走的是USB或无线Wi-Fi。Android Auto则基于AOA协议(Android Open Accessory)和AA协议(Android Auto Protocol)。

我习惯把协议适配分成三层:

  • 传输层:USB HID、Wi-Fi P2P、蓝牙RFCOMM
  • 会话层:身份认证、能力协商、加密通道建立
  • 应用层:音频流、视频流、触控事件、Siri/Google Assistant指令

举个例子,CarPlay的无线连接,用的是Wi-Fi P2P + Bluetooth LE做配对。手机和车机先通过蓝牙交换Wi-Fi凭证,然后建立P2P直连。嗯,这里有个坑——蓝牙配对时的PIN码显示,很多车机做得不够流畅。我曾经在一个项目里,因为蓝牙配对超时,导致用户反复连接失败,最后不得不加了一个「手动输入配对码」的兜底方案。

核心要点:协议适配的关键在于「握手流程」的健壮性。手机和车机的版本组合千差万别,握手失败时要有重试机制和日志输出。

应用投射:谁在渲染,谁在控制?

很多人以为CarPlay和Android Auto是「手机把屏幕镜像到车机」。其实不是。它们用的是远程渲染模式。

手机端负责运行应用、处理逻辑、生成UI描述(不是像素)。车机端负责接收UI描述,然后本地渲染。触控事件则从车机传回手机。

这样做的好处很明显:延迟低、画面流畅、而且车机不需要很强的算力。你想想看,一个2018年的车机,跑CarPlay照样丝滑,就是这个原因。

Android Auto的投射流程大致如下:

  1. 手机启动AA服务,建立与车机的Socket连接
  2. 车机发送屏幕尺寸、DPI、安全区域等参数
  3. 手机根据参数调整UI布局,生成SurfaceFlinger的Buffer
  4. Buffer通过H.264编码,实时传输到车机
  5. 车机解码并显示,同时监听触控事件回传

CarPlay的流程类似,但它用的是H.265编码,而且对延迟要求更苛刻——苹果要求端到端延迟不超过100ms。我在做无线CarPlay时,为了压这几十毫秒,折腾了整整两周的Wi-Fi QoS配置。

个人经验:如果你在调试投射延迟,先检查Wi-Fi的802.11e/WMM是否开启。很多车机默认没开,导致视频流和音频流抢带宽。

UI映射:车机屏幕的适配艺术

UI映射,说白了就是手机应用怎么在车机屏幕上「摆得好看」。这里有两个核心问题:

  • 分辨率适配:车机屏幕从7寸到15寸,分辨率从800x480到1920x1080,甚至还有带鱼屏
  • 交互适配:车机没有多点触控?没有旋钮?只有方向盘按键?

CarPlay的做法是:苹果定义了一套固定的UI模板。开发者只能在这些模板里填充内容。比如音乐应用,只能使用「列表+封面」的布局,不能自定义。这样保证了所有CarPlay应用在任意车机上,体验一致。

Android Auto则灵活一些。它允许开发者使用CarAppServiceCarAppExtender来适配不同屏幕。但灵活也意味着坑多。我记得有一次,一个第三方音乐App在横屏车机上显示正常,换到竖屏车机,底部的播放控制栏直接跑到了屏幕中间——因为开发者没处理安全区域。

避坑指南:我曾经因为忽略了车机的「安全区域」(Safe Area),导致应用的部分UI被系统状态栏遮挡。解决方案是在布局时,始终读取CarContext.getCarDevice()中的displayBounds和safeInsets。

知识体系总览

下面这张图,是我自己总结的CarPlay与Android Auto的核心知识结构。你可以把它当作一个检查清单,看看自己还有哪些盲区。

CarPlay 与 Android Auto 知识体系 协议适配 iAP2 / AOA / AA协议 应用投射 远程渲染 / H.264/H.265 UI映射 模板 / 安全区域 / 交互 传输层 USB HID / Wi-Fi P2P 蓝牙RFCOMM / BLE 身份认证 / 加密通道 渲染流程 手机生成UI描述 H.264/H.265编码传输 车机解码 + 本地渲染 适配要点 分辨率 / DPI适配 安全区域 / SafeInsets 旋钮 / 方向盘按键 核心原则 1. 协议握手必须健壮,失败要有重试和日志 2. 投射延迟控制在100ms以内,优先优化Wi-Fi QoS 3. UI映射始终读取车机安全区域,不要硬编码

代码示例:Android Auto的UI映射适配

下面这段代码,是我在实际项目中用过的。它展示了如何读取车机的安全区域,并动态调整布局。

// 在CarAppService中获取车机信息
@Override
public void onCreate() {
    super.onCreate();
    CarContext carContext = getCarContext();
    CarDevice carDevice = carContext.getCarDevice();
    
    // 获取屏幕信息
    DisplayMetrics metrics = carDevice.getDisplayMetrics();
    int screenWidth = metrics.widthPixels;
    int screenHeight = metrics.heightPixels;
    
    // 获取安全区域
    Rect safeInsets = carDevice.getSafeInsets();
    int topSafe = safeInsets.top;
    int bottomSafe = safeInsets.bottom;
    int leftSafe = safeInsets.left;
    int rightSafe = safeInsets.right;
    
    // 动态计算可用区域
    int usableWidth = screenWidth - leftSafe - rightSafe;
    int usableHeight = screenHeight - topSafe - bottomSafe;
    
    // 调整布局参数
    ViewGroup.MarginLayoutParams params = 
        (ViewGroup.MarginLayoutParams) myLayout.getLayoutParams();
    params.setMargins(leftSafe, topSafe, rightSafe, bottomSafe);
    myLayout.setLayoutParams(params);
}

小技巧:调试时可以在车机上开启「开发者模式」,显示安全区域边界。这样你一眼就能看出UI有没有被遮挡。

CarPlay的UI映射:模板化开发

CarPlay这边,苹果提供了几个固定的模板:

模板类型 适用场景 限制
ListTemplate 列表类应用(音乐、播客) 只能显示列表+标题
GridTemplate 网格类应用(应用启动器) 最多4列
MapTemplate 导航类应用 必须包含地图+控制栏
NowPlayingTemplate 正在播放界面 固定布局,只能替换封面和按钮

你可能会问:「为什么苹果要这么死板?」其实原因很简单——安全。开车时操作屏幕,越少的交互复杂度,越安全。苹果把UI的决策权收走了,开发者只需要填充数据。嗯,这种做法虽然限制了创意,但确实降低了事故率。

我在做CarPlay导航应用时,就遇到过一个问题:MapTemplate的「开始导航」按钮,必须放在固定位置。我们想把它挪到屏幕底部,结果审核被拒了。后来查文档才发现,苹果对按钮位置有硬性要求——必须在驾驶员一侧,且距离方向盘不超过一定距离。

注意:CarPlay的UI模板不是「建议」,是「强制」。如果你的应用使用了自定义布局,苹果审核会直接拒绝。所以老老实实用模板吧。

总结一下

CarPlay和Android Auto,虽然都是手机投射,但设计哲学完全不同。CarPlay是「苹果定义一切」,开发者只管填内容;Android Auto是「谷歌给框架,开发者自己适配」。两种方式各有优劣,但最终目标是一样的——让驾驶员在安全的前提下,用好手机上的应用。

我个人建议,如果你同时对接两个平台,先搞定CarPlay。因为它的约束多,反而容易做对。Android Auto自由度大,但坑也多。等你把CarPlay的模板摸透了,再回头搞Android Auto,会顺手很多。

好了,这一章就到这里。记住,协议适配是基础,投射性能是关键,UI映射是门面。三者缺一不可。

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