一文了解 Android Auto 车载开发~

以下内容来自公众号code小生,关注每日干货及时送达d4b4407747715b8bc6dca9468857decb.png

转载自:https://juejin.cn/post/7194075625328984120

作者:牛蛙点点申请出战

1. 背景

我的产品作为一个海外音乐播放器,在车载场景听歌是一个很普遍的需求。在用户反馈中,也有很多用户提到希望能在车上播放音乐。同时车载音乐也可以作为提升用户消费时长一个抓手。

出海产品,主要服务于海外用户。不同于国内的 Android 车载系统,往往是定制的 ROM,国外的 Android 车载也是 Google 一家独大,主要可以分为 Android Auto、Automotive OS。Android Auto 可以理解为是一套将手机应用「投屏」在车载屏幕上的方案,和 iOS 的 CarPlay 类似。而 Automotive OS 则类似于国内的定制 ROM。如果要支持 Automotive OS,那我们可能要重头开发一个适配大屏的 mini 版 app,那样工作量很大。所以我们第一步先从支持 Android Auto 开始

9b8b112bf12f5e2323c953b06dfc3c04.jpeg26cbe9dd780e4a2cac766fec247f7bad.jpeg

2. 准备知识

Android Auto 支持多种类型的应用,包括导航、媒体、消息应用等。音乐播放器属于媒体应用,因此下面给出的参考仅限于媒体应用。

Android Auto 的适配需要做提前了解做一些准备知识,具体如下:

  • 官方文档

    • 构建车载媒体应用

      教你明白 Android Auto 开发媒体应用的各种概念以及如何开发。这个网址的中文翻译可读性很差,建议直接看英文。另外,如果要开发的是导航类应用而不是媒体应用,那可以直接使用 androidX 的 Car Library 。

    • 测试 Android Automotive OS 应用

      教你如何使用车机模拟器(DHU)配合手机上的 Android Auto app 来测试 Auto 应用。但是文档里有个坑点,文档里默认你的手机是 Pxiel 系列,Pxiel 系列是自带 Android Auto app 的,但是国产手机基本都没有这个 app,需要自己去下载然后安装在手机上。可以去这里下载。另外 Android Auto app 第一次启动的时候会安装好几个谷歌服务,华为手机因为 ban 掉了google,这一步在华为手机上会失败。所以华为手机没法用来测试 Android Auto。推荐尽量使用 Pixel 系列手机来测试,避免测试过程中遇到奇奇怪怪的问题。

  • 车载设备

    • 业务功能的测试,一般使用模拟器 DHU 就可以。但是在应用发布之前,我们肯定要在真实设备上充分测试。如果你有带 Android Auto 的真车的话,直接用真车测试就行。但是国内带 Android Auto 的车比较少,大概率是没有真车可供测试的。可以去闲鱼上单独购买一个车载主机(闲鱼搜索「187b主机」即可)。

    • 还有一个坑点,应用没有经过 google play store 分发的话,在真实车机设备上是不会显示的。这个问题官方文档没有明确提到,导致当时我们 debug 的时候莫名其妙的花了很久的时间。

    • 这里顺带提一下,谷歌分发渠道有四个,internal、closed testing、open testing、production,分别对应内部、内测、公测、产品。当然,我们不可能为了测试直接提 play store 的 production 渠道,用 internal 分发渠道用作真机测试就可以了。closed testing 和 open testing 渠道也可以。

  • 质量规范

    • https://developer.android.google.cn/docs/quality-guidelines/car-app-quality

      因为车载场景事关驾驶员生命安全,所以 google 对 Android Auto 应用审核很严格。所有支持 Android Auto 的应用,必须满足上面链接中的质量规范才可能通过 Play Store 的审核。这个规范要求的点很多,坑也不少,后面还会说到。这里面很重要的一点是必须支持语音搜索歌曲起播,这是审核强卡的一个点。

  • 官方 demo

    • 因为文档讲得很晦涩,照着文档大概率是依然很难上手开发的。可以参考 google 官方的音乐播放器 uamp,虽然这个 demo 很简单,但是它也支持 Android Auto,有不懂的开发细节都可以参考这个 demo。

  • Android Auto app(包名为 com.google.android.projection.gearhead

    • 想要把 android auto 连上车机跑起来,必须要在手机上安装 Android Auto app,在这里 下载。大部分国内的 rom 是不带这个 app 的,要自己手动安装。pixel 系列手机是自带的,可以在设置中搜索到这个应用。

980570051213f9afde8da2e04ccec34c.jpeg

3. 如何开发

基本概念

首先,Android Auto 是不支持自定义 UI 的,你的应用投屏到车机上,UI 展示已经在车机内部写死了,你能做的只是把数据传到车机上。所以支持 Auto 的应用在车机上看起来都差不多。Auto 只允许你在播控界面的自定义操作里添加自定义图标。

其次,整个车机的播放流程涉及到三个部分,播放器应用、android auto app、车机。

另外还有几个概念我们要了解

  • MediaBrowserService

    • 指的就是音乐播放器的 Service。在 Service 里通过覆写 onGetRootonLoadChildren 等方法对外提供车机所需要展示的数据。相当于「媒体生产者」,该生产者由你的 app 的提供。

  • MediaBrowser

    • 展示、消费你从上面的 MediaBrowserService 拿到的数据。手机上的 Android Auto app 内部包含了这个类。这个类会来主动 binds 上面提到的的 MediaBrowserService。相当于「媒体消费者」,就是 Android Auto app。

  • MediaSession

    • MediaSession 就是 app 和车机之间进行交互的桥梁。实际上 app 和车机不是直接连接进行通信。app 是和 Android Auto app 进行 IPC 通信,Android Auto 再和车机进行通信。MediaSession 本质上就是一个对 IPC 的封装。这个封装不仅能和车机通信,还可以和耳机线控等外部设备进行通信。这些外部设备共用一个 MediaSession。

132198c83da74a19db709e4f28feb13f.jpeg

  • MediaItem、MediaMetadata、MediaDescription

    • MediaItem 代表了车机屏幕上的一个媒体元素,比如某一个页面,一首歌,一张专辑等等,在这个类中对应的属性设置成什么,车机上屏幕上就显示什么。

    • MediaMetadata 代表 MediaItem 中各个属性对应的值,用来构造 MediaDescription。MediaItem 持有 MediaDescription 用以描述该媒体元素该如何在车机上展示。

  • BROWSABLE 和 PLAYABLE

    • 这个一个枚举标记,车机上每一个 MediaItem 要么是 BROWSABLE 的,要么是 PLAYABLE 的。

    • PLAYABLE 意味着该 MediaItem 可播,这样当你在车机屏幕上点击该 MediaItem 时,会直接进行播放。这种 MediaItem 一般是歌曲,可直接起播。推荐 Android 学习资料

    • BROWSABLE 意味着该 MediaItem 是可浏览的,也就是说点击该 MediaItem 时,会加载一个新的媒体集合用于展示。比如专辑、歌单等,点击的时候会进入一个新的页面展示歌曲列表。

上面就是做 Android Auto 开发需要理解的基本的概念,可以理解为一套车机的框架,把手机和车机交互中的各个角色都定义好了。我们的工作就是在这套框架上开发我们的业务。

车机连接手机
  • 真车上和模拟器上都可以参考官方文档 https://developer.android.com/training/cars/testing?hl=zh-cn#test-automotive-os

  • 或者可以下载这个压缩包,运行 main.py 即可。该脚本把上述文档里的操作步骤都集成进去了。 https://github.com/ultimateHandsomeBoy666/AndroidAutoTest

  • 连上以后,音乐自动会从车机上播放,像耳机连上手机之后音频自动从耳机播放一样。不需要额外在代码中设置。

检测连接
  • 这是 auto 开发中的一个坑点。以往有一个方法可以检测手机是否处在车载模式,但是该方法只对 android 12 以下的系统生效,android 12 上无效:

public static boolean isCarUiMode(Context c) {
UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
    if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
        LogHelper.d(TAG, "Running in Car mode");
        return true;
    } else {
        LogHelper.d(TAG, "Running on a non-Car mode");
        return false;
    }
}
  • 官方有一个新的 CarLibrary 「androidx.car.app:app」,这个库中使用 CarConnection 类来检测车机和手机的连接。

    • CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)
    • 但是该方法只适合于 android 6-12 的系统,并且我们只想使用连接检测,导入整个库的话未免显得太笨重。

  • 基于上述 CarLibrary 「androidx.car.app:app」的方法,我们把核心代码抽出来即可。具体代码我在 stackoverflow 上贴出来作为一个回答,也被采纳了。对于 android 5 的系统,使用前述的 isCarUiMode 方法即可。

    https://stackoverflow.com/questions/39320048/how-to-detect-if-phone-is-connected-to-android-auto/72881651#72881651

工作原理

如前面 MediaSession 的介绍所述,这个工作过程涉及到车机、android auto app、应用三方。为了叙述方便,后面提到的「车机回调客户端方法」实际上均是中介作用的 Android Auto App 通过 IPC 完成的。

下文中的 PlayerService 指的是应用中用于播放音乐的 Service。

整体的工作流程如下面的时序图所示,后面会逐一解释。

112cbe221d1629485d6f6f441edf0d7e.jpeg

bindService
  • 当我们的车机和手机连接上之后,Android Auto app 就会主动地 bind 我们的 PlayerService。因此 Service 必须声明 android:exported="true" ,这样才可以被外部拉起。同时整个 app 进程也被拉起了。

9d8d904c4cc042aee28dccf473808657.jpeg

页面树
  • 整个车机屏幕的 UI 可以看做一个页面树,客户端应用负责把定义各个页面树的节点 ID 和传输节点数据对应的 MediaItem。而车机在拿到这些数据以后就可以渲染屏幕 UI。

  • 车机通过 IPC 回调到客户端代码中的 onGetRoot() 方法和 onLoadChildren() 方法来获取页面 ID 和数据。接下来详细说下这两个方法。

  • 页面树的节点 ID 可以自己定义,只要保证每个节点对应唯一的 ID 即可。

  • 一般来说,叶子节点是 PLAYABLE 类型的 MediaItem,而非叶子节点对应 BROWSABLE 类型的 MediaItem。

onGetRoot()
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot
  • onGetRoot 是车机和手机交互的入口方法,只要车机和手机连接上了,就会回调这个方法。在这个方法中,需要构造一个 BrowserRoot 返回,作为车机页面树的根节点。整个页面树构造 BrowserRoot 需要传入 rootId。

  • clientPackageName -- 是唤起客户端是包名,一般来说是 Android Auto App,包名是 com.google.android.projection.gearhead。但是有时候通过语音搜索播歌唤起客户端,包名就是 com.google.android.googlequicksearchbox,是 google 语音助手。通过这个参数可以过滤你认为有效的包名。

  • 其他两个参数一般用不上。

onLoadChildren()
override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaItem>>
)
  • onLoadChildren 方法一旦被回调,说明在车机上真正开始加载手机客户端对应的车机页面树了。所以这个方法可以认为是车机端的 launch 点,可以用这个方法统计车机 DAU。

  • parentMediaId -- 之前我们会在 onGetRoot 中返回了 rootId,接下来车机会回调 onLoadChildren, rootId 就会作为该方法的 parentMediaId 传入,代表在此次 onLoadChildren 方法回调中我们需要加载 parentMediaId 对应的页面树节点对应的 List,也就是页面树中该节点的子节点。

  • 加载好了子节点后,以 List 的形式,通过 result.sendResult(List) 方法将结果返回给车机。

a18b90d9a80f96d4d6b2208a48820b0d.jpeg

整体的页面树如上所示。当在车机上点击蓝色子节点时,会回调 onLoadChildren 加载下一级子节点。当点击绿色叶子节点时,会回调 onPlayFromMediaId 方法进行起播。下面详细介绍。

MediaSession.Callback
  • MediaSession.Callback 是用户在车机上进行起播、播控以及语音搜索等操作的回调。该抽象类中包含了一系列的回调方法。下面介绍一些重要的回调方法。

  • 通过 mediaSession.setCallback 方法设置回调。

public abstract static class Callback { 
    public boolean onMediaButtonEvent(Intent mediaButtonEvent) {}// 线控耳机回调
    public void onPlay() {} // 播放
    public void onPause() {} // 暂停
    public void onPlayFromMediaId(String mediaId, Bundle extras) {} // 起播
    public void onPlayFromSearch(String query, Bundle extras) {} // 语音搜索
    public void onSkipToQueueItem(long id) {} // 切到播放队列中的某一首歌
    public void onSkipToNext() {} // 切到下一首
    public void onSkipToPrevious() {} // 切到上一首
    public void onCustomAction(String action, Bundle extras) {} // 自定义操作
    ....
}
MediaSession.setPlaybackState
  • 当我们执行了上述回调后,如何通知车机进行播放页的 UI 更新呢?这时候就需要调用 MediaSession.setPlaybackState 方法来通知车机更新 playback 的状态和 UI。PlaybackState,顾名思义,就是车机播放状态,所以改变这个状态意味着车机播控页 UI 也会更新。

  • 该方法具体如何使用可以参考 UMAP demo 和官方文档,这里不展开了。

onPlayFromMediaId
  • 上面我们说到,当用户在车机屏幕上点击歌曲起播时,就会回调该方法,并将构建页面树时赋予的 id 作为 mediaId 参数传入。因此,在该方法中我们需要调用客户端的起播歌曲的方法来起播。

onPlay 和 onPause
  • 这两个方法对应车机上的播放和暂停操作。

onSkipToNext 和 onSkipToPrevious
  • 这两个方法对应车机上的切下一首和切上一首操作。

onSkipToQueueItem
  • 在车机上切换到当前队列里的某一首歌,会回调该方法。传入对应的歌曲 id。

  • 在队列切歌之前,需要先给车机构造队列。调用 mediaSessionCompat.setQueue(List) 即可。

onPlayFromSearch(String query, Bundle extras)
  • 当我们在车内使用语音助手起播歌曲时,比如说 「播放周杰伦的夜曲」,会回调该方法。并且将语音内容识别成文字,分词后将 「周杰伦 夜曲」 通过入参 query 传入。这样我们拿到 query 字符串后,调用客户端的搜索服务就可以获得搜索的歌曲结果,并将该结果起播即可。

onCustomAction(String action, Bundle extras)
  • 当你想在车机播控界面添加其他自定义的操作,比如收藏歌曲、单曲循环等,就会用到这个方法。

  • 首先需要在构造 PlaybackState 的时候传入你定义的自定义操作,通过 PlaybackStateBuilder.addCustomAction(CustomAction action) 方法完成。构造 aciton 时需要传入唯一的标识字符串。在刷新了 PlaybackState 之后,在车机的播控界面就会出现自定义的操作按钮。

  • 当在车机屏幕点击了自定义操作按钮后,会回调 onCustomAction 方法,入参 action 就是唯一标识字符串,根据该字符串来区分不同的自定义操作。

4. 开发中的坑

Android Auto 在国内渗透率不高,所以大部分开发者对这个东西很陌生,我也是。并且很多国产 ROM 系统层就不支持 Android Auto。作为国内业界少数 Android Auto 的应用,在开发过程中经历了资料匮乏、机型兼容、审核被拒等很多坑。这里把之前开发踩坑的经历分享出来。

图标缓存

车机界面每个 tab 的 icon 在设置完之后是会有在车机里缓存的。如果修改了 icon 样式,一定要改掉对应的 drawable 的 id,不然车机会从缓存中取图片,icon 修改不生效。

机型不兼容

很多国产的 rom 对 Android Auto 的支持有问题。具体表现有:

  • 无法安装 GMS 导致无法使用 Android Auto App,以华为系手机为代表。

  • 可以安装并且运行 Android Auto App,但是一旦连上 DHU 测试的时候,DHU 就一直黑屏,无法正常运行。亲测小米 11 、vivo S15 机型有该问题。

  • 可以连接 DHU,可以正常运行,但是使用 debug 包在测试的时候,DHU 上不会显示应用。只有使用 GP 商店分发的包才能显示出来。部分 vivo 手机有这个问题。

所以,最好使用 pixel 这样的原生系统进行测试。

GP 分发

当我们在 DHU 上测完,想使用真实车机进行测试的时候,却发现真车上不显示我们的应用。正如之前所述,在真车上测试,需要经过 GP 分发的包才行,没有经过 GP 分发过的包,即使是 release 包也不行。这个坑当时困扰了我们很久,最后我们也是靠猜测才猜出原因。后来我们和 google 官方进行沟通,也确认了这一点。但是坑爹的是,google 文档里完全没有提及这一点。

语音搜索

语音搜索这个功能,在 DHU 上经常莫名其妙地不好用。具体有

  • 识别不出语音,语音助手回复 「对不起,我没有听懂」

  • 识别成别的应用,比如无论你怎么说 「使用 xxx 播放音乐」,它都回复 「好的,我来让 youtube music 播放音乐」,然后打开 youtube music 播放音乐

  • 语音说完没有任何反应。比如你说 「播放音乐」,在语音助手的对话框消失后就没有任何反应了,也没有回复,也不会打开应用播放。

  • 识别率差。我们的应用名,经常会识别错。但是像 Spotify、微信等应用名,识别率很高。怀疑是 google 语音助手对特定应用名识别做了优化。上面这些坑是在做语音搜索功能时经常遇到的。QA 在测的过程中也会经常遇到并且反馈 bug 给我。但大部分时候都是语音助手抽风。

如何判断到底是 bug 还是语音助手抽风呢,可以用同样的语音去试下其他应用,比如 spotify 和 YT music。如果也有同样的问题,那么可以认为是语音助手又抽风了。

审核

Google 商店对车载应用的审核标准很高。详见 质量规范,其中对车载应用需要满足什么样的条件做了严格的要求。对于音乐类应用,有几点容易忽视的需要格外关注:

  • 必须支持语音搜索播歌功能。google 认为,用户在开车时不能分散注意力,所以必须提供语音搜索播歌的功能,让用户可以开车的同时按下方向盘上的麦克风按钮,直接语音控制歌曲的播放。如果这个功能没满足,应用不能过审。

ca7c61513fcaa95ed69920d210bd0c88.jpeg

  • 歌曲播放时,如果手机上碰到阻塞,比如出需要手动关闭的广告、出弹窗、请求权限,必须让用户转到手机上处理时,这时候必须要在车机屏幕上提示用户。这时可以使用错误提示的 API 来做提示。

44171690636ac8f470a90994e6f31c6d.jpeg906da2207bf15f6f2ed808d1738add70.jpeg

  • 添加特定的 Intent Filter

    一定要在启动 Acitivity 里面添加这个 intent-filter,用来兼容古老版本的 android 手机语音播歌。可以参考 developer.android.com/guide/componets/intents-common 。

    这个点其实在 google 官方文档里有提,但是没有明确说必须要有,只是建议添加。但是如果不加的话,应用审核会被拒绝。所以这里也是个坑点。

    • <activity ...>
            <intent-filter>
                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

Android 14 × ColorOS 内测版本降级教程

最好用的 Android 模拟器推荐(附下载)

终于要来了,安卓二维码技术即将大改

bf2c55b0e5876bdf0f7fc3f12d54280d.png

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值