Android for Cars 应用库可以帮助您实现汽车导航、停车和充电应用。为此,它提供了一套模板,这些模板符合防止驾驶员分心的标准,它还解决了一些细节问题,例如存在各种车载显示屏类型和输入模式。
本指南简要介绍了该库的关键功能和概念,并引导您逐步完成设置简单应用的过程。
准备工作
- 查看 Android for Cars 应用库设计准则。
- 查看本部分中列出的关键术语和概念。
- 熟悉 Android Auto 系统界面。
- 查看版本说明。
- 查看示例。
- [仅适用于闭源库] 查看 Android for Cars 应用库使用条款。
关键术语和概念
- 模型和模板
- 界面由模型对象的图来表示,这些模型对象可以按照它们所属的模板允许的不同方式排列在一起。模板是模型的子集,它们可以在这些图中充当根。模型包含要以文字和图片的形式显示给用户的信息,以及用于配置此类信息的视觉外观各个方面(例如,文字颜色或图片大小)的属性。主机会将模型转换为符合防止驾驶员分心标准的视图,还解决了一些细节问题,例如存在各种车载显示屏类型和输入模式。
- 主机
- 主机是一个后端组件,它会实现库的 API 提供的功能,以便您的应用在汽车中运行。从发现应用并管理其生命周期,到将模型转换为视图,再到将用户交互操作通知给应用,这些都属于主机的职责范围。在移动设备上,此主机由 Android Auto 实现。
- 模板限制
- 不同的模板会对其模型的内容施加限制。例如,列表模板对可以呈现给用户的项数有限制。模板对可以采用什么方式连接它们以形成任务流也有限制。例如,应用最多只能将 5 个模板推送到屏幕堆栈。如需了解详情,请参阅模板限制。
- Screen
Screen
是一个由库提供的类,应用实现该类来管理呈现给用户的界面。Screen
具有生命周期,并提供了一种机制,可让应用发送要在屏幕可见时显示的模板。此外,也可以将Screen
实例入栈到屏幕堆栈以及对屏幕堆栈中的这些实例执行出栈操作,这样可以确保它们遵循模板流限制。- CarAppService
CarAppService
是一个抽象Service
类,应用必须实现并导出该类才能被主机发现并由主机进行管理。应用的CarAppService
负责使用CarAppService.createHostValidator
验证主机连接是否可以信任,随后使用CarAppService.onCreateSession
为每个连接提供Session
实例。- Session
Session
是一个抽象类,应用必须使用CarAppService.onCreateSession
实现并返回该类。它充当在车载显示屏上显示信息的入口点,并且具有生命周期,可告知车载显示屏上应用的当前状态,例如当应用可见或隐藏时。当
Session
开始时(例如当应用首次启动时),主机会使用Session.onCreateScreen
方法请求要显示的初始Screen
。
安装库
有关如何将库添加到应用的说明,请参阅 Jetpack 库发布页面。
配置应用的清单文件
您需要先配置应用的清单文件,然后才能创建汽车应用。
声明 CarAppService
主机通过 CarAppService
实现连接到您的应用。您应在清单中声明此服务,以允许主机发现并连接到您的应用。
您还需要在应用的 intent 过滤器的 category
元素中声明应用的类别。请查看支持的应用类别的列表,了解此元素允许的值。
以下代码段展示了如何在清单中声明停车应用的汽车应用服务:
<application>
...
<service
...
android:name=".MyCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService"/>
<category android:name="androidx.car.app.category.PARKING"/>
</intent-filter>
</service>
...
<application>
支持的应用类别
为了能够在 Play 商店中的 Android Auto 版块上架,应用需要属于某个支持的汽车应用类别。声明汽车应用服务时,您可以通过在 intent 过滤器中添加以下一个或多个支持的类别值来声明应用的类别:
androidx.car.app.category.NAVIGATION
:此类应用提供精细导航方向。androidx.car.app.category.PARKING
:此类应用提供与查找停车位相关的功能。androidx.car.app.category.CHARGING
:此类应用提供与查找电动车辆充电站相关的功能。
有关应用属于各个类别的详细说明和条件,请参阅 Android 汽车应用质量。
指定应用名称和图标
您需要指定应用名称和图标,主机可以使用它们在系统界面中表示您的应用。
您可以使用 CarAppService
的 label
和 icon
元素来指定用于表示应用的应用名称和图标:
...
<service
android:name=".MyCarAppService"
android:exported="true"
android:label="@string/my_app_name"
android:icon="@drawable/my_app_icon">
...
</service>
...
如果未在 service
元素中声明标签或图标,主机将回退到使用为应用指定的值。
汽车应用 API 级别
汽车应用库定义了自己的 API 级别,以便您了解指定车辆上的模板主机支持库的哪些功能。如需检索主机支持的最高汽车应用 API 级别,请使用 getCarAppApiLevel()
方法。
您必须在 AndroidManifest.xml
文件中声明应用支持的最低汽车应用 API 级别:
<manifest ...>
<application ...>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"/>
</application>
</manifest>
如需详细了解如何保持向后兼容性以及声明使用某个功能所需的最低 API 级别,请参阅 RequiresCarApi
注释文档。如需了解使用汽车应用库的特定功能所需的 API 级别,请参阅 CarAppApiLevels
参考文档。
创建 CarAppService 和 Session
您的应用需要扩展 CarAppService
类并实现 CarAppService.onCreateSession
方法,该方法会返回一个 Session
实例,它对应于到主机的当前连接:
Kotlin
class HelloWorldService : CarAppService() { ... override fun onCreateSession(): Session { return HelloWorldSession() } ... }
Java
public final class HelloWorldService extends CarAppService { ... @Override @NonNull public Session onCreateSession() { return new HelloWorldSession(); } ... }
Session
实例负责返回要在应用首次启动时使用的 Screen
实例:
Kotlin
class HelloWorldSession : Session() { ... override fun onCreateScreen(intent: Intent): Screen { return HelloWorldScreen() } ... }
Java
public final class HelloWorldSession extends Session { ... @Override @NonNull public Screen onCreateScreen(@NonNull Intent intent) { return new HelloWorldScreen(); } ... }
若要处理汽车应用需要从应用主屏幕或着陆屏幕以外的屏幕启动的情况(例如处理深层链接),您可以使用 ScreenManager.push
,在应用从 onCreateScreen
返回前预先植入屏幕的返回堆栈。预先植入可让用户从应用显示的第一个屏幕导航回之前的屏幕。
创建启动屏幕
您可以通过定义扩展 Screen
类的类并实现 Screen.onGetTemplate
方法来创建由应用显示的屏幕,该方法会返回 Template
实例,它表示要在车载显示屏上显示的界面状态。
以下代码段展示了如何声明 Screen
,它使用 PaneTemplate
模板显示简单的“Hello world!”字符串:
Kotlin
class HelloWorldScreen(carContext: CarContext) : Screen(carContext) { override fun onGetTemplate(): Template { val row = Row.Builder().setTitle("Hello world!").build() val pane = Pane.Builder().addRow(row).build() return PaneTemplate.Builder(pane) .setHeaderAction(Action.APP_ICON) .build() } }
Java
public class HelloWorldScreen extends Screen { @NonNull @Override public Template onGetTemplate() { Row row = new Row.Builder().setTitle("Hello world!").build(); Pane pane = new Pane.Builder().addRow(row).build(); return new PaneTemplate.Builder(pane) .setHeaderAction(Action.APP_ICON) .build(); } }
CarContext 类
CarContext
类是可由 Session
和 Screen
实例访问的 ContextWrapper
子类,它可提供对汽车服务的访问,例如用于管理屏幕堆栈的 ScreenManager
、用于常规应用相关功能(例如访问 Surface
对象以绘制导航应用的地图)的 AppManager
,以及精细导航应用就导航元数据及其他导航相关事件与主机通信所用的 NavigationManager
。如需查看导航应用可用的库功能的详尽列表,请参阅访问导航模板部分。
CarContext
还提供了一些其他功能,例如允许从车载显示屏使用配置加载可绘制资源、使用 intent 在汽车中启动应用,以及指示导航应用是否应在深色模式下显示其地图。
实现屏幕导航
应用通常会呈现许多不同的屏幕,每个屏幕可能会利用不同的模板,用户可以在与屏幕中显示的界面交互时浏览这些屏幕。
ScreenManager
类提供了一个屏幕堆栈,您可以使用它来推送屏幕,当用户选择车载显示屏上的返回按钮或使用某些汽车中提供的硬件返回按钮时,可以自动弹出这些屏幕。
以下代码段展示了如何向消息模板添加返回操作,以及在用户选择新屏幕时推入该屏幕的操作:
Kotlin
val template = MessageTemplate.Builder("Hello world!") .setHeaderAction(Action.BACK) .addAction( Action.Builder() .setTitle("Next screen") .setOnClickListener { screenManager.push(NextScreen(carContext)) } .build()) .build()
Java
MessageTemplate template = new MessageTemplate.Builder("Hello world!") .setHeaderAction(Action.BACK) .addAction( new Action.Builder() .setTitle("Next screen") .setOnClickListener( () -> getScreenManager().push(new NextScreen(getCarContext()))) .build()) .build();
Action.BACK
对象是自动调用 ScreenManager.pop
的标准 Action
。可通过使用 CarContext
提供的 OnBackPressedDispatcher
实例来替换此行为。
为了确保应用在汽车行驶过程中能够保障安全,屏幕堆栈的最大深度为 5 个屏幕。如需了解详情,请参阅模板限制。
刷新模板的内容
应用可通过调用 Screen.invalidate
方法来请求使 Screen
的内容无效。主机随后回调应用的 Screen.onGetTemplate
方法,以检索包含新内容的模板。
刷新 Screen
时,请务必了解模板中可更新的特定内容,以便主机不会将新模板计入模板配额。如需了解详情,请参阅模板限制。
建议您为屏幕设置适当的结构,以使 Screen
与其通过 Screen.onGetTemplate
实现返回的模板类型之间存在一对一的映射关系。
与用户互动
您的应用可以使用与移动应用类似的模式与用户互动。
处理用户输入
应用可通过将适当的监听器传递给支持它们的模型来响应用户输入。以下代码段展示了如何创建一个 Action
模型,该模型设置了一个 OnClickListener
,它会回调由应用代码定义的方法:
Kotlin
val action = Action.Builder() .setTitle("Navigate") .setOnClickListener(::onClickNavigate) .build()
Java
Action action = new Action.Builder() .setTitle("Navigate") .setOnClickListener(this::onClickNavigate) .build();
然后,onClickNavigate
方法可使用 CarContext.startCarApp
方法启动默认的汽车导航应用:
Kotlin
private fun onClickNavigate() { val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address)) carContext.startCarApp(intent) }
Java
private void onClickNavigate() { Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address)); getCarContext().startCarApp(intent); }
如需详细了解如何启动应用(包括 ACTION_NAVIGATE
intent 的格式),请参阅使用 intent 启动汽车应用。
某些操作(例如那些需要引导用户在其移动设备上继续交互的操作)只有在汽车停好后才允许执行。您可以使用 ParkedOnlyOnClickListener
实现这些操作。如果汽车没有停好,主机会向用户显示一条消息,指出在这种情况下不允许执行该操作。如果汽车已停好,代码就会正常执行。以下代码段展示了如何使用 ParkedOnlyOnClickListener
在移动设备上打开设置屏幕:
Kotlin
val row = Row.Builder() .setTitle("Open Settings") .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone)) .build()
Java
Row row = new Row.Builder() .setTitle("Open Settings") .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone)) .build();
显示通知
发送到移动设备的通知只有在使用 CarAppExtender
扩展后才会显示在车载显示屏上。某些通知属性(例如内容标题、文字、图标和操作)可以在 CarAppExtender
中设置,从而在通知显示在车载显示屏上时替换其属性。
以下代码段展示了如何向车载显示屏发送一条通知,让其显示的标题不同于移动设备上显示的标题:
Kotlin
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(titleOnThePhone) .extend( CarAppExtender.Builder() .setContentTitle(titleOnTheCar) ... .build()) .build()
Java
Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(titleOnThePhone) .extend( new CarAppExtender.Builder() .setContentTitle(titleOnTheCar) ... .build()) .build();
通知可能会影响界面的以下几个部分:
- 可能会向用户显示浮动通知 (HUN)。
- 可能会在通知中心添加一个条目,并且选择性地在侧边栏显示一个标志。
- 对于导航应用,通知可能会显示在侧边栏微件中,如精细导航通知中所述。
应用可以通过使用通知的优先级,选择如何配置通知以影响每个界面元素,如 CarAppExtender
文档中所述。
如果调用 NotificationCompat.Builder.setOnlyAlertOnce
且将值设置为 true
,则高优先级通知将只显示为 HUN 一次。
如需详细了解如何设计汽车应用的通知,请参阅通知。
显示消息框
应用可以使用 CarToast
显示消息框,如以下代码段所示:
Kotlin
CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()
Java
CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();
请求权限
如果您的应用需要访问受限数据或操作(例如,获取位置信息访问权限),也应遵循 Android 权限的标准规则。如需请求权限,您可以使用 CarContext.requestPermissions()
方法。在 Android Auto 上,系统会在手机上向用户显示权限对话框。
与使用标准 Android API 相比,使用 CarContext.requestPermissions()
的优势在于,您无需仅出于创建权限对话框的目的启动自己的 Activity
。Android Automotive OS 支持会在今年晚些时候推出,届时您将可以在 Android Auto 和 Android Automotive OS 上使用相同的代码,无需创建依赖于平台的流程。
使用 intent 启动汽车应用
您可以调用 CarContext.startCarApp
方法来执行以下某项操作:
- 打开拨号器拨打电话。
- 使用默认汽车导航应用开始精细导航到某个位置。
- 使用 intent 启动您自己的应用。
以下示例展示了如何创建一条通知,该通知包含一项操作,即打开应用中显示停车预订详情的屏幕。您可以使用内容 intent 扩展通知实例,该 intent 包含 PendingIntent
,它将显式 intent 封装到应用的操作中:
Kotlin
val notification = notificationBuilder ... .extend( CarAppExtender.Builder() .setContentIntent( PendingIntent.getBroadcast( context, ACTION_VIEW_PARKING_RESERVATION.hashCode(), Intent(ACTION_VIEW_PARKING_RESERVATION) .setComponent(ComponentName(context, MyNotificationReceiver::class.java)), 0)) .build())
Java
Notification notification = notificationBuilder ... .extend( new CarAppExtender.Builder() .setContentIntent( PendingIntent.getBroadcast( context, ACTION_VIEW_PARKING_RESERVATION.hashCode(), new Intent(ACTION_VIEW_PARKING_RESERVATION) .setComponent(new ComponentName(context, MyNotificationReceiver.class)), 0)) .build());
应用还必须声明 BroadcastReceiver
,当用户在通知界面中选择相应的操作并使用包含数据 URI 的 intent 调用 CarContext.startCarApp
时,会调用该类来处理 intent:
Kotlin
class MyNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val intentAction = intent.action if (ACTION_VIEW_PARKING_RESERVATION == intentAction) { CarContext.startCarApp( intent, Intent(Intent.ACTION_VIEW) .setComponent(ComponentName(context, MyCarAppService::class.java)) .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction))) } } }
Java
public class MyNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String intentAction = intent.getAction(); if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) { CarContext.startCarApp( intent, new Intent(Intent.ACTION_VIEW) .setComponent(new ComponentName(context, MyCarAppService.class)) .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction))); } } }
最后,应用中的 Session.onNewIntent
方法通过在堆栈上推入停车预订屏幕(如果还没有在顶部)来处理此 intent:
Kotlin
override fun onNewIntent(intent: Intent) { val screenManager = carContext.getCarService(ScreenManager::class.java) val uri = intent.data if (uri != null && MY_URI_SCHEME == uri.scheme && MY_URI_HOST == uri.schemeSpecificPart && ACTION_VIEW_PARKING_RESERVATION == uri.fragment ) { val top = screenManager.top if (top !is ParkingReservationScreen) { screenManager.push(ParkingReservationScreen(carContext)) } } }
Java
@Override public void onNewIntent(@NonNull Intent intent) { ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class); Uri uri = intent.getData(); if (uri != null && MY_URI_SCHEME.equals(uri.getScheme()) && MY_URI_HOST.equals(uri.getSchemeSpecificPart()) && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment()) ) { Screen top = screenManager.getTop(); if (!(top instanceof ParkingReservationScreen)) { screenManager.push(new ParkingReservationScreen(getCarContext())); } } }
如需详细了解如何处理汽车应用的通知,请参阅显示通知。
模板限制
主机将针对给定任务显示的模板数限制为最多 5 个,在这 5 个模板中,最后一个模板必须是以下某种类型:
请注意,此限制适用于模板数,而不是堆栈中的 Screen
实例数。例如,如果在屏幕 A 中,应用发送了 2 个模板,然后推送屏幕 B,那么它现在可以再发送 3 个模板。或者,如果将每个屏幕的结构都设置为发送单个模板,那么应用可以将 5 个屏幕实例推送到 ScreenManager
堆栈上。
这些限制有一些特殊情况:模板刷新、返回和重置操作。
模板刷新
某些内容更新不计入模板限制。一般来说,只要应用推送的新模板所属的类型及其包含的主要内容与之前的模板相同,就不会将新模板计入配额。例如,更新 ListTemplate
中某一行的切换状态不会计入配额。如需详细了解可将哪些类型的内容更新视为刷新,请参阅各个模板的文档。
返回操作
为了在任务中启用子流,主机会检测应用何时从 ScreenManager
堆栈中弹出 Screen
,并根据应用倒退的模板数更新剩余配额。
例如,如果在屏幕 A 中,应用发送了 2 个模板,然后推送屏幕 B 并且又发送了 2 个模板,那么应用的剩余配额就为 1。如果应用现在弹回到屏幕 A,主机会将配额重置为 3,因为应用倒退了 2 个模板。
请注意,当弹回到某个屏幕时,应用发送的模板所属的类型必须与该屏幕上次发送的模板的类型相同。发送其他任何类型的模板都会导致错误。不过,只要类型在返回操作期间保持不变,应用就可以随意修改模板的内容,而不会影响配额。
重置操作
某些模板具有表示任务结束的特殊语义。例如,NavigationTemplate
是一个视图,它应该会持续显示在屏幕上,并使用新的精细导航指示进行刷新,以供用户使用。到达其中一个模板时,主机会重置模板配额,将该模板当作新任务的第一步来对待,从而使应用能够开始新任务。如需了解哪些模板会在主机上触发重置操作,请参阅各个模板的文档。
如果主机收到通过通知操作或从启动器启动应用的 intent,也会重置配额。此机制使应用能够从通知开始新任务流,即使应用已绑定且在前台运行,也是如此。
如需详细了解如何在车载显示屏上显示应用的通知,请参阅显示通知;如需了解如何通过通知操作启动应用,请参阅使用 intent 启动汽车应用。
添加登录流程
如果您的应用为用户提供登录体验,您可以配合使用 SignInTemplate
和 LongMessageTemplate
等模板以及汽车应用 API 2 及更高级别,以处理在汽车的车机上登录您应用的操作。
如需创建 SignInTemplate
,您必须定义一个 SignInMethod
。汽车应用库目前支持以下三种登录方法:
InputSignInMethod
:用于用户名/密码登录PinSignInMethod
:用于 PIN 码登录,对于这种方法,用户可以使用车机上显示的 PIN 码从手机上关联其帐号ProviderSignInMethod
:用于提供商登录,例如 Google 登录
举例来说,如需实现收集用户密码的模板,请首先创建 InputCallback
来处理和验证用户输入:
Kotlin
val callback = object : InputCallback { override fun onInputSubmitted(text: String) { // You will receive this callback when the user presses enter on the keyboard. } override fun onInputTextChanged(text: String) { // You will receive this callback as the user is typing. The update frequency is determined by the host. } }
Java
InputCallback callback = new InputCallback() { @Override public void onInputSubmitted(@NonNull String text) { // You will receive this callback when the user presses enter on the keyboard. } @Override public void onInputTextChanged(@NonNull String text) { // You will receive this callback as the user is typing. The update frequency is determined by the host. } };
InputSignInMethod
Builder
需要 InputCallback
。
Kotlin
val passwordInput = InputSignInMethod.Builder(callback) .setHint("Password") .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD) ... .build()
Java
InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback) .setHint("Password") .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD) ... .build();
最后,使用您的新 InputSignInMethod
创建 SignInTemplate
。
Kotlin
SignInTemplate.Builder(passwordInput) .setTitle("Sign in with username and password") .setInstructions("Enter your password") .setHeaderAction(Action.BACK) ... .build()
Java
new SignInTemplate.Builder(passwordInput) .setTitle("Sign in with username and password") .setInstructions("Enter your password") .setHeaderAction(Action.BACK) ... .build();
添加文本字符串变体
不同尺寸的车载显示屏可以显示不同的文本量。利用汽车应用 API 2 及更高级别,您可以为文本字符串指定多个变体,以最佳适配屏幕。如需了解接受文本变体的位置,请查找采用 CarText
的模板和组件。
您可以使用 CarText.Builder.addVariant()
方法将文本字符串变体添加到 CarText
:
Kotlin
val itemTitle = CarText.Builder("This is a very long string") .addVariant("Shorter string") ... .build()
Java
CarText itemTitle = new CarText.Builder("This is a very long string") .addVariant("Shorter string") ... .build();
然后,您可以将此 CarText
(举例来说)用作 GridItem
的主要文本。
Kotlin
GridItem.Builder() .addTitle(itemTitle) ... .build()
Java
new GridItem.Builder() .addTitle(itemTitle) ... build();
按照优先级从高到低的顺序(例如从最长到最短)添加字符串。主机会根据车载显示屏上的可用空间大小选择相应长度的字符串。
CarAppService、Session 和 Screen 的生命周期
Session
和 Screen
类实现了 LifecycleOwner
接口。当用户与应用交互时,系统将调用 Session
和 Screen
对象的生命周期回调,如下图所示。
CarAppService 和 Session 的生命周期

Session
生命周期。如需了解完整详情,请参阅 Session.getLifecycle
方法的文档。
Screen 的生命周期

Screen
生命周期。如需了解完整详情,请参阅 Screen.getLifecycle
的文档。
测试库
Android for Cars 测试库提供了一些辅助类,可用于在测试环境中验证应用的行为。例如,借助 SessionController
可以模拟与主机的连接,验证是否创建并返回正确的 Screen
和 Template
。
请参阅示例,查看使用示例。
报告 Android for Cars 应用库问题
如果您发现该库存在问题,请使用 Google 问题跟踪器报告该问题。请务必在问题模板中填写所有必填信息。
在提交新问题之前,请先查看该问题是否已在库的版本说明中列出或在问题列表中报告。您可以在跟踪器中点击问题的星标来对问题进行订阅和投票。如需了解详情,请参阅订阅问题。