AndroidX Media3 迁移指南

目前使用独立 com.google.android.exoplayer2 库和 androidx.media 的应用应迁移到 androidx.media3。使用迁移脚本将 Gradle 构建文件、Java 和 Kotlin 源文件以及 XML 布局文件从 ExoPlayer 2.19.1 迁移到 AndroidX Media3 1.1.1

概览

在迁移之前,请查看以下部分,了解新 API 的优势、要迁移的 API 以及您的应用项目应满足的先决条件。

为何迁移到 Jetpack Media3

  • 它是 ExoPlayer 的新家,而 com.google.android.exoplayer2 已停用。
  • 使用 MediaBrowser/MediaController 跨组件/进程访问播放器 API
  • 使用 MediaSessionMediaController API 的扩展功能
  • 通过精细的访问控制宣传播放功能。
  • 通过移除 MediaSessionConnectorPlayerNotificationManager 简化您的应用
  • 向后兼容 media-compat 客户端 API (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

要迁移到 AndroidX Media3 的媒体 API

  • ExoPlayer 及其扩展
    这包括旧版 ExoPlayer 项目的所有模块,除了已停用的 mediasession 模块。依赖于 com.google.android.exoplayer2 中软件包的应用或模块可以使用迁移脚本进行迁移。
  • MediaSessionConnector(依赖于 androidx.media:media:1.4.3+androidx.media.* 软件包)
    移除 MediaSessionConnector 并改为使用 androidx.media3.session.MediaSession
  • MediaBrowserServiceCompat(依赖于 androidx.media:media:1.4.3+androidx.media.* 软件包)
    androidx.media.MediaBrowserServiceCompat 的子类迁移到 androidx.media3.session.MediaLibraryService,并将使用 MediaBrowserCompat.MediaItem 的代码迁移到 androidx.media3.common.MediaItem
  • MediaBrowserCompat(依赖于 androidx.media:media:1.4.3+android.support.v4.media.* 软件包)
    将使用 MediaBrowserCompatMediaControllerCompat 的客户端代码迁移到使用 androidx.media3.session.MediaBrowserandroidx.media3.common.MediaItem

先决条件

  1. 确保您的项目受版本控制

    确保您可以轻松恢复脚本化迁移工具应用的更改。如果您的项目尚未受版本控制,现在是开始使用它的好时机。如果由于某种原因您不想这样做,请在开始迁移之前备份您的项目。

  2. 更新您的应用

    • 我们建议更新您的项目以使用 ExoPlayer 库的最新版本,并移除对已弃用方法的任何调用。如果您打算使用脚本进行迁移,您需要将要更新的版本与脚本处理的版本匹配。

    • 将应用的 compileSdkVersion 提高到至少 32

    • 升级 Gradle 和 Android Studio Gradle 插件到与上述更新的依赖项兼容的最新版本。例如:

      • Android Gradle 插件版本:7.1.0
      • Gradle 版本:7.4
    • 替换所有使用星号 (*) 的通配符导入语句,并使用完全限定的导入语句:删除通配符导入语句,并使用 Android Studio 导入完全限定的语句 (F2 - Alt/Enter, F2 - Alt/Enter, ...)。

    • com.google.android.exoplayer2.PlayerView 迁移到 com.google.android.exoplayer2.StyledPlayerView。这是必需的,因为 AndroidX Media3 中没有 com.google.android.exoplayer2.PlayerView 的等效项。

在脚本支持下迁移 ExoPlayer

该脚本有助于从 com.google.android.exoplayer2 迁移到 androidx.media3 下的新包和模块结构。该脚本对您的项目应用一些验证检查,如果验证失败则打印警告。否则,它会在用 Java 或 Kotlin 编写的 Android Gradle 项目的资源中应用重命名的类和包的映射

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

使用迁移脚本

  1. 从 GitHub 上的 ExoPlayer 项目标签下载迁移脚本,该标签对应于您已更新应用的版本。

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. 使脚本可执行

    chmod 744 media3-migration.sh
    
  3. 运行带 --help 的脚本以了解选项。

  4. 运行带 -l 的脚本以列出已选择迁移的文件集(使用 -f 强制列出,不带警告)

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. 运行带 -m 的脚本以将包、类和模块映射到 Media3。运行带 -m 选项的脚本将应用更改到选定的文件。

    • 在验证错误处停止,不进行更改
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • 强制执行

    如果脚本发现违反先决条件,可以使用 -f 标志强制执行迁移。

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

运行带 -m 选项的脚本后完成这些手动步骤

  1. 检查脚本如何更改了您的代码:使用差异工具并修复潜在问题(如果您认为脚本存在未通过 -f 选项而引入的普遍问题,请考虑提交 bug)。
  2. 构建项目:可以使用 ./gradlew clean build,或者在 Android Studio 中选择 File > Sync Project with Gradle Files(文件 > 将项目与 Gradle 文件同步),然后选择 Build > Clean project(构建 > 清理项目),再选择 Build > Rebuild project(构建 > 重新构建项目)(在 Android Studio 的“构建 - 构建输出”标签页中监控您的构建)。

建议的后续步骤

  1. 解决关于不稳定 API 用法的选择性加入错误
  2. 替换已弃用的 API 调用:使用建议的替换 API。将指针悬停在 Android Studio 中的警告上,并查阅已弃用符号的 JavaDoc,以找出要使用的替代调用。
  3. 排序导入语句:在 Android Studio 中打开项目,然后在项目浏览器中右键单击包文件夹节点,选择包含已更改源文件的包上的 Optimize imports(优化导入)。

MediaSessionConnector 替换为 androidx.media3.session.MediaSession

在旧版 MediaSessionCompat 世界中,MediaSessionConnector 负责同步播放器状态与会话状态,并接收需要委派给相应播放器方法的控制器命令。在 AndroidX Media3 中,这由 MediaSession 直接完成,无需连接器。

  1. 移除所有对 MediaSessionConnector 的引用和使用:如果您使用自动化脚本迁移 ExoPlayer 类和包,那么脚本很可能使您的代码在 MediaSessionConnector 方面处于无法编译的状态,并且无法解决。当您尝试构建或启动应用时,Android Studio 将显示损坏的代码。

  2. 在维护依赖项的 build.gradle 文件中,添加 AndroidX Media3 会话模块的实现依赖项,并移除旧版依赖项。

    implementation "androidx.media3:media3-session:1.7.1"
    
  3. MediaSessionCompat 替换为 androidx.media3.session.MediaSession

  4. 在您创建旧版 MediaSessionCompat 的代码位置,使用 androidx.media3.session.MediaSession.Builder 构建 MediaSession传入播放器以构建会话构建器。

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. 根据您的应用要求实现 MySessionCallback。这是可选的。如果您希望控制器向播放器添加媒体项,请实现 MediaSession.Callback.onAddMediaItems()。它提供各种当前和旧版 API 方法,以向后兼容的方式将媒体项添加到播放器中进行播放。这包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及旧版 API 的 TransportControls.prepareFrom*/playFrom* 方法。onAddMediaItems 的示例实现可在会话演示应用的 PlaybackService找到。

  6. 在迁移前销毁会话的代码位置释放媒体会话

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    

Media3 中的 MediaSessionConnector 功能

下表显示了 Media3 中处理以前在 MediaSessionConnector 中实现的功能的 API。

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setMediaButtonPreferences()
PlaybackPreparer MediaSession.Callback.onAddMediaItems()prepare() 在内部调用)
QueueNavigator ForwardingSimpleBasePlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

MediaBrowserService 迁移到 MediaLibraryService

AndroidX Media3 引入了 MediaLibraryService,它取代了 MediaBrowserServiceCompatMediaLibraryService 及其超类 MediaSessionService 的 JavaDoc 很好地介绍了 API 和服务的异步编程模型。

MediaLibraryService 向后兼容 MediaBrowserService。使用 MediaBrowserCompatMediaControllerCompat 的客户端应用在连接到 MediaLibraryService 时,无需更改代码即可继续工作。对于客户端而言,您的应用是使用 MediaLibraryService 还是旧版 MediaBrowserServiceCompat 是透明的。

App component diagram with service, activity and external apps.
图 1:媒体应用组件概览
  1. 为了实现向后兼容,您需要在 AndroidManifest.xml 中使用您的服务注册两个服务接口。这样,客户端就可以通过所需的服务接口找到您的服务。

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. 在维护依赖项的 build.gradle 文件中,添加 AndroidX Media3 会话模块的实现依赖项,并移除旧版依赖项。

    implementation "androidx.media3:media3-session:1.7.1"
    
  3. 将您的服务更改为继承自 MediaLibraryService,而不是 MediaBrowserService。如前所述,MediaLibraryService 与旧版 MediaBrowserService 兼容。因此,该服务向客户端提供的更广泛的 API 仍然相同。因此,应用很可能可以保留实现 MediaBrowserService 所需的大部分逻辑,并使其适应新的 MediaLibraryService

    与旧版 MediaBrowserServiceCompat 的主要区别如下:

    • 实现服务生命周期方法:需要在服务本身上重写的方法是 onCreate/onDestroy,应用在此处分配/释放库会话、播放器和其他资源。除了标准服务生命周期方法之外,应用还需要重写 onGetSession(MediaSession.ControllerInfo) 以返回在 onCreate 中构建的 MediaLibrarySession

    • 实现 MediaLibraryService.MediaLibrarySessionCallback:构建会话需要一个实现实际领域 API 方法的 MediaLibraryService.MediaLibrarySessionCallback。因此,您将重写 MediaLibrarySession.Callback 的方法,而不是重写旧版服务的 API 方法。

      然后使用回调来构建 MediaLibrarySession

      mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, MySessionCallback())
               .build()
      

      在 API 文档中查找 MediaLibrarySessionCallback 的完整 API

    • 实现 MediaSession.Callback.onAddMediaItems():回调 onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) 提供各种当前和旧版 API 方法,以向后兼容的方式将媒体项添加到播放器中进行播放。这包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及旧版 API 的 TransportControls.prepareFrom*/playFrom* 方法。回调的示例实现可在会话演示应用的 PlaybackService找到。

    • AndroidX Media3 使用 androidx.media3.common.MediaItem,而不是 MediaBrowserCompat.MediaItemMediaMetadataCompat。您的代码中与旧版类相关联的部分需要相应地更改或映射到 Media3 MediaItem

    • 总体的异步编程模型已更改为 Futures,与 MediaBrowserServiceCompat 的可分离 Result 方法不同。您的服务实现可以返回异步 ListenableFuture,而不是分离结果,或者返回即时 Future 以直接返回值

移除 PlayerNotificationManager

当使用 MediaLibraryServiceMediaSessionService 时,MediaLibraryService 自动支持媒体通知,并且可以移除 PlayerNotificationManager

应用可以通过在 onCreate() 中设置自定义 MediaNotification.Provider自定义通知,该提供程序将替换 DefaultMediaNotificationProviderMediaLibraryService 然后会根据需要负责在前台启动服务。

通过重写 MediaLibraryService.updateNotification(),应用可以进一步完全掌控发布通知以及根据需要启动/停止前台服务。

迁移使用 MediaBrowser 的客户端代码

在 AndroidX Media3 中,MediaBrowser 实现了 MediaController/Player 接口,除了浏览媒体库之外,还可以用于控制媒体播放。如果您需要在旧版中创建 MediaBrowserCompatMediaControllerCompat,在 Media3 中您只需使用 MediaBrowser 即可实现相同的功能。

可以构建 MediaBrowser 并等待与服务建立连接。

scope.launch {
    val sessionToken =
        SessionToken(context, ComponentName(context, MusicService::class.java)
    browser =
        MediaBrowser.Builder(context, sessionToken))
            .setListener(BrowserListener())
            .buildAsync()
            .await()
    // Get the library root to start browsing the library.
    root = browser.getLibraryRoot(/* params= */ null).await();
    // Add a MediaController.Listener to listen to player state events.
    browser.addListener(playerListener)
    playerView.setPlayer(browser)
}

请参阅在媒体会话中控制播放,了解如何创建 MediaController 以控制后台播放。

后续步骤和清理

不稳定 API 错误

迁移到 Media3 后,您可能会看到关于不稳定 API 用法的 Lint 错误。这些 API 可以安全使用,Lint 错误是我们新二进制兼容性保证的副产品。如果您不需要严格的二进制兼容性,这些错误可以使用 @OptIn 注解安全地抑制。

背景

ExoPlayer v1 和 v2 都未在后续版本之间提供库的二进制兼容性严格保证。ExoPlayer API 表面在设计上非常庞大,以允许应用自定义播放的几乎所有方面。ExoPlayer 的后续版本偶尔会引入符号重命名或其他破坏性更改(例如接口上新的必需方法)。在大多数情况下,这些破坏性更改通过引入新符号同时弃用旧符号几个版本来缓解,以给开发者时间迁移他们的用法,但这并非总是可能。

这些破坏性更改给 ExoPlayer v1 和 v2 库的用户带来了两个问题:

  1. 升级到 ExoPlayer 版本可能导致代码停止编译。
  2. 一个同时直接和通过中间库依赖 ExoPlayer 的应用必须确保两个依赖项是相同的版本,否则二进制不兼容可能导致运行时崩溃。

Media3 中的改进

Media3 保证 API 表面子集的二进制兼容性。保证二进制兼容性的部分标有 @UnstableApi。为了明确区分这一点,除非使用 @OptIn 注解,否则使用不稳定 API 符号会生成 Lint 错误。

从 ExoPlayer v2 迁移到 Media3 后,您可能会看到许多不稳定的 API Lint 错误。这可能会让 Media3 看起来“不如 ExoPlayer v2 稳定”。事实并非如此。Media3 API 的“不稳定”部分与 ExoPlayer v2 API 表面的整体具有相同的稳定性级别,并且 ExoPlayer v2 中根本不提供 Media3 稳定 API 表面的保证。区别仅在于 Lint 错误现在会提醒您不同的稳定性级别。

处理不稳定 API Lint 错误

有关如何使用 @OptIn 注解 Java 和 Kotlin 中不稳定 API 用法的详细信息,请参阅这些 Lint 错误的故障排除部分

已弃用的 API

您可能会注意到 Android Studio 中已弃用的 API 调用被划掉。我们建议将此类调用替换为适当的替代方案。将鼠标悬停在符号上可查看 JavaDoc,其中会告知您应使用哪个 API 代替。

Screenshot: How to display JavaDoc with alternative of deprecated method
图 3:Android Studio 中的 JavaDoc 提示会为任何已弃用符号建议替代方案。

代码示例和演示应用