为媒体应用添加 Android Automotive OS 支持

Android Automotive OS 允许用户在车内安装应用。为了吸引此平台上的用户,您需要分发与 Android Automotive OS 兼容且专为驾驶员优化的应用。您可以在 Android Auto 应用中重复使用几乎所有代码和资源,但必须创建一个单独的 build,让其满足本页上的要求。

开发概览

添加 Android Automotive OS 支持的过程很简单,只需执行几个步骤:

  1. 在 Android Studio 中启用汽车功能
  2. 创建汽车模块
  3. 更新 gradle 依赖项
  4. 添加设置或登录 activity(可选)

设计注意事项

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

如果您要实现设置或登录 activity,必须对这些 activity 进行车辆优化。在设计应用的这些方面时,您应参阅 Android Automotive OS 的设计准则

设置项目

您需要设置应用项目的多个不同部分,以启用对 Android Automotive OS 的支持。

在 Android Studio 中启用汽车功能

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

创建汽车模块

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

如需在项目中添加汽车模块,请按以下步骤操作:

  1. 在 Android Studio 中,依次选择 File > New > New Module
  2. 选择 Automotive Module,然后点击 Next
  3. 输入 Application/Library name。这是用户在 Android Automotive OS 上看到的应用名称。
  4. 输入 Module name
  5. 调整 Package name,使其与您的应用匹配。
  6. 针对 Minimum SDK 选择 API 28:Android 9.0 (Pie),然后点击 Next

    所有支持 Android Automotive OS 的汽车都搭载的是 Android 9(API 级别 28)或更高版本,因此选择此值会将开发目标定位到所有使用 Android Automotive OS 的汽车。

  7. 选择 Add No Activity,然后点击 Finish

在 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" 属性并添加以下 users-features 元素,完成您的清单文件:

<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>

明确地将这些功能设置为非必需功能可确保应用不会与 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_MAIN intent 过滤器的任何 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

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 进行互动:

设置和登录 activity 的工作流

图 1. 设置和登录 activity 工作流

添加设置 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_PREFERENCES intent,告知应用启动设置 activity。

本部分的其余内容介绍了如何改编 Universal Android Music Player (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. 实现设置 activity(请参阅 SettingsActivity.kt)将显示的 PreferenceFragmentCompat(请参阅 UAMP 中的 SettingsFragment.kt)。如需了解详情,请参阅 Android 设置指南

实现设置 activity 时,请参考下面这些有关如何使用 Preference 库中某些组件的最佳做法:

  • 在应用的设置 activity 中,主视图下的深度应不超过两个层级。
  • 不要使用 DropDownPreference。请改用 ListPreference
  • 组织组件:
  • 以下所有组件都应具有 keytitle,并且可能具有 summary 和/或 icon
    • 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. 使用 setState() 方法将媒体会话的 PlaybackState 设置为 STATE_ERROR。这会告知 Android Automotive OS,只有在错误得到解决后才能执行其他操作。
  3. 将媒体会话的 PlaybackState 错误代码设置为 ERROR_CODE_AUTHENTICATION_EXPIRED。这会告知 Android Automotive OS,用户需要进行身份验证。
  4. 使用 setErrorMessage() 方法设置媒体会话的 PlaybackState 错误消息。由于此错误消息是面向用户的,因此必须根据用户当前的语言区域对消息进行本地化。
  5. 使用 setExtras() 方法设置媒体会话的 PlaybackState extra。添加以下两个键:

以下代码段展示了如何要求用户先登录才能使用您的应用:

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);

用户成功通过身份验证后,您的应用必须将 PlaybackState 重新设置为 STATE_ERROR 以外的状态,然后通过调用 activity 的 finish() 方法使用户返回到 Android Automotive OS。

实现登录 activity

Google 提供了多种身份验证工具,供您用于帮助用户在汽车内登录您的应用。Firebase Authentication 等一些工具提供全栈工具包,可帮助您构建自定义身份验证体验。其他工具利用用户现有的凭据或其他技术帮助您为用户打造无缝的登录体验。

建议您使用以下工具,为之前在其他设备上登录过的用户提供更为简便的登录体验:

  • Google 登录:如果您已面向其他设备(如手机应用)实现 Google 登录,您也应该为 Android Automotive OS 应用实现 Google 登录,以便为现有的 Google 登录用户提供支持。
  • Google 自动填充:如果用户在其他 Android 设备上选择使用 Google 自动填充,其凭据会保存到 Google 密码管理器中。以后,当用户登录您的 Android Automotive OS 应用时,Google 自动填充功能会建议使用相关的已保存凭据。使用 Google 自动填充功能不需要开发应用;但是,应用开发者应优化其应用以提高质量。搭载 Android Oreo 8.0(API 级别 26)或更高版本(包括 Android Automotive OS)的所有设备均支持 Google 自动填充。

使用 AccountManager

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

  • 提升用户体验并简化帐号管理:用户可以通过系统设置中的帐号菜单轻松管理其所有帐号,包括登录和退出。
  • “访客”体验:由于汽车是共用设备,因此 OEM 可以在车辆中启用“访客”体验,在这种体验模式下,无法添加帐号。通过使用 AccountManagerDISALLOW_MODIFY_ACCOUNT 来实现此限制。

权限

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

错误处理

Android Automotive OS 上的媒体应用中的错误通过媒体会话的 PlaybackState 进行传达。对于所有错误,您应在 PlaybackState 中设置适当的错误代码和错误消息。这会使得 Toast 出现在界面中。

如果发生了错误但可以继续播放,您应发出非严重错误。例如,用户可能在登录前就能在应用中播放音乐,但必须在登录后才能跳过某首歌曲。通过使用非严重错误,系统可以建议用户应登录,而不会中断当前媒体内容的播放。在这种情况下,您应按原样保留 PlaybackState 的其余部分(除了错误代码和错误消息之外)。采用这种方法,在用户决定是否登录时可以继续播放当前媒体内容。

如果无法播放(例如,如果未连接到互联网且没有离线内容),您应将 PlaybackState 状态设置为 STATE_ERROR

在对 PlaybackState 进行后续更新时,您应清除错误代码和错误消息,以避免针对同一错误显示多条警告。

如果您在任何时候无法加载浏览树(例如,如果您要求进行身份验证但用户未登录),应发送一个空的浏览树。为了表示这一点,根媒体节点的 onLoadChildren() 应返回 null 结果。当发生这种情况时,系统会根据 PlaybackState 中设置的错误消息显示全屏错误。

可操作的错误

如果错误是可操作的,在 PlaybackState 中另外设置以下两个 extra:

可操作的错误将显示为 Dialog,只有在停车后,用户才能解决这些错误。

测试错误情况

您应验证您的应用是否在所有场景中都能妥善处理,包括:

  • 产品的不同层级:例如,免费与付费或登录与退出。
  • 不同的驾驶状态:例如,停车与驾车。
  • 不同的连接状态:例如,在线与离线。

其他注意事项

开发 Android Automotive OS 应用时,请牢记下面这些其他注意事项:

离线内容

如果适用,请实现离线播放支持。搭载 Android Automotive OS 的汽车应有自己的数据连接,也就是说,包含在车辆费用中的流量套餐或由用户付费的数据连接。不过,与移动设备相比,汽车的连接也会更具可变性。因此,我们建议您考虑最适合自己内容的离线支持策略。汽车上的磁盘空间会有所不同,但将是合理的,因此请确保用户可以删除离线内容,例如通过设置 activity 中的某个选项。

当您考虑离线支持策略时,请牢记以下几点:

  • 下载内容的最佳时机是当您的应用目前正在使用中时。
  • 不要假定 WLAN 可用。汽车可能永远不会进入 WLAN 覆盖范围,或者 OEM 可能已停用 WLAN,改为使用移动网络。
  • 虽然可以巧妙地缓存您认为用户可能会使用的内容,但我们强烈建议允许用户通过设置 activity 更改此行为。

商业支持

Android Automotive OS 当前不支持任何商业功能。这意味着,您不能有付费应用,您的应用内也不能有任何应用内购商品。用户可以在您的 Android Automotive OS 应用之外购买应用内购商品,但只要是在车内,用户若要使用任何新的付费内容或功能,不得要求他们执行任何额外的商业相关步骤。

WebView 支持

Android Automotive OS 支持 WebView,但只允许设置和登录 activity 使用 WebView。使用 WebView 的 activity 必须在 WebView 之外提供关闭和/或返回功能。

下面列举了一些示例来说明可接受的 WebView 用例:

  • 在设置 activity 中显示隐私权政策、服务条款或其他法律相关链接。
  • 登录 activity 中基于网页的登录流程。

使用 WebView 时,允许您启用 JavaScript

保护 WebView 安全

您应采取一切可能的预防措施,以确保您的 WebView 不能用作浏览互联网上其他网页/网站的入口点。如需通过示例了解如何将 WebView 锁定为只能打开 loadUrl() 调用中指定的网址并杜绝重定向,请参阅以下代码段。强烈建议在符合您的用例需求时(例如,在显示法律相关链接时)采取这样的保护措施。

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 分发单独的 APK,因此您可以选择重复使用移动应用的软件包名称或创建一个新的软件包名称。这主要是一项业务决策。主要区别是,如果使用不同的软件包名称,应用具有两份完全独立的 Play 商店商品详情,而如果重复使用当前的软件包名称,则在两个平台上使用一份商品详情。

在开发方面,您可能要考虑分发流程如何与您的团队结构共同起作用。例如,如果您的一个团队负责开发移动应用,一个完全独立的团队负责开发 Android Automotive OS 应用,则一种合理的做法是,使用单独的软件包名称,并让每个团队管理他们自己的 Play 商店商品详情。使用这两种方法所需的技术工作没有很大的区别。

下表总结了这两种方法之间的一些其他关键区别:

功能 相同的软件包名称 新的软件包名称
商品详情 一份 多份
镜像安装 是的,设置向导操作期间的“快速应用重新安装”
Play 商店审核流程 阻止型审核。目前,如果针对某个 APK 的审核失败,会阻止在同一版本中提交的其他 APK 单独审核
统计信息、指标和核心指标 组合。注意:您可以按设备名称过滤汽车专用数据(例如,2020 年有 2 辆汽车) 单独
索引编制和搜索排名 基于当前的先后顺序构建 不会沿用
与其他应用集成 假定在两个 APK 之间共享媒体代码,很可能不需要更改 可能必须更新相应的应用(例如,为了使用 Google 助理进行 URI 播放)

常见问题解答

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

硬件

我的应用是否可以访问麦克风?

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

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

您只能访问 OEM 公开的 API。我们正在将您访问这些 API 的方式标准化。

应用可使用 CarPropertyManager 中的 SetProperty()GetProperty() 访问汽车 API。如需查看所有可用属性的列表,请参阅源代码参考文档。如果属性带有 @SystemApi 注解,则只有系统(预加载的)应用可以使用它。

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

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

是否支持 Widevine 数字版权管理?

是的,支持 Widevine 数字版权管理

开发和测试

对于使用第三方 SDK 和库是否有任何限制或建议?

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

我是否可以使用前台服务?

允许的唯一一个前台服务用例是下载内容以供离线使用。如果您希望其他前台服务用例获得支持,请通过 Android Automotive OS 论坛与我们联系。

发布 Android Automotive OS 应用

如何使用 Google Play 管理中心发布我的 Android Automotive OS 应用?

应用的发布流程与手机应用类似,但需使用不同的版本类型。如需选择让您的应用使用 Android Automotive OS 版本类型,请按以下步骤操作:

  1. 打开 Play 管理中心
  2. 选择您的应用。
  3. 在左侧菜单中,依次选择发布 > 设置 > 高级设置 > 版本类型
  4. 依次选择添加版本类型 > Android Automotive OS,然后按照 Play 管理中心的说明操作。

其他资源

如需详细了解 Android Automotive OS,请参阅下面列出的其他资源。

示例

指南

博客

视频

报告 Android Automotive OS 媒体问题

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

创建新问题

在提交新问题之前,请先查看问题列表,确认该问题是否已报告过。您可以在跟踪器中点击某个问题的星标,订阅该问题并为其投票。如需了解详情,请参阅订阅问题