Android for Cars应用库可以帮助实现汽车导航、停车和充电应用。为此,它提供了一套模板,这些模板符合防止驾驶员分心的标准,同时它还解决了一些细节问题,例如存在各种车载显示屏类型和输入模式。
(1)关键术语和概念
1)模型和模板
界面由模型对象的图来表示,这些模型对象可以按照它们所属的模板允许的不同方式排列在一起。模板是模型的子集,它们可以在这些图中充当根。模型包含要以文字和图片的形式显示给用户的信息,以及用于配置此类信息的视觉外观各个方面(例如,文字颜色或图片大小)的属性。主机会将模型转换为符合防止驾驶员分心标准的视图,还解决了一些细节问题,例如存在各种车载显示屏类型和输入模式。
2)主机
主机是一个后端组件,它会实现库的API提供的功能,以便应用在汽车中运行。从发现应用并管理其生命周期,到将模型转换为视图,再到将用户交互操作通知给应用,这些都属于主机的职责范围。在移动设备上,此主机由Android Auto实现。
3)模板限制
不同的模板会对其模型的内容施加限制。例如,列表模板对可以呈现给用户的项数有限制。模板对可以采用什么方式连接它们以形成任务流也有限制。例如,应用最多只能将5个模板推送到屏幕堆栈。
4)Screen
Screen是一个由库提供的类,应用实现该类来管理呈现给用户的界面。Screen具有生命周期,并提供了一种机制,可让应用发送要在屏幕可见时显示的模板。此外,也可以将Screen实例推送到屏幕堆栈以及从屏幕堆栈中弹出这些实例,这样可以确保它们遵循模板流限制。
5)CarAppService
CarAppService是一个抽象Service类,应用必须实现并导出该类才能被主机发现并由主机进行管理。应用的CarAppService负责使用CarAppService.createHostidator 验证主机连接是否可以信任,随后使用CarAppService. onCreateSession为每个连接提供Session实例。
6)Session
Session是一个抽象类,应用必须使用CarAppService.onCreateSession实现并返回该类。它充当在车载显示屏上显示信息的入口点,并且具有生命周期,可告知车载显示屏上应用的当前状态,例如当应用可见或隐藏时。
当Session开始时(例如当应用首次启动时),主机会使用Session.onCreateScreen方法请求要显示的初始 Screen。
(2)配置应用的清单文件
1)声明CarAppService
主机通过CarAppService实现连接到用户的应用。应在清单中声明此服务,以允许主机发现并连接到用户的应用。
还需要在应用的Intent过滤器的category元素中声明应用的类别。
以下代码展示如何在清单中声明停车应用的汽车应用服务:
2)支持的应用类别
为了能够在Play商店中的Android Auto板块上架,应用需要属于某个支持的汽车应用类别。声明汽车应用服务时,可以通过在Intent过滤器中添加以下一个或多个支持的类别值来声明应用的类别:
·androidx.car.app.category.NAVIGATION:此类应用提供精细导航方向。
·androidx.car.app.category.PARKING:此类应用提供与查找停车位相关的功能。
·androidx.car.app.category.CHARGING:此类应用提供与查找电动车辆充电站相关的功能。
注意: 汽车应用类别与Google Play商店中上架应用所选择的类别无关,后者用于帮助用户在Play商店中发现最相关的应用。
3)指定应用名称和图标
需要指定应用名称和图标,主机可以使用它们在系统界面中表示用户的应用。
可以使用CarAppService的label和icon元素来指定用于表示应用的应用名称和图标,代码如下:
如果未在service元素中声明标签或图标,主机将回退到使用为应用指定的值。
4)设置应用的minSdkVersion
Android Auto要求用户的应用以Android 6.0(API级别23)或更高版本为目标平台。
如需在项目中指定此值,请在手机应用模块的AndroidManifest.xml文件中将uses-sdk元素中的minSdkVersion属性设置为23或更高版本,如以下代码所示:
5)声明Android Auto支持
Android Auto主机会检查应用是否已声明支持Android Auto。如需启用此支持,请在应用的清单中添加以下条目:
此清单条目引用应创建的另一个XML文件,其路径为YourAppProject/app/src/main/res/xml/automotive_app_desc.xml,该文件中声明了应用支持的Android Auto功能。
使用Android for Cars应用库的应用必须在automotive_app_desc. xml文件中声明template功能:
(3)创建CarAppService和Session
应用需要扩展CarAppService类并实现CarAppService.onCreateSession方法,该方法会返回一个Session实例,它对应于主机的当前连接:
Session实例负责返回要在应用首次启动时使用的Screen实例:
若要处理汽车应用需要从应用主屏幕或者主屏幕以外的屏幕启动的情况(例如处理深层链接),可以使用ScreenManager.push,在应用从onCreateScreen返回前预先植入屏幕的返回堆栈。预先植入可让用户从应用显示的第一个屏幕导航回到之前的屏幕。
(4)创建启动屏幕
可以通过定义扩展Screen类的类并实现Screen.onGetTemplate方法来创建由应用显示的屏幕,该方法会返回Template实例,它表示要在车载显示屏上显示的界面状态。
以下代码段展示了如何声明Screen,它使用PaneTemplate模板显示简单的“Hello world!”字符串:
(5)CarContext类
CarContext类是可由Session和Screen实例访问的ContextWrapper子类,它可提供对汽车服务的访问,例如用于管理屏幕堆栈的ScreenManager、用于常规应用相关功能(例如访问 Surface对象以绘制导航应用的地图)的AppManager,以及精细导航应用就导航元数据及其他导航相关事件与主机通信所用的NavigationManager。
CarContext还提供了一些其他功能,例如允许从车载显示屏使用配置加载可绘制资源、使用Intent在汽车中启动应用,以及指示导航应用是否应在深色模式下显示其地图。
(6)实现屏幕导航
应用通常会呈现许多不同的屏幕,每个屏幕可能会利用不同的模板,用户可以在与屏幕中显示的界面交互时浏览这些屏幕。
ScreenManager类提供了一个屏幕堆栈,可以使用它来推送屏幕,当用户选择车载显示屏上的返回按钮或使用某些汽车中提供的硬件返回按钮时,可以自动弹出这些屏幕。
以下代码段展示了如何向消息模板添加返回操作,以及在用户选择新屏幕时推送该屏幕的操作:
Action. BACK对象是自动调用ScreenManager. pop的标准Action。可通过使用CarContext提供的OnBackPressedDispatcher实例来替换此行为。
为了确保应用在汽车行驶过程中能够保障安全,屏幕堆栈的最大深度为5个屏幕。
(7)刷新模板的内容
应用可通过调用Screen.inidate方法来请求使Screen的内容无效。主机随后回调应用的Screen.onGetTemplate方法,以检索包含新内容的模板。
刷新Screen时,请务必了解模板中可更新的特定内容,以便主机不会将新模板计入模板配额。
建议为屏幕设置适当的结构,以使Screen与其通过Screen.onGetTemplate实现返回的模板类型之间存在一对一的映射关系。
(8)处理用户输入
应用可通过将适当的监听器传递给支持它们的模型来响应用户输入。以下代码段展示了如何创建一个Action模型,该模型设置了一个OnClickListener,它会回调由应用代码定义的方法:
onClickNavigate方法可使用CarContext.startCarApp方法启动默认的汽车导航应用:
某些操作(例如那些需要引导用户在其移动设备上继续交互的操作)只有在汽车停好后才允许执行。可以使用ParkedOnlyOnClickListener实现这些操作。如果汽车没有停好,主机会向用户显示一条消息,指出在这种情况下不允许执行该操作。如果汽车已停好,代码就会正常执行。以下代码段展示了如何使用ParkedOnlyOnClickListener在移动设备上打开设置屏幕:
(9)显示通知
发送到移动设备的通知只有在使用CarAppExtender扩展后才会显示在车载显示屏上。某些通知属性(例如内容标题、文字、图标和操作)可以在CarAppExtender中设置,从而在通知显示在车载显示屏上时替换其属性。
以下代码段展示了如何向车载显示屏发送一条通知,让其显示的标题不同于移动设备上显示的标题:
通知可能会影响界面的以下几个部分:
·可能会向用户显示浮动通知(HUN)。
·可能会在通知中心添加一个条目,并且选择性地在侧边栏显示一个标志。
·对于导航应用,通知可能会显示在侧边栏微件中,如精细导航通知中所述。
应用可以通过使用通知的优先级,选择如何配置通知以影响每个界面元素,如CarAppExtender文档中所述。
如果调用NotificationCompat.Builder.setOnlyAlertOnce且将值设置为true,则高优先级通知将只显示为HUN一次。
注意 :库未提供仅向车载显示屏发送通知而不向移动设备发送通知的功能。
(10)显示消息框
应用可以使用CarToast显示消息框,如以下代码段所示:
(11)使用Intent启动汽车应用
可以调用CarContext.startCarApp方法来执行以下某项操作:
·打开拨号器拨打电话。
·使用默认汽车导航应用开始精确导航到某个位置。
·使用Intent启动自己的应用。
以下示例展示了如何创建一条通知,该通知包含一项操作,即打开应用中显示停车预订详情的屏幕。可以使用内容Intent扩展通知实例,该Intent包含PendingIntent,它将显式Intent封装到应用的操作中:
应用还必须声明BroadcastReceiver,当用户在通知界面中选择相应的操作并使用包含数据URI的Intent调用CarContext.startCarApp时,会调用该类来处理Intent:
最后,应用中的Session.onNewIntent方法通过在堆栈上推入停车预订屏幕(如果还没有在顶部)来处理此Intent:
(12)模板限制
主机将针对给定任务显示的模板数限制为最多5个,在这5个模板中,最后一个模板必须是以下某种类型:
·NavigationTemplate
·PaneTemplate
·MessageTemplate
请注意,此限制适用于模板数,而不是堆栈中的Screen实例数。例如,如果在屏幕A中,应用发送了2个模板,然后推送屏幕B,那么它现在可以再发送3个模板。或者,如果将每个屏幕的结构都设置为发送单个模板,那么应用可以将5个屏幕实例推送到ScreenManager堆栈上。
注意 :在使用Android Auto进行开发的过程中,先启用开发者模式,然后在开发者设置屏幕中选择启用叠加式调试界面,即可看到叠加在屏幕上的当前步骤数。
1)模板刷新
某些内容更新不计入模板限制。一般来说,只要应用推送的新模板所属的类型及其包含的主要内容与之前的模板相同,就不会将新模板计入配额。例如,更新ListTemplate中某一行的切换状态不会计入配额。
2)返回操作
为了在任务中启用子流,主机会检测应用何时从ScreenManager堆栈中弹出Screen,并根据应用倒退的模板数更新剩余配额。
例如,如果在屏幕A中,应用发送了2个模板,然后推送屏幕B并且又发送了2个模板,那么应用的剩余配额就为1。如果应用现在弹回到屏幕A,主机会将配额重置为3,因为应用倒退了2个模板。
请注意,当弹回到某个屏幕时,应用发送的模板所属的类型必须与该屏幕上次发送的模板的类型相同。发送其他任何类型的模板都会导致错误。不过,只要类型在返回操作期间保持不变,应用就可以随意修改模板的内容,而不会影响配额。
3)重置操作
某些模板具有表示任务结束的特殊语义。例如,NavigationTemplate是一个视图,它应该会持续显示在屏幕上,并使用新的精细导航指示进行刷新,以供用户使用。到达其中一个模板时,主机会重置模板配额,将该模板当作新任务的第一步来对待,从而使应用能够开始新任务。如需了解哪些模板会在主机上触发重置操作,请参阅各个模板的文档。
如果主机收到通过通知操作或从启动器启动应用的Intent,也会重置配额。此机制使应用能够从通知开始新任务流,即使应用已绑定且在前台运行,也是如此。
(13)构建停车或充电应用
1)在清单中声明类别支持
应用需要在其CarAppService的Intent过滤器中声明androidx. car. app. category.PARKING或 androidx.car.app.category.CHARGING汽车应用类别。例如:
2)访问地图模板
应用可以访问PlaceListMapTemplate,该模板专门用于在由主机渲染的地图上显示一系列感兴趣的地点。
为了能够访问此模板,应用需要在其AndroidManifest.xml中声明androidx.car.app.MAP_TEMPLATES权限:
(14)构建导航应用
1)在清单中声明导航支持
导航应用需要在其CarAppService的Intent过滤器中声明 androidx.car.app.category.NAVIGATION 汽车应用类别:
2)支持导航Intent
为了支持发送到应用的导航Intent(包括来自Google助理的语音查询导航Intent),应用需要在其Session. onCreateScreen和Session. onNewIntent中处理CarContext. ACTION_NAVIGATE intent。
3)访问导航模板
导航应用可以访问专为导航应用设计的以下模板。所有这些模板都会在后台显示一个Surface,应用可以访问该Surface以便绘制地图,这些模板还会显示应用提供的其他信息,这些信息因模板而异。
·NavigationTemplate:在有效导航期间,显示地图以及可选的信息性消息或路线方向和出行估计数据。
·PlaceListNavigationTemplate:显示地点列表,可以在地图上为这些地点绘制相应的标记。
·RoutePreviewNavigationTemplate:显示路线列表,可以选择其中一个路线并在地图上突出显示。
如需详细了解如何使用这些模板设计导航应用的界面,请参阅Android for Cars应用库设计准则。
为了能够访问导航模板,应用需要在其AndroidManifest.xml 中声明 androidx.car.app.NAVIGATION_TEMPLATES 权限:
4)绘制地图
导航应用可以访问Surface,以在相关模板上绘制地图。然后,可以通过将SurfaceCallback 实例设置为AppManager汽车服务来访问SurfaceContainer对象:
当SurfaceContainer可用时,SurfaceCallback会提供一个回调;当Surface的属性发生更改时,它还会提供其他回调。
为了能够访问Surface,应用需要在其AndroidManifest.xml中声明androidx.car.app.ACCESS_SURFACE权限:
5)地图的可见区域
主机可能会在地图上为不同的模板绘制界面元素。主机将通过调用 SurfaceCallback.onVisibleAreaChanged来告知保证不被遮挡而完全对用户可见的区域。此外,为了最大限度地减少更改次数,主机还会根据当前模板使用将会可见的最大矩形来调用SurfaceCallback.onStableAreaChanged方法。
例如,如果导航应用使用的是顶部带有操作栏的NavigationTemplate,当用户有一段时间没有与屏幕交互时,该操作栏可能会隐藏自身,以便为地图腾出更多空间。在这种情况下,将使用相同的矩形对onStableAreaChanged和onVisibleAreaChanged进行回调。当操作栏处于隐藏状态时,仅使用较大的区域调用onVisibleAreaChanged。如果用户与屏幕交互,则同样仅使用第一个矩形调用onVisibleAreaChanged。
6)深色模式
当主机确定条件允许时,导航应用必须使用适当的深色将地图重新绘制到Surface实例上,如Android Auto应用质量准则中所述。
为了决定是否应绘制深色地图,可以使用CarContext.isDarkMode方法。每当深色模式状态发生变化时,都会收到对Session.onCarConfigurationChanged的调用。
7)导航元数据
导航应用必须就额外的导航元数据与主机通信。主机利用这些信息向车机提供信息,并防止导航应用在共享资源上发生冲突。
导航元数据通过可从CarContext访问的NavigationManager汽车服务提供:
①开始、结束和停止导航:
为了让主机管理多个导航应用、路线通知和车辆仪表板数据,它需要了解导航的当前状态。当用户开始导航时,应用应调用NavigationManager.navigationStarted。同样,当导航结束时,例如当用户到达目的地或用户取消导航时,应用应调用NavigationManager.navigationEnded。
只有在用户完成导航时,才能调用NavigationManager.navigationEnded。例如,如果需要在行程中间重新计算路线,请改用Trip.Builder.setLoading(true)。
有时,主机需要应用停止导航,并将在应用通过NavigationManager.setListener提供的NavigationManagerListener对象中调用stopNavigation。然后,应用必须停止在仪表板屏幕、导航通知和语音导航中发出下一个转弯的信息。
②行程信息:
在有效导航期间,应用调用NavigationManager.updateTrip。此调用中提供的信息将用于车辆的仪表板和平视显示仪。并非所有信息都可以显示给用户,具体取决于驾驶的特定车辆。
为了测试信息是否到达仪表板,可以配置桌面车机(DHU)工具以显示简单的仪表板屏幕。创建一个包含以下内容的cluster.ini文件:
然后,可以使用一个额外的命令行参数调用 DHU:
8)精细导航通知
精细导航(TBT)指示可以通过频繁更新的导航通知来提供。为了在车载显示屏中被视为导航通知,通知的构建器必须执行以下操作:
·使用NotificationCompat.Builder.setOngoing方法将通知标记为持续性通知。
·将通知的类别设置为Notification.CATEGORY_NAVIGATION。
·使用CarAppExtender 扩展通知。
导航通知将显示在车载显示屏底部的侧边栏微件中。如果通知的重要性级别设置为IMPORTANCE_HIGH,它也会显示为浮动通知(HUN)。如果未使用CarAppExtender.Builder.setImportance方法设置重要性,将采用通知渠道的重要性。
应用可以在CarAppExtender中设置PendingIntent,当用户点按HUN或侧边栏微件时,会将其发送到应用。
如果调用NotificationCompat.Builder.setOnlyAlertOnce且将值设置为true,则高重要性通知将只以HUN的形式提醒一次。
以下代码段展示了如何构建导航通知:
·精细导航通知的准则。
导航应用应随着距离的变化定期更新TBT通知,这样会更新侧边栏微件,并且只将通知显示为HUN。应用可使用CarAppExtender.Builder.setImportance方法设置通知的重要性,由此控制HUN行为。将重要性设置为IMPORTANCE_HIGH将显示 HUN,将其设置为其他任何值都将只更新侧边栏微件。
注意 :当应用在车载显示屏上显示路线信息时(例如,在有效导航到目的地期间),会抑制TBT HUN。
9)语音导航
如需通过汽车扬声器播放导航指导,应用必须请求音频焦点。作为AudioFocusRequest的一部分,将用法设置为AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE。还应将焦点获取设置为AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。
10)模拟导航
为了在将应用提交到Google Play商店时验证其导航功能,应用必须实现NavigationManagerCallback.onAutoDriveEnabled回调。如果调用了此回调,当用户开始导航时,应用应模拟导航到选定的目的地。只要当前Session的生命周期达到Lifecycle.Event#ON_DESTROY状态,应用就可以退出此模式。
可以通过从命令行执行以下命令来测试是否调用了onAutoDriveEnabled实现:
11)默认汽车导航应用
在Android Auto中,默认汽车导航应用为用户最近启动的导航应用。例如,当用户通过助理调用导航命令或其他应用发送Intent以开始导航时,则就是接收导航Intent的应用。
(15)CarAppService、Session和Screen的生命周期
Session和Screen类实现了LifecycleOwner接口。当用户与应用交互时,系统将调用Session和 Screen对象的生命周期回调,如图1-2和图1-3所示。
(16)测试库
Android for Cars测试库提供了一些辅助类,可用于在测试环境中验证应用的行为。例如,借助SessionController可以模拟与主机的连接,验证是否创建并返回正确的Screen和Template。
(17)在真实的车机上运行应用
为了让应用在真实的车机(而不是提供的桌面车机)上运行,必须通过Google Play商店来分发应用。这样可确保应用经过测试和审查,遵循准则。这些准则可确保应用与汽车环境相关,并防止驾驶员分心。
图1-2 CarAppService、Session的生命周期
图1-3 Screen的生命周期
在开发过程中进行测试时,有3个选项可供选择:
·使用桌面车机。
·将应用推送到Google Play商店的internal test track。内部测试轨道允许手动添加团队,以便进行内部测试。发布到此轨道不需要Play商店审核。
·通过Google Play管理中心中的internal app sharing共享应用。与内部测试轨道类似,这不需要Play商店审核。
每当将APK发布到其他任何轨道(包括封闭式轨道)时,应用都会先经历审核流程,然后才被批准进入Play商店中的该轨道。如果应用未能通过审核流程,将收到有关为什么未通过的信息。此流程能够修复所有问题,以便符合准则。