AndroidX Media3 迁移指南

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

概览

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

为何迁移到 Jetpack Media3

  • 它是 ExoPlayer 的新平台,而 com.google.android.exoplayer2 已 停用。
  • 使用 MediaBrowser/MediaController 跨组件/进程访问 Player API
  • 使用 MediaController API 的扩展功能。MediaSession
  • 通过精细的访问权限控制 宣传播放功能。
  • 通过移除 MediaSessionConnectorPlayerNotificationManager 简化应用
  • 向后兼容 媒体兼容性客户端 API (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

要迁移到 AndroidX Media3 的媒体 API

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

前提条件

  1. 确保您的项目处于源代码控制之下

    确保您可以轻松还原脚本化迁移工具所做的更改。 如果您的项目尚未处于源代码控制之下,现在是开始使用它的好时机。如果出于某种原因您不想这样做,请在开始迁移之前制作项目的备份副本。

  2. 更新应用

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

    • 将应用的 compileSdkVersion 至少增加到 32

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

      • Android Gradle 插件版本:7.1.0
      • Gradle 版本:7.4
    • 替换所有使用星号 (*) 的通配符 import 语句 ,并使用完全限定的 import 语句:删除通配符 import 语句,并使用 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. 检查脚本如何更改您的代码:使用 diff(差异比较)工具并修复潜在问题(如果您认为脚本存在未通过 -f 选项引入的普遍问题,请考虑提交 错误)。
  2. 构建项目:可以使用 ./gradlew clean build,也可以在 Android Studio 中依次选择 File > Sync Project with Gradle FilesBuild > Clean projectBuild > Rebuild project (在 Android Studio 的 “Build - Build Output”标签页中监控构建)。

建议的后续步骤:

  1. 解决 选择启用与使用不稳定 API 相关的错误
  2. 替换已废弃的 API 调用:使用建议的替换 API。 将指针悬停在 Android Studio 中的警告上,并查阅已废弃符号的 JavaDoc,了解应使用什么来代替给定的调用。
  3. 对 import 语句进行排序:在 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.10.0"
    
  3. MediaSessionCompat 替换为 androidx.media3.session.MediaSession

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

    Kotlin

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player).setCallback(MySessionCallback()).build()

    Java

    ExoPlayer player = new ExoPlayer.Builder(context).build();
    mediaSession =
        new MediaSession.Builder(context, player).setCallback(new MySessionCallback()).build();

  5. 根据应用的要求实现 MySessionCallback。这是可选操作。如果您想允许控制器向播放器添加媒体项,请实现 MediaSession.Callback.onAddMediaItems()。它以向后兼容的方式为各种当前和旧版 API 方法提供服务,这些方法会将媒体文件添加到播放器以进行播放。这包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及旧版 API 的 TransportControls.prepareFrom*/playFrom* 方法。您可以在会话演示版应用的 PlaybackService 中找到 onAddMediaItems 的实现示例

  6. 在迁移之前销毁会话的代码站点释放媒体会话:

    Kotlin

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

    Java

    if (mediaSession != null) {
      mediaSession.getPlayer().release();
      mediaSession.release();
      mediaSession = null;
    }

Media3 中的 MediaSessionConnector 功能

下表显示了处理之前在 MediaSessionConnector 中实现的功能的 Media3 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 是透明的。

包含服务、activity 和外部应用的 app 组件图。
图 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 文件中,添加 an 实现依赖项到 AndroidX Media3 会话模块 并 移除旧版依赖项:

    implementation "androidx.media3:media3-session:1.10.0"
    
  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

      Kotlin

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

      Java

      mediaLibrarySession =
          new MediaLibrarySession.Builder(context, player, new 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 与可分离 Result 方法 的 MediaBrowserServiceCompat 相比。您的服务实现可以返回一 个异步 ListenableFuture,而不是分离结果或 返回即时 Future 以直接返回值

移除 PlayerNotificationManager

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

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

通过替换 MediaLibraryService.updateNotification(),应用可以进一步完全拥有发布通知以及根据需要在前台启动/停止服务的所有权。

迁移使用 MediaBrowser 的客户端代码

借助 AndroidX Media3,MediaBrowser 会实现 MediaController/Player 接口,除了浏览媒体库之外,还可以用于控制媒体播放。如果您必须在旧版世界中创建 MediaBrowserCompatMediaControllerCompat,则只需在 Media3 中使用 MediaBrowser 即可实现相同的操作。

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

Kotlin

scope.launch {
  val sessionToken = SessionToken(context, ComponentName(context, "MusicService"))
  browser =
    MediaBrowser.Builder(context, sessionToken)
      .setListener(BrowserListener())
      .buildAsync()
      .await()
}

Java

SessionToken sessionToken =
    new SessionToken(context, new ComponentName(context, "MusicService"));
ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken)
        .setListener(new BrowserListener())
        .buildAsync();

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

后续步骤和清理

不稳定 API 错误

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

背景

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

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

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

Media3 中的改进

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

从 ExoPlayer v2 迁移到 Media3 后,您可能会看到很多不稳定 API lint 错误。这可能会让您觉得 Media3 比 ExoPlayer v2“不太稳定”。但其实不是这样。Media3 API 的“不稳定”部分与 ExoPlayer v2 API Surface 的整体 具有相同的稳定性级别,而稳定的 Media3 API Surface 的保证在 ExoPlayer v2 中根本不可用。不同之处在于,lint 错误现在会提醒您不同的稳定性级别。

处理不稳定 API lint 错误

如需详细了解如何使用 注解 Java 和 Kotlin 对不稳定 API 的用法(使用 @OptIn),请参阅有关这些 lint 错误的问题排查部分。

已废弃的 API

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

屏幕截图:如何显示 JavaDoc(包含已弃用方法的替代方法)
图 3:Android Studio 中的 JavaDoc 工具提示会为任何已废弃的符号建议替代方案。

代码示例和演示应用