构建和测试适合在 Android Automotive OS 的停车状态下使用的应用:支持驾车时播放音频

1. 准备工作

这并非是

  • 介绍如何创建适用于 Android Auto 和 Android Automotive OS 的媒体(音频,例如音乐、电台、播客)应用的指南。如需详细了解如何构建此类应用,请参阅构建车载媒体应用

所需条件

构建内容

在此 Codelab 中,您将学习如何在Road Reels(一款同时支持移动设备和 Android Automotive OS 设备的现有应用)中添加在驾车时支持音频的功能。

会在用户体验限制处于已启用状态时暂停播放的起始版本。

当用户体验限制处于已启用状态时,应用的已完成版本会继续播放。

学习内容

  • 如何在 Android Automotive OS 上的视频应用中启用后台音频播放

2. 进行设置

获取代码

  1. 此 Codelab 的代码可以在 car-codelabs GitHub 存储库的 build-a-parked-app 目录中找到。若要克隆该代码,请运行以下命令:
git clone https://github.com/android/car-codelabs.git
  1. 或者,您也可以下载代码库 Zip 文件。

打开项目

  • 在启动 Android Studio 后,导入项目,仅选择 build-a-parked-app/end 目录。build-a-parked-app/audio-while-driving 目录包含解决方案代码,如果您遇到困难或只想查看完整项目,可以随时参考。

熟悉代码

  • 在 Android Studio 中打开项目后,花些时间浏览起始代码。

3. 了解适用于 Android Automotive OS 的停车状态下使用的应用

停车状态下使用的应用属于 Android Automotive OS 支持的应用类别的子集。在撰写本文时,这些应用包括视频在线播放应用、网络浏览器和游戏。鉴于含 Google 预装的汽车中存在的硬件以及电动汽车的普及程度越来越高,并且在充电期间,驾驶员和乘客有很好的机会来使用这些应用,因此这些应用非常适合汽车。

在许多方面,汽车与平板电脑和可折叠设备等其他大屏设备类似。它们的触摸屏具有相似的尺寸、分辨率和宽高比,并且屏幕方向可以是纵向或横向(但与平板电脑不同,它们的方向是固定的)。它们也是可断开网络连接和重新连接网络的已连接设备。考虑到以上所有因素,针对大屏设备进行了优化的应用通常只需进行少量工作就能在汽车上提供出色的用户体验,也就不足为奇了。

与大屏设备类似,车载应用也具有应用质量层级

  • 第 3 层级 - 支持汽车:应用兼容大屏设备,并且可以在停车时使用。虽然该应用可能没有针对汽车优化的功能,但用户可以像在任何其他大屏 Android 设备上一样体验该应用。符合这些要求的移动应用满足通过支持汽车的移动应用计划按原样分发到汽车的条件。
  • 第 2 层级 - 已针对汽车优化:应用在汽车中控台显示屏上提供出色的体验。为此,应用将具有一些汽车特有的工程,以包含可在驾驶模式或停车模式下使用的功能,具体取决于应用的类别。
  • 第 1 层级 - 量身打造的车载应用:应用专为适用于汽车中的各种不同硬件而打造,并且可以在驾驶模式和停车模式之间调整体验。它提供专为汽车中的不同屏幕(例如中控台、仪表板和其他屏幕,如许多高端汽车中常见的全景显示屏)设计的出色用户体验。

在此 Codelab 中,您将实现在驾车时播放音频的功能(这是一种 1 级功能),使该应用成为一款为汽车量身打造的应用。

4. 在 Android Automotive OS 模拟器中运行应用

安装 Automotive with Play Store 系统映像

  1. 首先,在 Android Studio 中打开 SDK 管理器,然后选择 SDK Platforms 标签页(如果尚未选择)。在 SDK 管理器窗口的右下角,确保选中 Show package details 复选框。
  2. 安装添加通用系统映像中列出的 API 34 Android Automotive 模拟器映像之一。映像只能在具有与其相同架构 (x86/ARM) 的机器上运行。

创建 Android Automotive OS Android 虚拟设备

  1. 打开设备管理器后,选择窗口左侧 Category 列下的 Automotive。从列表中选择“Automotive (1408p landscape)捆绑硬件配置文件,然后点击 Next

6a32a01404a7729f.png

  1. 在下一页上,选择上一步中的系统映像。点击 Next,并选择所需的任何高级选项,最后点击 Finish 以创建 AVD。

运行应用

在您刚刚使用 app 运行配置创建的模拟器上运行应用。 前往播放器界面并模拟驾驶以测试应用的行为。

5. 检测驾车时是否支持音频

由于并非所有车辆都支持驾车时播放音频,因此您需要检测当前设备是否支持此功能,并相应地调整应用的行为。为此,您可以使用 androidx.car.app:app 库中的 CarFeatures 类。

  1. 添加对 androidx.car.app:app 制品的依赖项。

libs.version.toml

[versions]
...
carApp = "1.7.0-rc01"


[libraries]
...
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }

build.gradle.kts(模块 :app)

implementation(libs.androidx.car.app)
  1. 然后,您可以在 RoadReelsPlayer 中确定是否支持该功能,并更新用于计算 shouldPreventPlay 值的逻辑。

RoadReelsPlayer.kt

if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
    ...

    val isBackgroundAudioWhileDrivingSupported = CarFeatures.isFeatureEnabled(
        context,
        CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING
    )

    shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
            carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
    invalidateState()

    carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions ->
        shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
            carUxRestrictions.isRequiresDistractionOptimization
        ...
    }
}

对应用进行这些更改后,再次运行应用,并模拟驾驶。请注意,现在,当应用的界面被系统遮挡时,音频播放会继续。不过,您尚未大功告成 - 为了满足所有要求,您还需要进行一些更改。

6. 支持后台播放

目前,应用的媒体播放由 activity 处理。因此,在用户体验限制已启用且 activity 被遮盖后,媒体播放可能会短暂继续,但您的应用最终会被系统缓存,导致播放结束。

为了支持长时间播放,应用必须更新为使用服务来处理播放。您可以使用 Media3 MediaSessionService 实现此目的。

创建 MediaSessionService

  1. 在“Project”窗口中,右键点击 com.example.android.cars.roadreels 软件包,然后依次选择 New > Kotlin Class/File。输入 PlaybackService 作为文件名称,然后点击 Class 类型。
  2. 添加 PlaybackService. 的以下实现。如需详细了解 MediaSessionService,请参阅使用 MediaSessionService 在后台播放

PlaybackService.kt

import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService

@UnstableApi
class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = RoadReelsPlayer(this)
        mediaSession = MediaSession.Builder(this, player).build()
    }

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        if (controllerInfo.isTrusted || controllerInfo.packageName == packageName) {
            return mediaSession
        }
        return null
    }
}
  1. 在清单文件中添加以下 <uses-permission> 元素。使用前台服务处理媒体播放

AndroidManifest.xml

<manifest ...>
    ...
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    ...
</manifest>
  1. 还要在清单中声明 PlaybackService

AndroidManifest.xml

<application ...>
    ...
    <service
        android:name=".PlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaSessionService"/>
        </intent-filter>
    </service>
</application>

更新 PlayerViewModel 以使用 PlaybackService

  1. 由于 PlaybackService 本身会创建并公开 MediaSession,因此 PlayerViewModel 无需再创建一个。找到并删除以下行以及对该变量的所有引用:

PlayerViewModel.kt

private var mediaSession: MediaSession? = null
  1. 接下来,将 init 代码块中用于实例化 RoadReelsPlayer 的部分替换为以下代码,以使用 MediaController 将应用连接到 PlaybackService

PlayerViewModel.kt

import android.content.ComponentName
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.example.android.cars.roadreels.PlaybackService
import com.google.common.util.concurrent.MoreExecutors

...

init {
        viewModelScope.launch {
            ...
        }
        
        val sessionToken = SessionToken(
            application,
            ComponentName(application, PlaybackService::class.java)
        )

        val mediaControllerFuture =
            MediaController.Builder(getApplication(), sessionToken).buildAsync()

        mediaControllerFuture.addListener(
            { _player.update { mediaControllerFuture.get() } },
            MoreExecutors.directExecutor()
        )
    }

再次测试应用,当应用被系统屏蔽时,您应该会看到不同的界面。现在,用户可以在移动时控制播放。当用户再次休息时,可以点击“Exit”按钮,返回到完整的应用体验。

33eb8ff3d4035978.gif

移除生命周期播放更改

由于现在支持后台播放,因此您可以移除 PlayerScreen.kt 中的两个 LifecycleEventEffect 代码块,以便在用户离开应用时(包括在前一个界面中显示播放控件时)继续播放。

离开播放器界面时暂停播放

由于实际播放器(现在包含在 PlaybackService 中)在离开播放器界面时不会释放,因此您需要添加一项调用以在离开播放器界面时暂停播放,从而保持之前的行为。为此,您可以更新 PlayerViewModelonCleared 方法实现:

PlayerViewModel.kt

override fun onCleared() {
    super.onCleared()
    _player.value?.apply {
        pause()
        release()
    }
}

7. 更新清单

最后,若要指明您的应用支持在驾车时播放音频,您需在应用的清单中添加以下 <uses-feature> 元素。

AndroidManifest.xml

<application>
    <uses-feature android:name="com.android.car.background_audio_while_driving" android:required="false">
</application>

8. 恭喜

您已成功为视频应用添加了对驾车时播放音频的支持。现在是时候将您学到的知识应用到您自己的应用中了!

深入阅读