为媒体应用添加 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)或更高版本,因此选择此值能将开发目标定位到所有兼容的汽车。

  7. 选择 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" 属性并添加以下 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_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

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

设置和登录 activity 的工作流

图 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_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 会显示的 PreferenceFragmentCompat。如需了解详情,请参阅 UAMP 中的 SettingsFragment.ktSettingsActivity.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() 方法将媒体会话的 PlaybackStateCompat 设置为 STATE_ERROR。这会告知 Android Automotive OS,只有在错误得到解决后才能执行其他操作。
  3. 将媒体会话的 PlaybackStateCompat 错误代码设置为 ERROR_CODE_AUTHENTICATION_EXPIRED。这会告知 Android Automotive OS,用户需要进行身份验证。
  4. 使用 setErrorMessage() 方法设置媒体会话的 PlaybackStateCompat 错误消息。由于此错误消息是面向用户的,因此必须根据用户当前的语言区域将其本地化。
  5. 使用 setExtras() 方法设置媒体会话的 PlaybackStateCompat 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);

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

实现登录 activity

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

以下工具可帮助您为之前在其他设备上登录过的用户提供更轻松的登录体验:

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

使用 AccountManager

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

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

权限

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

错误处理

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

如果发生了错误但可以继续播放,则发出非严重错误。例如,用户可能在登录前就能在应用中播放音乐,但必须在登录后才能跳过某首歌曲。您使用非严重错误时,系统可以建议用户登录,而不会中断当前媒体项的播放。

在这种情况下,您应按原样保留 PlaybackStateCompat 的其余部分(除了错误代码和错误消息之外)。采用这种方法,在用户决定是否登录时可以继续播放当前媒体项。

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

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

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

可操作的错误

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

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

测试错误情况

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

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

其他注意事项

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

离线内容

如果适用,请实现离线播放支持。搭载 Android Automotive OS 的汽车应有自己的数据连接,也就是说,包含在车辆费用中的流量套餐或由用户付费的数据连接。不过,与移动设备相比,汽车的连接性也会更加多变。

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

  • 下载内容的最佳时机是您的应用在使用中时。
  • 不要假定 Wi-Fi 可用。汽车可能永远不会进入 Wi-Fi 覆盖范围,或者 OEM 可能已停用 Wi-Fi,改为使用移动网络。
  • 虽然可以巧妙地缓存您认为用户可能会使用的内容,但我们强烈建议允许用户通过您的设置 activity 更改此行为。
  • 车载设备的磁盘可用空间各异,因此,请让用户可以删除离线内容,例如通过设置 activity 中的某个选项进行删除。

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 分发单独的 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 的方式标准化。

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

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

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

是否支持 Widevine DRM?

可以。支持 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 问题跟踪器报告该问题。请务必在问题模板中填写所有必填信息。

创建新问题

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