20、Android Auto协议实现:AA协议栈、Projection API、MediaSession映射、通知桥接
Android Auto的车机互联,说白了就是手机和车机之间的一场「对话」。手机是服务端,车机是客户端。但这场对话怎么建立?数据怎么传?媒体怎么控制?通知怎么同步?嗯,这就是我们今天要聊的核心——AA协议栈。
我最早接触AA协议栈时,觉得它像个黑盒子。后来踩了不少坑,才慢慢摸清楚它的脉络。今天我把这些经验整理出来,希望能帮你少走弯路。
AA协议栈的整体架构
AA协议栈不是单一协议,而是一组协议的集合。它分为三层:
- 传输层:负责手机和车机之间的物理连接和数据传输。早期用USB,现在主流是WiFi。
- 会话层:管理连接的生命周期,包括握手、认证、保活。
- 应用层:处理具体的功能,比如媒体投屏、电话、导航、通知。
我个人习惯把这三层想象成「高速公路—收费站—服务区」的关系。传输层是路,会话层是收费管理,应用层才是你真正使用的服务。
核心要点:AA协议栈基于Google的Protocol Buffers(protobuf)进行序列化。所有消息都是二进制格式,不是JSON或XML。这意味着调试时不能直接看文本,得用专门的工具。
我在项目中遇到过一个问题:车机端一直收不到手机的媒体信息。查了半天,发现是protobuf版本不匹配。手机端用了3.x,车机端还在用2.x。嗯,这种低级错误,但排查起来真的很费时间。
Projection API:投屏的核心
Projection API是AA应用层的核心接口。它负责把手机屏幕上的内容「投射」到车机屏幕上。但注意,它不是简单的屏幕镜像——AA做了大量的UI适配和交互优化。
Projection API主要包含以下几个组件:
| 组件 | 职责 | 我踩过的坑 |
|---|---|---|
| CarProjectionManager | 管理投屏会话的创建和销毁 | 必须在主线程调用,否则崩溃 |
| ProjectionView | 车机端显示手机内容的视图 | 触摸事件坐标需要转换,不然点不准 |
| InputForwarder | 将车机触摸事件转发给手机 | 多点触控支持有限,部分车机只支持单点 |
| AudioFocusController | 管理音频焦点,避免声音冲突 | 导航和音乐同时播放时,焦点切换容易出bug |
为什么Projection API这么重要?因为它是手机和车机之间「视觉」的桥梁。没有它,车机屏幕就是一块摆设。
我的建议:在实现Projection API时,一定要处理好车机屏幕的尺寸适配。不同车型的屏幕分辨率差异很大,从800x480到1920x1080都有。我曾经在一个项目里没做适配,结果手机画面在车机上被拉伸得面目全非。
MediaSession映射:让音乐在车上响起来
MediaSession是Android系统提供的媒体控制框架。在AA中,手机端的MediaSession需要映射到车机端,让车机可以控制手机上的音乐播放。
映射过程大致是这样的:
- 手机端创建MediaSession,注册MediaSessionCompat.Callback
- AA框架自动发现手机上的MediaSession
- 车机端通过MediaControllerCompat连接到手机的MediaSession
- 车机发送播放、暂停、下一曲等命令,手机端响应
听起来很简单对吧?但实际实现时,有几个关键点要注意。
警告:MediaSession的Token必须在车机端和手机端保持一致。如果Token不一致,车机端无法控制手机端的播放器。我曾经因为Token序列化/反序列化的问题,折腾了两天才找到原因。
另外,MediaSession的元数据(Metadata)也很重要。车机端需要知道当前播放的歌曲名、歌手、专辑封面等信息。这些信息通过MediaMetadataCompat对象传递。
// 手机端:设置MediaSession元数据
MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "晴天")
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "周杰伦")
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "叶惠美")
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 300000L) // 5分钟
.build();
mediaSession.setMetadata(metadata);
车机端收到元数据后,就可以在UI上显示歌曲信息了。但这里有个坑——专辑封面。封面图片通常比较大,直接传Bitmap会导致内存溢出。AA框架内部会做压缩,但如果你自己实现传输,一定要处理好图片大小。
通知桥接:把手机通知搬到车机上
通知桥接是AA协议栈中容易被忽视但非常重要的部分。开车时,你不能低头看手机,但车机屏幕上可以显示通知——比如微信消息、来电提醒、导航提示。
通知桥接的实现原理:
- 手机端通过NotificationListenerService监听所有通知
- AA框架筛选出适合在车机上显示的通知(比如排除系统通知)
- 将通知内容序列化后发送给车机端
- 车机端解析并显示通知,用户可以通过语音或触摸进行交互
我记得有一次,用户反馈说车机上收不到微信通知。排查后发现,是微信的通知渠道(NotificationChannel)被设置成了「静默」模式。AA框架默认只桥接「重要」级别的通知。嗯,这个坑其实不算AA的问题,但用户只会觉得是你的功能不好用。
避坑指南:我曾经因为通知的点击事件(PendingIntent)没有正确处理,导致用户点击通知后,手机端没有反应。原因是车机端发送的Intent extra参数和手机端期望的不一致。解决方案是统一使用AA框架提供的NotificationCompat.CarExtender来构建通知。
通知桥接还有一个安全方面的考虑:驾驶过程中,不应该显示过于复杂的通知内容。AA框架默认会限制通知的显示长度,超过一定字符数的通知会被截断。这个阈值在不同版本中可能不同,建议你在实现时主动控制。
协议实现的常见问题
最后,我总结几个AA协议实现中容易踩的坑:
- 连接稳定性:WiFi连接容易受干扰,建议实现重连机制。我一般设置3次重试,间隔2秒。
- 音频焦点冲突:导航和音乐同时播放时,要处理好音频焦点的获取和释放。AA框架提供了AudioFocusController,但需要正确配置。
- 触摸事件坐标:车机屏幕的触摸坐标需要映射到手机屏幕坐标。映射公式是:
phoneX = carX * phoneWidth / carWidth,但要注意旋转和缩放的情况。 - 版本兼容性:不同Android版本的AA协议实现有差异。Android 10和Android 12的协议细节就不一样。建议在测试时覆盖多个版本。
说实话,AA协议栈的实现并不复杂,但细节很多。每一个看似简单的功能,背后都有一堆边界情况要考虑。我的经验是:先跑通最简单的场景,再逐步增加复杂度。不要一开始就想把所有功能都实现,那样只会让你陷入调试的泥潭。
好了,AA协议栈的核心内容就这些。下一节我们会深入MediaSession的细节,看看如何实现一个完整的音乐投屏功能。