在您的媒体应用中添加 Android Automotive OS 支持

Android Automotive OS 允许用户在汽车中安装应用。要覆盖此平台上的用户,您需要分发一个针对驾驶员优化的应用,该应用与 Android Automotive OS 兼容。您可以重用 Android Auto 应用中的几乎所有代码和资源,但必须创建满足此页面要求的单独构建。

开发概述

添加 Android Automotive OS 支持只需要几个步骤,如下面的部分所述

  1. 在 Android Studio 中启用汽车功能.
  2. 创建汽车模块.
  3. 更新您的 Gradle 依赖项.
  4. 可选地,实现设置和登录活动
  5. 可选地,读取媒体主机提示

设计注意事项

Android Automotive OS 负责布局从您的应用的媒体浏览器服务接收的媒体内容。这意味着当用户触发媒体播放时,您的应用不会绘制 UI 也不会启动任何活动。

如果您正在实现设置或登录活动,则这些活动必须针对车辆进行优化。在设计应用的这些区域时,请参考Android Automotive OS 设计指南

设置项目

您需要设置应用项目的几个部分才能启用对 Android Automotive OS 的支持。

在 Android Studio 中启用汽车功能

使用 Android Studio 4.0 或更高版本以确保启用所有 Automotive OS 功能。

创建汽车模块

Android Automotive OS 的某些组件(例如清单文件)具有特定于平台的要求。创建一个模块,可以将这些组件的代码与项目中的其他代码(例如用于手机应用的代码)分开。

按照以下步骤将汽车模块添加到您的项目中

  1. 在 Android Studio 中,点击**文件 > 新建 > 新建模块**。
  2. 选择**汽车模块**,然后点击**下一步**。
  3. 输入**应用/库名称**。这是用户在 Android Automotive OS 上看到的应用名称。
  4. 输入**模块名称**。
  5. 调整**包名**以匹配您的应用。
  6. 为**最低 SDK**选择**API 28:Android 9.0 (Pie)**,然后点击**下一步**。

    所有支持 Android Automotive OS 的汽车都运行在 Android 9(API 级别 28)或更高版本上,因此选择此值可以定位所有兼容的汽车。

  7. 选择**无 Activity**,然后点击**完成**。

在 Android Studio 中创建模块后,打开新汽车模块中的AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.media">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" />

    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />

</manifest>

application元素包含一些标准的应用信息,以及一个uses-feature声明对 Android Automotive OS 支持的元素。请注意,清单文件中没有声明任何 Activity。

如果您实现了设置或登录 Activity,请在此处添加它们。这些 Activity 由系统使用显式 Intent 触发,并且是您在 Android Automotive OS 应用的清单文件中声明的唯一 Activity。

添加任何设置或登录 Activity 后,通过在application元素中设置android:appCategory="audio"属性并添加以下uses-feature元素来完成清单文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.media">

    <application
        android:allowBackup="true"
        android:appCategory="audio"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" />

    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />

    <uses-feature
        android:name="android.hardware.wifi"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.portrait"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.landscape"
        android:required="false" />

</manifest>

将这些功能显式设置为required="false"可以确保您的应用不会与 Automotive OS 设备中可用的硬件功能发生冲突。

声明对 Android Automotive OS 的媒体支持

使用以下清单条目声明您的应用支持 Android Automotive OS

<application>
    ...
    <meta-data android:name="com.android.automotive"
        android:resource="@xml/automotive_app_desc"/>
    ...
</application>

此清单条目引用了一个 XML 文件,该文件声明了您的应用支持的汽车功能。

要指示您有一个媒体应用,请将名为automotive_app_desc.xml的 XML 文件添加到项目中的res/xml/目录中。在此文件中包含以下内容

<automotiveApp>
    <uses name="media"/>
</automotiveApp>

Intent 过滤器

Android Automotive OS 使用显式 Intent 触发媒体应用中的 Activity。不要在清单文件中包含任何具有CATEGORY_LAUNCHERACTION_MAINIntent 过滤器的 Activity。

像以下示例中的 Activity 通常针对手机或其他移动设备。在构建手机应用的模块中声明这些 Activity,而不是在构建 Android Automotive OS 应用的模块中声明。

<activity android:name=".MyActivity">
    <intent-filter>
        <!-- You can't use either of these intents for Android Automotive OS -->
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <!--
        In their place, you can include other intent filters for any activities
        that your app needs for Android Automotive OS, such as settings or
        sign-in activities.
        -->
    </intent-filter>
</activity>

更新您的 Gradle 依赖项

我们建议您将媒体浏览器服务保留在单独的模块中,并在手机应用和汽车模块之间共享。如果您使用此方法,则需要更新您的汽车模块以包含共享模块,如下面的代码片段所示

my-auto-module/build.gradle

Groovy

buildscript {
    ...
    dependencies {
        ...
        implementation project(':shared_module_name')
    }
}

Kotlin

buildscript {
    ...
    dependencies {
        ...
        implementation(project(":shared_module_name"))
    }
}

实现设置和登录 Activity

除了媒体浏览器服务之外,您还可以为 Android Automotive OS 应用提供针对车辆优化的设置和登录 Activity。这些 Activity 允许您提供 Android Media API 中未包含的应用功能。

仅当您的 Android Automotive OS 应用需要允许用户登录或指定应用设置时才实现这些 Activity。Android Auto 不使用这些 Activity。

Activity 工作流程

下图显示了用户如何使用 Android Automotive OS 与您的设置和登录 Activity 进行交互

Workflows for Settings and Sign-in activities

图 1. 设置和登录 Activity 工作流程。

避免在设置和登录 Activity 中造成分心

为了确保您的设置和/或登录 Activity 仅在用户车辆停放时可用,请验证<activity>元素是否不包含以下<meta-data>元素。如果存在此类元素,您的应用将在审查期间被拒绝。

<!-- NOT ALLOWED -->
<meta-data
  android:name="distractionOptimized"
  android:value="true"/>

添加设置 Activity

您可以添加一个针对车辆优化的设置 Activity,以便用户可以在其汽车中配置应用的设置。您的设置 Activity 还可以提供其他工作流程,例如登录或注销用户帐户或切换用户帐户。请记住,此 Activity 仅由在 Android Automotive OS 上运行的应用触发。连接到 Android Auto 的手机应用不使用它。

声明设置 Activity

您必须在应用的清单文件中声明设置 Activity,如下面的代码片段所示

<application>
    ...
    <activity android:name=".AppSettingsActivity"
              android:exported="true"
              android:theme="@style/SettingsActivity"
              android:label="@string/app_settings_activity_title">
        <intent-filter>
            <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
        </intent-filter>
    </activity>
    ...
</application>

实现设置 Activity

当用户启动您的应用时,Android Automotive OS 会检测到您声明的设置 Activity 并显示一个功能,例如图标。用户可以使用其汽车的显示屏点击或选择此功能以导航到该 Activity。Android Automotive OS 发送ACTION_APPLICATION_PREFERENCESIntent,指示您的应用启动设置 Activity。

本节的其余部分介绍了如何从通用 Android 音乐播放器 (UAMP) 示例应用中调整代码以实现应用的设置 Activity。

首先,下载示例代码

# Clone the UAMP repository
git clone https://github.com/android/uamp.git

# Fetch the appropriate pull request to your local repository
git fetch origin pull/323/head:NEW_LOCAL_BRANCH_NAME

# Switch to the new branch
git checkout NEW_LOCAL_BRANCH_NAME

要实现您的 Activity,请按照以下步骤操作

  1. automotive/automotive-lib文件夹复制到您的汽车模块中。
  2. 定义一个首选项树,如automotive/src/main/res/xml/preferences.xml中所示。
  3. 实现一个PreferenceFragmentCompat,您的设置 Activity 将显示它。有关更多信息,请参阅 UAMP 中的SettingsFragment.ktSettingsActivity.kt文件以及Android 设置指南

在实现设置 Activity 时,请考虑使用首选项库中某些组件的以下最佳实践

  • 在设置 Activity 中,主视图下方最多只有两级深度。
  • 不要使用DropDownPreference。请改用ListPreference
  • 组织组件
  • 在以下所有组件中包含keytitle。您还可以包含summaryicon或两者。
    • Preference
      • 自定义PreferenceFragmentCompat实现的onPreferenceTreeClick()回调中的逻辑。
    • CheckBoxPreference
      • 可以具有summaryOnsummaryOff,而不是summary,用于条件文本。
    • SwitchPreference
      • 可以具有summaryOnsummaryOff,而不是summary,用于条件文本。
      • 可以具有switchTextOnswitchTextOff
    • SeekBarPreference
      • 包含minmaxdefaultValue
    • EditTextPreference
      • 包含dialogTitlepositiveButtonTextnegativeButtonText
      • 可以具有dialogMessage和/或dialogLayoutResource
    • com.example.android.uamp.automotive.lib.ListPreference
      • 主要派生自ListPreference
      • 用于显示Preference对象的单选列表。
      • 必须具有entries数组和相应的entryValues
    • com.example.android.uamp.automotive.lib.MultiSelectListPreference
      • 主要派生自MultiSelectListPreference
      • 用于显示Preference对象的复选列表。
      • 必须具有entries数组和相应的entryValues

添加登录 Activity

如果您的应用要求用户在使用应用之前登录,您可以添加一个针对车辆优化的登录 Activity 来处理应用的登录和注销。您还可以将登录和注销工作流程添加到设置 Activity中,但如果您的应用在用户登录之前无法使用,请使用专用的登录 Activity。请记住,此 Activity 仅由在 Android Automotive OS 上运行的应用触发。连接到 Android Auto 的手机应用不使用它。

在应用启动时要求登录

要要求用户在使用应用之前登录,媒体浏览器服务必须执行以下操作

  1. 在服务的onLoadChildren()方法中,使用sendResult()方法发送null结果。
  2. 将媒体会话的PlaybackStateCompat设置为STATE_ERROR,使用setState()方法。这会告诉 Android Automotive OS 在错误解决之前无法执行其他操作。
  3. 将媒体会话的PlaybackStateCompat错误代码设置为ERROR_CODE_AUTHENTICATION_EXPIRED。这会告诉 Android Automotive OS 用户需要进行身份验证。
  4. 使用setErrorMessage()方法设置媒体会话的PlaybackStateCompat错误消息。由于此错误消息面向用户,因此请针对用户的当前语言环境对其进行本地化。
  5. 使用setExtras()方法设置媒体会话的PlaybackStateCompat附加信息。包含以下两个键

以下代码段显示了您的应用如何在用户使用前要求其登录。

Kotlin

import androidx.media.utils.MediaConstants

val signInIntent = Intent(this, SignInActivity::class.java)
val signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
    signInIntent, 0)
val extras = Bundle().apply {
    putString(
        MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL,
        "Sign in"
    )
    putParcelable(
        MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT,
        signInActivityPendingIntent
    )
}

val playbackState = PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
        .setErrorMessage(
            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
            "Authentication required"
        )
        .setExtras(extras)
        .build()
mediaSession.setPlaybackState(playbackState)

Java

import androidx.media.utils.MediaConstants;

Intent signInIntent = new Intent(this, SignInActivity.class);
PendingIntent signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
    signInIntent, 0);
Bundle extras = new Bundle();
extras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL,
    "Sign in");
extras.putParcelable(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT,
    signInActivityPendingIntent);

PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
    .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
    .setErrorMessage(
            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
            "Authentication required"
    )
    .setExtras(extras)
    .build();
mediaSession.setPlaybackState(playbackState);

在用户成功完成身份验证后,将 PlaybackStateCompat 设置回 STATE_ERROR 以外的状态,然后通过调用活动的 finish() 方法将用户带回 Android Automotive OS。

实现您的登录活动

Google 提供各种 身份工具,您可以使用这些工具帮助用户登录其汽车中的应用。某些工具(例如 Firebase 身份验证)提供全栈工具包,可以帮助您构建自定义的身份验证体验。其他工具利用用户的现有凭据或其他技术来帮助您为用户构建无缝的登录体验。

以下工具可以帮助您为以前在其他设备上登录过的用户构建更简单的登录体验

  • 一键登录和注册:如果您已为其他设备(例如您的手机应用)实现了 一键登录,请为您的 Android Automotive OS 应用实现它以支持现有的一键登录用户。
  • Google 登录:如果您已为其他设备(例如您的手机应用)实现了 Google 登录,请为您的 Android Automotive OS 应用实现 Google 登录以支持现有的 Google 登录用户。
  • 使用 Google 自动填充:如果用户已在其其他 Android 设备上选择使用 Google 自动填充,则其凭据将保存到 Google 密码管理器。当这些用户登录您的 Android Automotive OS 应用时,Google 自动填充会建议相关的已保存凭据。使用 Google 自动填充无需任何应用开发工作。但是,应用开发者可以 优化其应用以获得更高质量的结果。所有运行 Android 8.0(API 级别 26)或更高版本的设备(包括 Android Automotive OS)都支持 Google 自动填充。

使用 AccountManager

具有身份验证功能的 Android Automotive OS 应用必须使用 AccountManager,原因如下

  • 更好的用户体验和简化帐户管理:用户可以轻松地从系统设置中的帐户菜单管理其所有帐户,包括登录和注销。
  • “访客”体验:汽车是共享设备,这意味着 OEM 可以启用车辆中的“访客”体验,在这种体验中无法添加帐户。此限制是使用 DISALLOW_MODIFY_ACCOUNTSAccountManager 实现的。

权限

如果您需要向用户请求权限,请使用与上一节中显示的 活动工作流程图 中的身份验证活动或设置活动相同的流程。

读取媒体主机提示

根据连接到您的媒体浏览器服务的系统应用(包括其版本),您的应用可能会收到以下额外信息

错误处理

Android Automotive OS 上媒体应用中的错误通过媒体会话的 PlaybackStateCompat 传达。对于所有错误,请在 PlaybackStateCompat 中设置适当的错误代码和错误消息。这会导致 UI 中出现 Toast

当发生错误但播放可以继续时,发出 非致命错误。例如,用户可能能够在登录前在应用中播放音乐,但必须在跳过歌曲前登录。当您使用非致命错误时,系统可以建议用户登录,而不会中断当前媒体项目的播放。

当您发出非致命错误时,除了错误代码和错误消息外,请保留其余的 PlaybackStateCompat 不变。使用这种方法可以让当前媒体项目的播放继续,同时用户决定是否登录。

当无法播放时(例如,当没有互联网连接且没有离线内容时),将 PlaybackStateCompat 状态设置为 STATE_ERROR

在您对 PlaybackStateCompat 的后续更新中,清除所有错误代码和错误消息,以避免为同一错误显示多个警告。

如果在任何时候您都无法加载浏览树(例如,如果您需要身份验证且用户未登录),请发送一个空的浏览树。为此,请从根媒体节点的 onLoadChildren() 返回空结果。发生这种情况时,系统会显示一个全屏错误,其中包含在 PlaybackStateCompat 中设置的错误消息。

可操作的错误

如果错误是可操作的,请在 PlaybackStateCompat 中另外设置以下两个额外信息

可操作的错误显示为 Dialog,并且只有在汽车停止时才能由用户解决。

测试错误情况

验证您的应用在所有情况下都能优雅地处理错误,包括

  • 产品的不同层级:例如,免费版与付费版或已登录与未登录
  • 不同的驾驶状态:例如,已停车与正在驾驶
  • 不同的连接状态:例如,联机与脱机

其他注意事项

在开发您的 Android Automotive OS 应用时,请记住以下其他注意事项

离线内容

如果适用,请实现离线播放支持。配备 Android Automotive OS 的汽车预计将拥有自己的数据连接,这意味着数据计划包含在车辆成本中或由用户付费。但是,汽车也预计比移动设备具有更多变化的连接性。

在考虑您的离线支持策略时,请记住以下几点

  • 下载内容的最佳时间是在您的应用正在使用时。
  • 不要假设 WiFi 可用。汽车可能永远无法进入 WiFi 范围,或者 OEM 可能已禁用 WiFi 以支持蜂窝网络。
  • 虽然智能缓存用户期望使用的内容是可以的,但我们建议您允许用户通过您的设置活动更改此行为。
  • 汽车上的磁盘空间各不相同,因此请为用户提供一种删除离线内容的方法,例如通过设置活动中的选项。

WebView 支持

Android Automotive OS 支持 WebViews,但仅允许用于您的设置和登录活动。使用 WebView 的活动必须在 WebView 外部具有“关闭”或“返回”功能。

以下是一些 WebView 可接受用例的示例

  • 在您的设置活动中显示您的隐私政策、服务条款或其他与法律相关的链接。
  • 登录活动中的基于 Web 的流程。

使用 WebView 时,您可以 启用 Javascript

保护您的 WebView

采取所有可能的预防措施,以帮助确保您的 WebView 不是进入更大互联网的入口点。有关如何将 WebView 锁定到 loadUrl() 调用中使用的 URL 并防止重定向的示例,请参阅以下代码段。我们强烈建议您在可行的情况下实现此类安全措施,例如在显示与法律相关的链接时。

Kotlin

override fun shouldOverrideUrlLoading(webView: WebView,
                             webResourceRequest: WebResourceRequest): Boolean {
  val originalUri: Uri = Uri.parse(webView.originalUrl)
  // Check for allowed URLs
  if (originalUri.equals(Uri.parse(BLANK_URL))
      || originalUri.equals(webResourceRequest.url)) {
    return false
  }
  if (webResourceRequest.isRedirect) {
    logger.w("Redirect detected, not following")
    return true
  }
  setupWizardWebViewClientListener.onUriBlocked(webResourceRequest.url)
  logger.w(
    String.format(
      "Navigation prevented to %s original is %s", webResourceRequest.url, originalUri))
  return true
}

Java

@Override
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest webResourceRequest) {
  Uri originalUri = Uri.parse(webView.getOriginalUrl());
  // Check for allowed URLs
  if (originalUri.equals(Uri.parse(BLANK_URL))
      || originalUri.equals(webResourceRequest.getUrl())) {
    return false;
  }
  if (webResourceRequest.isRedirect()) {
    logger.w("Redirect detected, not following");
    return true;
  }
  setupWizardWebViewClientListener.onUriBlocked(webResourceRequest.getUrl());
  logger.w(
      String.format(
          "Navigation prevented to %s original is %s", webResourceRequest.getUrl(), originalUri));
  return true;
}

包名

因为您为 Android Automotive OS 分发单独的 Android 软件包套件 (APK),所以您可以重用移动应用的包名或创建新的包名。如果您使用不同的包名,则您的应用将有两个单独的 Play 商店列表。如果您重用当前的包名,则您的应用将在两个平台上都有一个列表。

这主要是一个业务决策。例如,如果您有一个团队负责移动应用,而另一个团队负责您的 Android Automotive OS 应用,那么拥有单独的包名并让每个团队管理自己的 Play 商店列表可能是有意义的。使用任一方法所需的技​​术工作量没有太大差异。

下表总结了保留当前包名和使用新包名之间的一些其他关键区别

功能 相同的包名 新的包名
商店列表 单个 多个
镜像安装 是:“快速应用重新安装”在设置向导期间
Play 商店审核流程 阻止审核:如果一个 APK 的审核失败,则在同一版本中提交的其他 APK 将被阻止 单独审核
统计信息、指标和 重要指标 组合:您可以过滤汽车特定数据。 单独
索引和搜索排名 基于当前排名构建 无延续
与其他应用集成 最有可能无需更改,假设媒体代码在两个 APK 之间共享 可能需要更新相应的应用,例如用于与 Google 助理的 URI 播放。

常见问题

有关 Android Automotive OS 的一些常见问题的解答,请参阅以下部分。

硬件

我的应用能否访问麦克风

对于目标为 Android 10(API 级别 29)或更高版本的应用,请参阅 共享音频输入 文档。在 API 级别 29 之前,这是不可行的。

我们可以访问哪些汽车 API 以及如何访问?

您仅限于 OEM 公开的 API。正在开发标准化访问这些 API 的流程。

应用可以通过 SetProperty()GetProperty()CarPropertyManager 中访问汽车 API。请参阅 源代码参考文档 以查看所有可用属性的列表。如果属性使用 @SystemApi 进行 注释,则仅限于预加载的系统应用。

支持哪些类型的音频编解码器?

请参阅 Android CDD 中的 音频编解码器详细信息

是否支持 Widevine DRM?

是的。支持 Widevine DRM

开发和测试

使用第三方 SDK 和库是否存在任何限制或建议?

我们没有关于使用第三方 SDK 和库的具体指南。如果您选择使用第三方 SDK 和库,您仍然有责任遵守所有汽车应用质量要求。

我可以使用前台服务吗?

前台服务的唯一允许用例是下载内容以供离线使用。如果您有其他想要支持的前台服务用例,请使用 Android Automotive OS 讨论组 与我们联系。

发布 Android Automotive OS 应用

如何使用 Google Play Console 发布我的 Android Automotive OS 应用?

应用发布过程类似于发布手机应用,但您使用的是不同的外形尺寸。要选择加入您的应用以使用 Android Automotive OS 外形尺寸,请按照以下步骤操作

  1. 打开 Play Console
  2. 选择您的应用。
  3. 在左侧菜单中,点击发布 > 设置 > 高级设置 > 外形尺寸
  4. 点击添加外形尺寸 > Android Automotive OS,然后按照 Play Console 中的说明操作。

其他资源

要了解有关 Android Automotive OS 的更多信息,请参阅以下其他资源。

示例

指南

博客

视频

报告 Android Automotive OS 媒体问题

如果您在为 Android Automotive OS 开发媒体应用时遇到问题,可以使用 Google Issue Tracker 报告问题。请务必在问题模板中填写所有所需信息。

创建新的问题

在提交新问题之前,请检查该问题是否已在问题列表中报告。您可以通过点击跟踪器中问题旁边的星号来订阅和投票支持问题。有关更多信息,请参阅 订阅问题