随着车载显示屏在 Android Auto 和 Android Automotive OS 车辆中普及,开发者面临着独特的双重挑战:既要在停车时提供丰富、自适应的体验,又要在驾车时提供安全、模板驱动的体验。在这场时长 30 分钟的全面技术讲座中,我们将探讨端到端架构和开发方法,帮助您将 Android 应用适配到汽车。
从车载应用设计基础知识,到打造媒体体验的最新方法(当汽车从行驶状态切换到停车状态时,媒体体验也会从音频切换到视频),您将掌握让应用上车所需的一切知识。
转写文稿
好了
欢迎大家。
感谢您参加本次“汽车是您的画布”专题演讲。
本讲座将介绍如何解锁 Android 汽车开发。
我是 Paul Lammertsma。
我是 Thomas Weathers。
我们是 Android for Cars 团队的开发者关系工程师。
我相信,许多开发者都投入了大量时间和精力,确保应用在各种屏幕和设备(手机、可折叠设备、平板电脑等)上都能呈现出色的视觉效果和功能。
今天,我们将重点关注一个您可能没有想到的屏幕。
它还能帮助您以独特的方式触达用户。
那就是车载显示屏。
首先,我们来简要介绍一下 Android for Cars 的含义。
Android for Cars 并非单一产品。
您可以定位到两个平台。
首先是 Android Auto,这是一个由手机驱动的投屏系统。
借助 Android Auto,应用在手机上执行,并通过有线或无线连接将其界面投射到车载显示屏。
第二种是 Android Automotive OS,这是 Android 的一个变体,直接在车辆本身上运行。
Android Automotive OS 独立于手机运行,可直接访问车辆硬件(例如空调和收音机),更重要的是,它还可管理自己的数据连接。
部分搭载 Android Automotive OS 的汽车是含 Google 预装的汽车。
含 Google 预装的汽车还预装了 Google 应用和服务,例如 Play 商店、Gemini 和 Google 地图。
虽然有两个平台,但我们的目标是让您能够轻松地一次性构建应用,并部署到这两个平台上,提供出色的体验。
下面是一个示例,展示了使用汽车应用库一次性构建的媒体应用,以及它在 Android Auto 和 Android Automotive OS 上,在各种不同显示屏尺寸和形状下的外观。
您可能需要为许多不同类型的设备和平台进行开发。
那么,这就引出了一个问题:为什么汽车要采用这种模式?
首先,这里的规模非常庞大。
目前,全球有超过 2.5 亿辆汽车兼容 Android Auto,16 个不同品牌的 100 多款车型搭载了 Google 预装。
当您的应用进入汽车后,您就可以在用户日常通勤、往返于家庭活动、外出办事甚至公路旅行时触达他们。
在美国,根据美国汽车协会 (AAA) 发布的《2024 年美国驾驶调查》,驾驶员平均每天在方向盘后度过 60 分钟。
但您不仅可以在用户主动驾驶时触达他们。
您还可以在用户停车时触达他们。
想象一下,他们可能正在电动车辆充电站给车充电,或者正在学校门口排队接孩子。
随着副驾屏和后排屏的兴起,我们预计未来会有更多机会来提升用户互动度。
好了
现在,我们来看看您可以为汽车打造的不同类型的体验。
为了解如何针对汽车进行构建,最好先了解应用在车内运行时存在的两种主要情况和状态。
首先,有些应用是您在驾驶过程中使用的。
这些是经过驾驶优化的体验,可在车辆行驶时使用。
这包括屏幕上列出的这些类别的应用,例如音频应用(我们通常称为媒体应用)、即时通讯和通信应用、导航应用等。
经过驾驶优化的体验必须遵守严格的防分心标准。
在为应用构建界面时,需要特别注意一些事项,以便在车辆行驶时安全使用该应用。
现在,有几种不同的方法可以构建针对驾驶场景优化的应用。
但在本演讲中,我们将重点介绍如何使用汽车应用库。
这是一组 Jetpack 库,提供了一个模板化框架,其中包含针对驾驶进行了优化的界面,并负责适应汽车中存在的各种硬件配置。
因此,请考虑输入模式、屏幕尺寸和宽高比等因素。
虽然这不是为汽车打造驾驶优化体验的唯一方法,但我在本演讲中要重点介绍的方法是使用 Android for Cars 应用库进行构建。
我们通常将其简称为 CAL。
如前所述,汽车应用库是一组库,可帮助您构建车载应用。
该应用库是一个模板化框架,其中包含针对驾驶进行了优化的界面,并负责适应不同的硬件配置。
您可以构建应用,并确信应用在所有这些不同的配置中都能顺畅运行,无论它们是运行 Android Auto 还是 Android Automotive OS。
使用 CAL 构建的应用可以依赖于此主机客户端架构。
主机应用(Android Auto 本身或含 Google 预装的汽车中的 Google Automotive App Host)是系统渲染程序。
它最终控制着需要在用户显示屏上显示的内容。
因此,作为应用开发者,您最终不会绘制像素。
而是向汽车应用库模板提供结构化数据及其状态。
然后,宿主会获取这些数据,并以符合安全要求的方式,采用防分心优化布局安全地绘制这些数据。
接下来,我们来看看 CAL 中的类,以及您需要扩展哪些类来构建应用,首先从 CarAppService 开始。
这是 Android 服务的子类,充当托管应用与客户端应用进行通信的入口点,其用途是创建会话实例,然后托管应用会与这些实例进行交互。
您可以将会话视为在车辆内的显示屏中运行的客户端应用实例。
与其他 Android 组件一样,它有自己的生命周期,可用于初始化和销毁在会话实例存在期间使用的资源。
这是 CarAppService 与会话之间的一对多关系。
例如,对于支持仪表板屏幕的导航应用,一个 CarAppService 可以有两个会话实例,其中一个用于主显示屏,另一个用于仪表板显示屏。
下面是一个示例:一辆汽车有一个主显示屏和一个仪表板显示屏,每个显示屏都与一个专用会话相关联,这也是应用开发者支持多显示屏车辆的方式。
然后,在会话中,屏幕实例负责生成由主机应用渲染的界面。
这些界面由模板类表示,每个类模拟特定类型的布局,例如网格或列表。
因此,每个会话都会管理一组屏幕实例,这些实例用于处理应用不同部分之间的用户流。与会话一样,屏幕也有自己的生命周期,您可以在其中应用钩子。
虽然主机负责渲染界面,且您使用的是汽车应用库模板,但这并不意味着您会受到严格限制,或者无法自定义应用。
有丰富的模板和组件可供使用,您还可以进行混搭,以获得所需的体验。
有些是通用模板,可供汽车应用库中的所有类型的应用使用;有些是专用模板,专为特定应用体验(例如导航或地图注点)而设计。
但在详细介绍如何打造驾车优化体验之前,我想先介绍一下您可以在汽车中运行应用的另一种情况:汽车处于停车状态。
某些类别的应用只能在车辆处于停车状态时使用,这包括视频应用、游戏和浏览器等。
但当车辆未行驶时,驾驶员分心准则不再适用,您可以提供更符合现有自适应界面的体验。
驻留应用基于 activity,与在其他设备(例如手机或平板电脑)上使用的 activity 类似,甚至相同。我们将在下一部分中详细介绍驻留应用。
虽然停车状态下使用的应用体验会重复使用现有的自适应 activity,但您仍需满足一些汽车专属质量指南,从清单声明和刘海屏,到在汽车开始行驶时暂停播放。
为了帮助您自动完成这项工作,我们推出了一项新的 Android 技能,专门用于处理搁置的应用。
因此,在开始之前,请确保您已安装 Android CLI,您可以通过此短链接找到它。
设置完成后,运行 android skills list 即可探索可用的技能。
android skills list
android skills add android-for-cars-parked-apps这些技能有很多,您可以运用许多不同的技能来提升 Android 开发水平。
具体到 Android for Cars,您可以使用 android skills add android-for-cars-parked-apps 将技能添加到智能体的上下文中。
现在,无论您最喜欢的 AI 智能体是什么,它都能为您提供分步指导,帮助您为 Android Auto 或 Android Automotive OS 配置清单、处理车窗边衬区和返回导航等事项、强制执行驾驶员分心播放限制,甚至还可以在您将视频、游戏或浏览器应用提交到 Google Play 之前对其进行审核。
因此,由于这些应用类别的概念,为汽车构建应用也有些独特。
如前所述,某些类别的应用可在驾驶时使用,而其他类别的应用则可在停车时使用。
这些突出显示的类别是可在驾车时使用的类别。
其中一些应用带有 CAL 标记,如我所说,这是汽车应用库,此类别中的应用使用我们刚才看到的模板。
现在,我来快速介绍一下这些功能,不会讲得太详细。
首先是媒体应用,现在这些应用播放的是音频,而不是视频。
有两种构建方式。
这里顶部的这个,用 MediaBrowserService 表示,是一种最简单的选项。
您只需提供元数据,信息娱乐屏幕界面会完全负责以针对汽车优化的方式呈现播放和媒体浏览器界面。
另一种方法是使用汽车应用库构建媒体应用,这样您就可以更好地控制和自定义媒体应用的样式和呈现方式,Thomas 稍后会向我们详细介绍。
导航应用当然是关键用例,可提供路线和精细导航。
拼车司机应用也属于此类别。
兴趣点应用可帮助您找到附近的实用地点,例如电动汽车充电桩或停车位。
借助物联网 (IoT) 应用,您可以随时随地访问智能设备,例如进行家居管理。
此外,还有天气应用,可提供实时雷达、风暴警报和特定位置的天气预报等功能。
最后,通信类应用分为三个子类别:将基本通知扩展到车载系统、即时通讯应用和视频通话应用。
此外,还有一些类别处于搁置状态:在线影音应用、游戏和网络浏览器。
我很高兴地宣布,从几周前开始,所有开发者都可以在 Android Auto 和 Android Automotive OS 上发布游戏。
您会注意到,其中一些类别显示为“开放”,另一些显示为“Beta 版”,还有一些显示为“敬请期待”。
“开放”意味着任何开发者都可以将此类别中的应用直接提交到正式版。
Beta 版意味着,虽然您可以将应用发布到内部或封闭式轨道,但除非您获准加入抢先体验计划,否则无法将应用发布到正式版轨道。
每个类别的文档都包含更多信息以及指向意向调查表的链接。
“即将推出”则适用于目前不支持但我们计划在未来支持的类别。
我们的最终目标是统一平台。
我们希望您能够通过一次编写并分发到 Android Auto 和 Android Automotive OS,构建应用并获得最大覆盖面,无论应用属于哪个类别。
现在,请 Thomas 介绍媒体。
谢谢!
太好了。
因此,去年我们在 1.8 版中推出了适用于媒体应用的汽车应用库,为 Android Auto 带来了模板化浏览界面和自定义操作。
其中包括左侧所示的媒体播放模板。
在今年的 I/O 大会上,我们通过汽车应用库 1.9 推出了更多可浏览的界面。
因此,随着 CAL 1.9 进入 Beta 版阶段,您现在可以解锁 Auto 和 Automotive OS 的关键功能更新。
亮点包括通过应用表达进行品牌自定义,让您能够使用宿主渲染的布局自定义颜色和形状令牌,同时遵守安全规则,以及新的界面组件,例如精选部分、精简列表项、可滚动条状标签和常驻迷你播放器。
此外,CAL 1.9 还将媒体模板支持全面扩展到 AAOS,从而在两个生态系统中实现真正的平台统一。
汽车应用库的下一个迭代版本即将推出:CAL 版本 10。
CAL 版本 10 将为通讯应用提供更多自定义模板,从而在驾驶时提供更强大、更个性化的通话体验。
我们还新增了一个应用类别和应用模板,旨在为用户提供驾车时的对话式体验。
最后,我们还在开发更多功能,以便通过汽车应用库模板实现更顺畅的停车到驾驶过渡。
更多精彩即将推出,敬请期待。
借助汽车应用库的停车到驾驶功能,您的应用可以在车辆静止时动态切换模式并显示自适应内容,并在驾驶时无缝切换回汽车应用库模板。
主要应用场景包括:在车辆静止时提供量身定制的身份验证流程,或在车辆停放时启用专用体验,例如在线播放视频播客或音乐片段。
这会直接与媒体播放模板集成。
因此,当车辆处于静止状态时,驾驶员可以完全访问应用的 Android 原生 activity,从而体验视频在线播放等富媒体功能。
一旦车辆挂挡,防分心机制就会启动,这会提示您的应用切换回汽车应用库媒体模板,以维持合规且不间断的音频收听体验。
我们正处于车内体验的变革时刻。
媒体应用希望超越传统的浏览和播放界面,为驾驶员和乘客提供更丰富、更具沉浸感和互动性的体验。
现在,我们将深入探讨一个示例,了解如何使用 Media3 库添加原生视频 activity,以便在汽车切换到停车状态时开始播放。
因此,首先要将视频渲染界面直接连接到后台播放实例。
override fun onStart() {
super.onStart()
...
controllerFuture = MediaController.Builder(
this,
sessionToken
).buildAsync()
controllerFuture?.addListener({
mediaController = controllerFuture?.get()
playerView.player = mediaController
}, ContextCompat.getMainExecutor(this))
}您希望避免在此 activity 中实例化单独的媒体播放器,而是希望在主要服务(例如 Media3 媒体库服务)中维护一个中央 ExoPlayer 或媒体播放器,以便同时处理音频和视频流。
因此,在车辆行驶期间,当没有绑定任何活跃服务时,播放器仅解码音频;但当司机在停车时打开视频 activity 时,您需要将其渲染 Surface 直接附加到底层服务播放器。
因此,使用 Media3 时,mediaController 会原生实现播放器,从而允许直接赋值,如您所见,playerView 设置为…playerView.player 直接设置为 mediaController。
接下来,您需要使用 ParkedOnlyOnClickListener 注册原生入口点。
val watchVideoAction = Action.Builder()
.setTitle("Watch Video")
.setIcon(videoCarIcon)
.setOnClickListener(
ParkedOnlyOnClickListener.create {
val intent = Intent(
carContext,
VideoPlayerActivity::class.java
)
.apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
carContext.startActivity(intent)
})
.build()此封装容器在媒体播放界面的标题栏中提供了一个 intent 触发器。
因此,添加一个启动视频播放的操作,将其选择监听器封装在 ParkedOnlyOnClickListener 中。
因此,在驾车模式下,这些点按操作会被宿主应用拦截,宿主应用会显示系统安全消息,因此您在驾车时无法进入该界面,但在停车时,回调会触发 carContext 中的 startActivity,以启动指定的全屏 activity。
请注意,在这些汽车应用库应用中,您需要在清单中将视频目标标记为 distractionOptimized=false,以便操作系统识别出这只是一个停车工作流。
<activity
android:name=".VideoPlayerActivity"
android:exported="false"
android:launchMode="singleTop">
<meta-data
android:name="distractionOptimized"
android:value="false" />
</activity>最后,我们来看看当驾驶员从停车挡切换到行驶挡时会发生什么。
如果 activity 未经过防分心优化,并且在汽车行驶时仍处于活跃状态,AAOS 会显示一个系统屏蔽叠加层,基本上就是告诉您,请停车后再继续使用该应用。
为了避免出现此屏蔽屏幕,我们的目标是无缝返回到媒体播放模板,而不会中断音频。
因此,如果 isRequiresDistractionOptimization 的求值结果为 true,我们基本上会从底层媒体会话中清除视频渲染 surface,并在播放器 activity 上调用 finish,因为该 activity 是在 CAL 体验之上启动的,完成该 activity 会直接返回到媒体播放模板并继续播放音频。
class VideoPlayerActivity : ComponentActivity(){
override fun onStart() {
…
if (uxRestrictionsManager?
.currentCarUxRestrictions?
.isRequiresDistractionOptimization
== true) {
revertToCarAppTemplate()
}
}
private fun revertToCarAppTemplate() {
mediaControllerCompat
.sendCommand("CMD_CLEAR_VIDEO_SURFACE",
null, null)
finish()
}
}保持音频不间断播放是提供出色用户体验的关键,因此,通过将媒体会话作为前台服务保持活跃状态,声音可以在这些状态变化中无缝持续播放。
<activity
android:name="androidx.car.app.activity.CarAppActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK" />
</intent-filter>
<meta-data
android:name="distractionOptimized"
android:value="true" />
</activity>您还应在 CAL 界面中替换 onNewIntent,以捕获 SHOW_MEDIA_PLAYBACK intent。
class MyCarAppSession : Session() {
override fun onCreateScreen(intent: Intent): Screen {
// 1. Link your active MediaSession token with Car App Library's MediaPlaybackManager
val mediaPlaybackManager = carContext.getCarService(MediaPlaybackManager::class.java)
mediaPlaybackManager.registerMediaPlaybackToken(mediaSessionToken)
// 2. Handle deep link if cold-started from the vehicle's media widget
handlePlaybackIntent(intent)
return HomeScreen(carContext)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// 3. Handle deep link when the session is already running
handlePlaybackIntent(intent)
}
}这样,每当用户与汽车主屏幕上的车载系统媒体微件互动时,深层链接都会将他们直接转到汽车应用库中的“闻曲知音”视图。
因此,在整个开发过程中验证这些过渡非常简单。
您可以在 AAOS 上执行 adb 命令。
对于这种情况,当您准备好在 activity 之间切换回来时,它将是 emulate-driving-state drive 或 emulate-driving-state park。
下面是一个示例,展示了我们新推出的 Media3 媒体应用示例,该应用使用汽车应用库,支持在停车时播放视频。
欢迎前往楼下的自适应 Android 展位,或访问我们的 car-samples GitHub 代码库,了解更多信息。
目前,Android Automotive OS 上的媒体应用已集成汽车 CAL,可提供驾驶体验,现在还可提供更强大的停车体验。
请尽快前往 developer.android.com 查看我们的文档,并在其中表达您对在应用中采用此功能的兴趣。
Paul。
太好了。
谢谢 Thomas。
在结束之前,我想分享几种方式,让大家了解今年晚些时候将推出的更多功能。
正如 Thomas 所说,CAL 1.9 目前处于 Alpha 版阶段。
该功能很快就会进入 Beta 版阶段。
CAL 1.10 将在年底前进入 Alpha 版阶段,届时您就可以开始评估对话式界面以及最新的通信和停车到驾驶功能。
最后,正如我之前提到的,我们很高兴地宣布,Android Auto 和 Android Automotive OS 现已支持游戏,因此现在正是针对汽车优化游戏内容的绝佳时机。
如需详细了解实现细节,请务必参阅“打造车载游戏”指南中的文档。
如需详细了解如何让游戏适配车载系统,以及 Android for Cars 的其他新变化和新功能,请访问我们的“新变化”页面。
希望您已经了解汽车和用户如何为您的应用做好准备。
您可以基于现有的自适应应用打造丰富的停车体验,并使用各种 Car App Library 模板打造安全且经过优化的驾驶体验。
在发布之前,您可以使用桌面车机、模拟器、在 Pixel Tablet 上运行的 AAOS 或 Firebase Test Lab 进行全面测试。
您已经具备为该生态系统构建应用所需的技能,并且有数百万辆汽车正在等待您的应用,所以让我们开始行动吧。
android skills list
android skills add android-for-cars-parked-appsoverride fun onStart() {
super.onStart()
...
controllerFuture = MediaController.Builder(
this,
sessionToken
).buildAsync()
controllerFuture?.addListener({
mediaController = controllerFuture?.get()
playerView.player = mediaController
}, ContextCompat.getMainExecutor(this))
}val watchVideoAction = Action.Builder()
.setTitle("Watch Video")
.setIcon(videoCarIcon)
.setOnClickListener(
ParkedOnlyOnClickListener.create {
val intent = Intent(
carContext,
VideoPlayerActivity::class.java
)
.apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
carContext.startActivity(intent)
})
.build()<activity
android:name=".VideoPlayerActivity"
android:exported="false"
android:launchMode="singleTop">
<meta-data
android:name="distractionOptimized"
android:value="false" />
</activity>class VideoPlayerActivity : ComponentActivity(){
override fun onStart() {
…
if (uxRestrictionsManager?
.currentCarUxRestrictions?
.isRequiresDistractionOptimization
== true) {
revertToCarAppTemplate()
}
}
private fun revertToCarAppTemplate() {
mediaControllerCompat
.sendCommand("CMD_CLEAR_VIDEO_SURFACE",
null, null)
finish()
}
}<activity
android:name="androidx.car.app.activity.CarAppActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK" />
</intent-filter>
<meta-data
android:name="distractionOptimized"
android:value="true" />
</activity>class MyCarAppSession : Session() {
override fun onCreateScreen(intent: Intent): Screen {
// 1. Link your active MediaSession token with Car App Library's MediaPlaybackManager
val mediaPlaybackManager = carContext.getCarService(MediaPlaybackManager::class.java)
mediaPlaybackManager.registerMediaPlaybackToken(mediaSessionToken)
// 2. Handle deep link if cold-started from the vehicle's media widget
handlePlaybackIntent(intent)
return HomeScreen(carContext)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// 3. Handle deep link when the session is already running
handlePlaybackIntent(intent)
}
}