本页面详细介绍了车载应用库的不同功能,您可以使用这些功能来实现您的逐向导航应用。
在清单中声明导航支持
您的导航应用需要在其 CarAppService
的 intent 过滤器中声明 androidx.car.app.category.NAVIGATION
车载应用类别。
<application>
...
<service
...
android:name=".MyNavigationCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.NAVIGATION"/>
</intent-filter>
</service>
...
</application>
支持导航 intent
多种 intent 格式使导航应用能够与其他应用(如兴趣点应用和语音助手)协同工作。
要支持这些 intent 格式,首先需要通过在应用的清单中添加 intent 过滤器来声明支持。这些 intent 过滤器的位置取决于平台:
- Android Auto:在
<activity>
清单元素中,用于处理用户未使用 Android Auto 时 intent 的Activity
。 - Android Automotive OS:在
<activity>
清单元素中,用于CarAppActivity
。
然后,在应用的 Session
实现中,读取并处理 onCreateScreen()
和 onNewIntent()
回调中的 intent。
必需的 intent 格式
为了满足 NF-6
质量要求,您的应用必须处理导航 intent。
可选的 intent 格式
您还可以支持以下 intent 格式,以进一步提高应用的互操作性:
访问导航模板
导航应用可以访问以下模板,这些模板在后台显示带有地图的界面,并在主动导航期间显示逐向导航方向。
NavigationTemplate
:还在主动导航期间显示可选的信息性消息和行程估算。MapWithContentTemplate
:一个模板,允许应用渲染带有某种内容(例如列表)的地图图块。内容通常以叠加层形式渲染在地图图块之上,地图可见且稳定区域会根据内容进行调整。
有关如何使用这些模板设计导航应用用户界面的更多详细信息,请参阅导航应用。
要访问导航模板,您的应用需要在其 AndroidManifest.xml
文件中声明 androidx.car.app.NAVIGATION_TEMPLATES
权限。
<manifest ...>
...
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>
...
</manifest>
额外的权限是绘制地图必需的。
迁移到 MapWithContentTemplate
从车载应用 API 级别 7 开始,MapTemplate
、PlaceListNavigationTemplate
和 RoutePreviewNavigationTemplate
已弃用。弃用模板将继续受支持,但强烈建议迁移到 MapWithContentTemplate
。
这些模板提供的功能可以使用 MapWithContentTemplate
实现。请参阅以下代码段以获取示例:
MapTemplate
Kotlin
// MapTemplate (deprecated) val template = MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(mapController) .build()
Java
// MapTemplate (deprecated) MapTemplate template = new MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) build()) .setActionStrip(actionStrip) .setMapController(mapController) .build();
PlaceListNavigationTemplate
Kotlin
// PlaceListNavigationTemplate (deprecated) val template = PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// PlaceListNavigationTemplate (deprecated) PlaceListNavigationTemplate template = new PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
RoutePreviewNavigationTemplate
Kotlin
// RoutePreviewNavigationTemplate (deprecated) val template = RoutePreviewNavigationTemplate.Builder() .setItemList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .build()) .build()) .setHeader(header) .setNavigateAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .addAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .build()) .build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// RoutePreviewNavigationTemplate (deprecated) RoutePreviewNavigationTemplate template = new RoutePreviewNavigationTemplate.Builder() .setItemList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .build()) .build()) .setHeader(header) .setNavigateAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .addAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .build()) .build())) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
通信导航元数据
导航应用必须与主机通信额外的导航元数据。主机使用这些信息向车辆主机提供信息,并防止导航应用争用共享资源。
导航元数据通过可从 CarContext
访问的 NavigationManager
车载服务提供。
Kotlin
val navigationManager = carContext.getCarService(NavigationManager::class.java)
Java
NavigationManager navigationManager = carContext.getCarService(NavigationManager.class);
启动、结束和停止导航
为了让主机管理多个导航应用、路径通知和车辆仪表盘数据,它需要了解当前的导航状态。当用户开始导航时,调用 NavigationManager.navigationStarted
。同样,当导航结束时(例如,当用户到达目的地或用户取消导航时),调用 NavigationManager.navigationEnded
。
仅当用户完成导航时才调用 NavigationManager.navigationEnded
。例如,如果您需要在行程中途重新计算路线,请改用 Trip.Builder.setLoading(true)
。
有时,主机需要应用停止导航,并会通过应用通过 NavigationManager.setNavigationManagerCallback
提供的 NavigationManagerCallback
对象调用 onStopNavigation
。然后,应用必须停止在仪表盘显示屏、导航通知和语音导航中发出下一转弯信息。
更新行程信息
在主动导航期间,调用 NavigationManager.updateTrip
。此调用中提供的信息可供车辆的仪表盘和抬头显示屏使用。根据所驾驶的特定车辆,并非所有信息都会显示给用户。例如,桌面主机 (DHU) 显示添加到 Trip
的 Step
,但不显示 Destination
信息。
绘制到仪表盘显示屏
为了提供最沉浸式的用户体验,您可能希望在车辆的仪表盘显示屏上显示基本元数据之外进行扩展。从车载应用 API 级别 6 开始,导航应用可以选择直接在仪表盘显示屏(在受支持的车辆中)上渲染自己的内容,但有以下限制:
- 仪表盘显示屏 API 不支持输入控件。
- 车载应用质量准则
NF-9
:仪表盘显示屏应仅显示地图图块。这些图块上可以选择性地显示活动导航路线。 - 仪表盘显示屏 API 仅支持使用
NavigationTemplate
。- 与主显示屏不同,仪表盘显示屏可能无法一致地显示所有
NavigationTemplate
UI 元素,例如逐向指令、预计到达时间卡片和操作。地图图块是唯一一致显示的 UI 元素。
- 与主显示屏不同,仪表盘显示屏可能无法一致地显示所有
声明仪表盘支持
为了让主机应用知道您的应用支持在仪表盘显示屏上进行渲染,您必须将 androidx.car.app.category.FEATURE_CLUSTER
<category>
元素添加到您的 CarAppService
的 <intent-filter>
中,如以下代码段所示:
<application> ... <service ... android:name=".MyNavigationCarAppService" android:exported="true"> <intent-filter> <action android:name="androidx.car.app.CarAppService" /> <category android:name="androidx.car.app.category.NAVIGATION"/> <category android:name="androidx.car.app.category.FEATURE_CLUSTER"/> </intent-filter> </service> ... </application>
生命周期和状态管理
从 API 级别 6 开始,车载应用生命周期流程保持不变,但现在 CarAppService::onCreateSession
接受类型为 SessionInfo
的参数,该参数提供有关正在创建的 Session
的额外信息(即显示类型和支持的模板集)。
应用可以选择使用相同的 Session
类来处理仪表盘和主显示屏,或者创建特定于显示屏的 Sessions
以自定义每个显示屏上的行为(如以下代码段所示)。
Kotlin
override fun onCreateSession(sessionInfo: SessionInfo): Session { return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) { ClusterSession() } else { MainDisplaySession() } }
Java
@Override @NonNull public Session onCreateSession(@NonNull SessionInfo sessionInfo) { if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_CLUSTER) { return new ClusterSession(); } else { return new MainDisplaySession(); } }
无法保证何时或是否提供仪表盘显示屏,仪表盘 Session
也可能成为唯一的 Session
(例如,用户在您的应用正在主动导航时将主显示屏切换到另一个应用)。“标准”约定是,只有在调用 NavigationManager::navigationStarted
之后,应用才能获得对仪表盘显示屏的控制权。但是,应用可能在没有主动导航发生时获得仪表盘显示屏,或者从未获得仪表盘显示屏。您的应用需要通过渲染应用的地图图块空闲状态来处理这些场景。
主机为每个 Session
创建单独的 binder 和 CarContext
实例。这意味着,在使用 ScreenManager::push
或 Screen::invalidate
等方法时,只会影响调用它们的 Session
。如果需要跨 Session
通信(例如,通过使用广播、共享单例或其他方式),应用应在这些实例之间创建自己的通信通道。
测试仪表盘支持
您可以在 Android Auto 和 Android Automotive OS 上测试您的实现。对于 Android Auto,这是通过将桌面主机配置为模拟辅助仪表盘显示屏来完成的。对于 Android Automotive OS,API 级别 30 及更高的通用系统映像会模拟仪表盘显示屏。
使用文本或图标自定义 TravelEstimate
要使用文本、图标或两者自定义行程估算,请使用 TravelEstimate.Builder
类的 setTripIcon
或 setTripText
方法。NavigationTemplate
使用 TravelEstimate
可选地设置文本和图标,以替代或与预计到达时间、剩余时间、剩余距离一起显示。

以下代码段使用 setTripIcon
和 setTripText
自定义行程估算:
Kotlin
TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build()
Java
new TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build();
提供逐向通知
使用频繁更新的导航通知提供逐向 (TBT) 导航指令。要在车载屏幕中被视为导航通知,您的通知构建器必须执行以下操作:
- 使用
NotificationCompat.Builder.setOngoing
方法将通知标记为正在进行。 - 将通知类别设置为
Notification.CATEGORY_NAVIGATION
。 - 使用
CarAppExtender
扩展通知。
导航通知会显示在车载屏幕底部的导轨微件中。如果通知的重要性级别设置为 IMPORTANCE_HIGH
,它还会显示为浮动通知 (HUN)。如果未使用 CarAppExtender.Builder.setImportance
方法设置重要性,则使用通知渠道的重要性。
应用可以在 CarAppExtender
中设置一个 PendingIntent
,当用户点击 HUN 或导轨微件时,该 Intent 会发送到应用。
如果使用 true
值调用 NotificationCompat.Builder.setOnlyAlertOnce
,则高重要性通知在 HUN 中仅提醒一次。
以下代码段演示了如何构建导航通知:
Kotlin
NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), Intent(ACTION_OPEN_APP).setComponent( ComponentName(context, MyNotificationReceiver::class.java)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build()
Java
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( new CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), new Intent(ACTION_OPEN_APP).setComponent( new ComponentName(context, MyNotificationReceiver.class)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build();
定期更新 TBT 通知以反映距离变化,这会更新导轨微件,并且仅将通知显示为 HUN。您可以通过使用 CarAppExtender.Builder.setImportance
设置通知的重要性来控制 HUN 行为。将重要性设置为 IMPORTANCE_HIGH
会显示 HUN。将其设置为任何其他值只会更新导轨微件。
刷新 PlaceListNavigationTemplate 内容
您可以让驾驶员在浏览使用 PlaceListNavigationTemplate
构建的地点列表时,通过轻触按钮刷新内容。要启用列表刷新,请实现 OnContentRefreshListener
接口的 onContentRefreshRequested
方法,并使用 PlaceListNavigationTemplate.Builder.setOnContentRefreshListener
在模板上设置监听器。
以下代码段演示了如何设置模板上的监听器:
Kotlin
PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate() } .build()
Java
new PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener(() -> { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate(); }) .build();
刷新按钮仅在 PlaceListNavigationTemplate
的标题中显示,如果监听器有值。
当用户点击刷新按钮时,会调用您的 OnContentRefreshListener
实现的 onContentRefreshRequested
方法。在 onContentRefreshRequested
中,调用 Screen.invalidate
方法。然后主机回调您的应用的 Screen.onGetTemplate
方法,以检索带有刷新内容的模板。有关刷新模板的更多信息,请参阅刷新模板内容。只要 onGetTemplate
返回的下一个模板是相同类型,它就计为一次刷新,并且不计入模板配额。
提供音频导航
要通过车载扬声器播放导航引导,您的应用必须请求音频焦点。作为 AudioFocusRequest
的一部分,将用途设置为 AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
。此外,将焦点增益设置为 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
。
模拟导航
为了在将您的应用提交到 Google Play 商店时验证其导航功能,您的应用必须实现 NavigationManagerCallback.onAutoDriveEnabled
回调。当此回调被调用时,您的应用必须在用户开始导航时模拟导航到所选目的地。当当前 Session
的生命周期达到 Lifecycle.Event.ON_DESTROY
状态时,您的应用可以退出此模式。
您可以通过从命令行执行以下操作来测试您的 onAutoDriveEnabled
实现是否被调用:
adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE
示例如下:
adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE
默认导航车载应用
在 Android Auto 中,默认导航车载应用对应于用户上次启动的导航应用。当用户通过 Assistant 调用导航命令或当另一个应用发送 intent 以启动导航时,默认应用会接收导航 intent。
显示情境导航警报
Alert
向驾驶员显示重要信息,并可选择操作,而无需离开导航屏幕的上下文。为了给驾驶员提供最佳体验,Alert
在 NavigationTemplate
内工作,以避免阻挡导航路线并最大程度地减少驾驶员分心。
Alert
仅在 NavigationTemplate
内可用。要在 NavigationTemplate
之外通知用户,请考虑使用浮动通知 (HUN),如显示通知中所述。
例如,使用 Alert
来:
- 通知驾驶员与当前导航相关的更新,例如交通状况变化。
- 请求驾驶员提供与当前导航相关的更新,例如是否存在测速陷阱。
- 提议即将到来的任务并询问驾驶员是否接受,例如驾驶员是否愿意在途中接人。
Alert
的基本形式包含标题和 Alert
持续时间。持续时间由进度条表示。您可以选择添加副标题、图标以及最多两个 Action
对象。

一旦 Alert
显示,如果驾驶员交互导致离开 NavigationTemplate
,它不会延续到另一个模板。它会停留在原始 NavigationTemplate
中,直到 Alert
超时、用户采取行动或应用解除 Alert
。
创建警报
使用 Alert.Builder
创建 Alert
实例:
Kotlin
Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build()
Java
new Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build();
如果您想监听 Alert
取消或解除,请创建 AlertCallback
接口的实现。 AlertCallback
的调用路径如下:
如果
Alert
超时,主机将使用AlertCallback.REASON_TIMEOUT
值调用AlertCallback.onCancel
方法。然后它会调用AlertCallback.onDismiss
方法。如果驾驶员点击其中一个操作按钮,主机将调用
Action.OnClickListener
,然后调用AlertCallback.onDismiss
。如果不支持
Alert
,主机将使用AlertCallback.REASON_NOT_SUPPORTED
值调用AlertCallback.onCancel
。主机不会调用AlertCallback.onDismiss
,因为Alert
未显示。
配置警报持续时间
选择一个与您的应用需求相匹配的 Alert
持续时间。导航 Alert
的推荐持续时间为 10 秒。有关更多信息,请参阅导航警报。
显示警报
要显示 Alert
,请调用通过应用的 CarContext
可用的 AppManager.showAlert
方法。
// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)
- 使用与当前显示的
Alert
ID 相同的alertId
调用showAlert
不会执行任何操作。Alert
不会更新。要更新Alert
,您必须使用新的alertId
重新创建它。 - 使用与当前显示的
Alert
的alertId
不同的Alert
调用showAlert
会解除当前显示的Alert
。
解除警报
虽然 Alert
会因超时或驾驶员交互而自动解除,您也可以手动解除 Alert
,例如当其信息过时时。要解除 Alert
,请使用 Alert
的 alertId
调用 dismissAlert
方法。
// Dismiss the same alert
carContext.getCarService(AppManager.class).dismissAlert(alert.getId())
使用与当前显示的 Alert
不匹配的 alertId
调用 dismissAlert
不会执行任何操作,也不会抛出异常。