构建基于模板的媒体应用

模板化媒体应用目前处于 Beta 版阶段
目前,任何人都可以将模板化媒体应用发布到 Google Play 商店中的内部测试轨道和封闭式测试轨道。日后将允许发布到开放式轨道和正式版轨道。

使用汽车应用库模板的媒体应用可以自定义媒体浏览和播放体验,同时确保该体验针对汽车屏幕进行了优化,并最大限度地减少驾驶时的分心。

本指南假定您已经拥有可在手机上播放音频的媒体应用,并且您的媒体应用遵循 Android 媒体应用架构。借助汽车应用库,您可以使用模板替换应用内体验,而不是使用通过为汽车构建媒体应用 MediaBrowser 数据结构构建的体验。您仍必须提供用于播放控制的 MediaSession,以及用于推荐和其他智能体验的 MediaBrowserServiceMediaLibraryService

配置应用的清单

除了使用 Android for Cars 应用库中所述的步骤外,模板化媒体应用还必须满足以下要求:

在清单中声明类别支持

应用需要在其 CarAppService 的 intent 过滤器中声明 androidx.car.app.category.MEDIA 汽车应用类别

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.MEDIA"/>
      </intent-filter>
    </service>
    ...
<application>

为了能够访问 MediaPlaybackTemplate,您的应用还需要在其清单文件中声明 androidx.car.app.MEDIA_TEMPLATES 权限:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.MEDIA_TEMPLATES"/>
  ...
</manifest>

设置最低汽车应用 API 级别

使用 MediaPlaybackTemplate 的媒体应用仅在 CAL API 8 及更高级别中受支持,请确保您的最低 Car App API level 设置为 8。

<application ...>
  ...
  <meta-data
    android:name="androidx.car.app.minCarApiLevel"
    android:value="8"/>
  ...
</application>

提供提供方图标

请务必为使用汽车应用库构建的媒体应用添加提供方信息图标

声明 Android Auto 支持

确保应用的清单中包含以下内容:

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

然后,将 template 声明添加到 XML 资源中的 automotive_app_desc.xml。它看起来应该如下所示:

<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android">
 <uses name="media"/>
 <uses name="template"/>
</automotiveApp>

声明 Android Automotive OS 支持

您可以通过两种不同的方式在 Android Automotive OS 上分发启用汽车应用库的媒体应用:作为单个 APK 或作为两个单独的 APK。如果您分发单个 APK,它将支持已启用 Android Automotive OS 并具有汽车应用库宿主的车辆,否则将回退到 MediaBrowserServiceMediaLibraryService 应用,即使对于较旧的 Android 版本(Android 10 - Android 13)也是如此。如果您选择分发两个单独的 APK,则可以更轻松地更新 Car App Library 版本的新增功能,而不必担心影响应用的 MediaBrowserServiceMediaLibraryService 版本。

分发单个 APK

为汽车应用库和应用的 MediaBrowserServiceMediaLibraryService 版本分发单个 APK 时,务必将“”设置为 android:required="false"

<uses-feature android:name="android.software.car.templates_host.media" android:required="false"/>

接下来,请遵循 AAOS 的 Car App Library 指南,并引入可启动的 CarAppActivity(或 trampoline activity)。您必须在清单中将 activity 设置为 android:enabled="false"。接下来,向 MediaBrowserService 声明添加一个元数据标记,以将 CarAppActivity 组件指定为替换项。请参阅下面的示例清单:

<service android:name=".media.MyMediaService"
    android:exported="true"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
    </intent-filter>

    <!-- Link to Car App Library Activity -->
    <meta-data
        android:name="androidx.car.app.media.CalMediaActivityComponent" 
        android:value="com.example.mediaapp.LaunchableTrampoline"/>
</service>

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false"> <!-- Set to false -->

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

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Play 分发

包含 Car App Library 和 MediaBrowserServiceMediaLibraryService 的 APK 应启用更高的版本代码,并将 minSdk 设为以 Android 14 (34) 为目标平台。

使用两个 APK 进行分发

如需分发两个单独的 APK(一个使用汽车应用库,另一个使用 MediaBrowserServiceMediaLibraryService),请按以下步骤操作,以确保正确定位到正确的车辆功能。

为应用的汽车应用库版本创建单独的 APK 时,您必须将 android.software.car.templates_host.media 设置为 android:required=true。这样可确保应用仅在经过认证且支持汽车应用库主机的 Android Automotive OS build 上分发。

<uses-feature android:name="android.software.car.templates_host.media" android:required="true"/>

除了使用 android.software.car.templates_host.media 并将其设置为上述 android:required=true 之外,请按照以下步骤为可启动的汽车应用库 activity 启用 Android Automotive OS

Play Distribution

使用汽车应用库的 APK 应在 Automotive OS 专用轨道中分发。

支持语音操作

为应用启用语音功能,让用户无需手动操作即可完成常见操作。 如需了解更详细的实现说明,请参阅支持媒体语音操作。对于模板化媒体应用,如果您收到语音指令,则无需使用搜索结果更新 MediaBrowserServiceMediaLibraryService。不妨考虑在媒体播放模板中添加一项操作,以便用户根据该播放内容或搜索查询查找更多内容。支持语音指令是满足 VC-1 质量指南的必要条件。

创建播放模板

MediaPlaybackTemplate 在汽车应用库媒体应用中显示媒体播放信息。此模板允许设置带有标题和可自定义操作的标题,而媒体信息和播放控制由主机根据应用 MediaSession 的状态填充。

音乐播放器显示了 Summer Fielding 的《Sounds of Spring》,并附有一张女性弹吉他的方形肖像。

图 1:顶部带有用于打开队列的标题操作的 MediaPlaybackTemplate

此代码示例展示了如何构建一个示例播放模板,该模板设置了一个标题操作,允许用户前往包含歌曲队列的界面。

val playbackTemplate = MediaPlaybackTemplate.Builder()
      .setHeader(
        Header.Builder()
          .setStartHeaderAction(Action.BACK)
          .addEndHeaderAction(
                Action.Builder()
                  .setTitle(model.context.getString(R.string.queue_button_title))
                  .setIcon(
                    CarIcon.Builder(
                        IconCompat.createWithResource(
                          model.context,
                          R.drawable.gs_queue_music_vd_theme_24,
                        ))
                      .build())
                  .setOnClickListener(showQueueScreen())
                  .build())
          .setTitle(model.context.getString(R.string.media_playback_view_title))
          .build())
      .build()

使用 MediaPlaybackTemplate 时,请使用 CarAppService 中的 MediaPlaybackManager 注册 MediaSession 令牌。否则,当 MediaPlaybackTemplate 发送到主机时,系统会显示错误。

import androidx.car.app.media.MediaPlaybackManager


override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return object : Session() {
        

        init {
          lifecycle.addObserver(
            LifecycleEventObserver { _, event ->
              if (event == ON_CREATE) {
                val token = ... // MediaSessionCompat.Token
                (carContext.getCarService(CarContext.MEDIA_PLAYBACK_SERVICE) as MediaPlaybackManager)
                  .registerMediaPlaybackToken(token)
              }
              ...
            }
          )
        }
    }
}

.registerMediaPlaybackToken 对于向 Android Auto 公开媒体播放信息和控件是必需的。对于主机创建媒体专用通知,这也是非常重要的。

对于使用 Media3 库的应用(使用 PlatformToken 而不是标准 MediaSessionCompat.Token),您需要在 MediaLibrarySession.Callback 中实现自定义 SessionCommand,以返回会话的底层平台令牌:session.platformToken。在 CarAppService 中,将此自定义命令发送到会话。收到平台令牌后,使用 MediaSessionCompat.Token.fromToken(platformToken) 对其进行转换,并将此兼容性令牌传递给 .registerMediaPlaybackToken() 中的 Car App Library。

使用模板整理媒体

为了整理媒体以供浏览(例如歌曲或专辑),我们建议使用 SectionedItemTemplate,这样您就可以将 GridSectionRowSection 结合使用,创建混合了图片列表和文字项的布局。

音乐应用界面显示了最近播放的歌曲和专辑,包括两行竖向排列的专辑封面和三张横向排列的专辑封面。

图 2:包含 RowSectionSectionedItemTemplate,后跟 GridSection

在 TabTemplate 中使用 SectionedItemTemplate

在应用中对媒体进行分类的一种便捷方式是在 TabTemplate 中使用 SectionedItemTemplate

val template =
      SectionedItemTemplate.Builder()...build();
val tabTemplate = 
      TabTemplate.Builder(tabCallback)
          .setTabContents(TabContents.Builder(template).build)
          .setHeaderAction(Action.APP_ICON)
          
          .build();

车载应用库 1.9 组件和功能

Car App 库 API 版本 1.9 引入了自定义组件,可实现独特的浏览功能,例如功能块进度条精简项互动式展开式标题聚光灯部分横幅

音乐应用界面显示了最近播放的歌曲和专辑,包括两行竖向排列的专辑封面和三张横向排列的专辑封面。

图 3:包含 ChipsCondensed ItemsInteractive HeaderGrid ItemsMinimized Control PanelSectionedItemTemplate

音乐应用界面显示了最近播放的歌曲和专辑,包括两行竖向排列的专辑封面和三张横向排列的专辑封面。

图 4:两个媒体浏览界面,其中显示了 Expanded HeaderSpotlight SectionsProgress Bars

如需详细了解如何使用这些模板设计媒体应用的界面,请参阅媒体应用

在浏览媒体时,用户应能够以最小的干扰快速导航到 MediaPlaybackTemplate。为了满足 MFT-1 质量要求,您的应用必须提供一种从所有媒体浏览界面访问 MediaPlaybackTemplate 的方式。

如果您使用的是 SectionedItemTemplate,则可以通过添加一个可导航到媒体播放界面的操作按钮来实现此目的。使用标准 Car App 库 Action.MEDIA_PLAYBACK 操作。媒体应用会将此操作显示为最小化的控制面板,如果您使用的是汽车应用库 API 1.9 或更高版本,则必须满足 MFT-1 质量要求。对于其他模板,标题操作是实现此目的的另一种方式。

处理系统媒体播放 intent

当应用从播放媒体的系统界面(例如媒体卡)启动时,必须将用户引导至 MediaPlaybackTemplate。我们要求媒体应用处理此 Intent Action,以便为用户提供顺畅的体验。

androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK 操作添加到车载设备应用库组件(CarAppActivity 或您的 trampoline Activity)的 intent-filter。

确保您的 activity 使用 singleTasksingleToplaunchMode,以便调用 onNewIntent()

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false">

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

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Session 类中,替换 onNewIntent() 以解析传入的 intent。如果传入的 intent 操作与 SHOW_MEDIA_PLAYBACK 匹配,则将用户导航到“正在播放”界面。

@Override
public void onNewIntent(@NonNull Intent intent) {
    super.onNewIntent(intent);
    if (SHOW_MEDIA_PLAYBACK.equals(intent.getAction())) {
        ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
        // Avoid redundant navigation if already on the playing screen
        if (screenManager.getTop() instanceof MyMediaPlayScreen) {
            return;
        }
        screenManager.push(MyMediaPlayScreen.createScreenFromPlaying(
                getCarContext(), mMediaSessionController));
    }
}

如果您使用的是蹦床 activity,请在 onCreate() 中检查 intent 操作。在调用 finish() 之前,将此操作传递给 CarAppActivity 创建 intent。

public class LaunchableTrampoline extends AppCompatActivity {
    private static final String SHOW_MEDIA_PLAYBACK = "androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent receivedIntent = getIntent();
        String action;

        if (SHOW_MEDIA_PLAYBACK.equals(receivedIntent.getAction())) {
            action = SHOW_MEDIA_PLAYBACK;
        } else {
            action = Intent.ACTION_MAIN;
        }

        Intent intent = new Intent(action);
        intent.setClassName(getPackageName(), "androidx.car.app.activity.CarAppActivity");
        startActivity(intent);
        finish();
    }
}