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
  • 使用 MediaSessionMediaController API 的扩展功能
  • 通过精细的访问权限控制通告播放功能。
  • 通过移除 MediaSessionConnectorPlayerNotificationManager 简化应用
  • 与 media-compat 客户端 API 向后兼容 (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

要迁移到 AndroidX Media3 的 Media 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
    • 替换所有使用 asterix (*) 的通配符 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 选项的情况下引入的一般性问题),可以考虑提交 bug
  2. 构建项目:使用 ./gradlew clean build,或者在 Android Studio 中依次选择 File > Sync Project with Gradle Files,然后依次选择 Build > Clean project,然后点击 Build > Rebuild project(在 Android Studio 的“Build - Build Output”标签页中监控您的 build)。

建议的后续步骤:

  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.2.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* 方法。您可以在会话演示应用的 PlaybackService 中找到 onAddMediaItems 的实现示例。

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

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

Media3 中的 MediaSessionConnector 功能

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

MediaSessionConnector 类AndroidX 媒体 3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems()prepare() 在内部调用)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

MediaBrowserService 迁移到 MediaLibraryService

AndroidX Media3 引入了 MediaLibraryService,以取代 MediaBrowserServiceCompatMediaLibraryService 的 JavaDoc 及其父类 MediaSessionService 是对 API 和服务异步编程模型的详细介绍。

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

包含服务、activity 和外部应用的应用组件示意图。
图 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.2.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

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

移除了 PlayerNotificationManager

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

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

通过替换 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 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 具有相同的稳定性级别,并且 ExoPlayer v2 完全不提供 Media3 API 稳定版的保证。不同之处在于,lint 错误现在会提醒您注意不同的稳定性级别。

处理 API lint 不稳定的错误

您可以通过以下两种方式处理 API 不稳定的 lint 错误:

  • 请改用可实现相同结果的稳定 API。
  • 继续使用不稳定的 API,并为其添加 @OptIn 注解。

    import androidx.annotation.OptIn
    import androidx.media3.common.util.UnstableApi
    
    @OptIn(UnstableApi::class)
    fun functionUsingUnstableApi() {
      // Do something useful.
    }
    

    请注意,还有不应使用的 kotlin.OptIn 注解。为此,请务必使用 androidx.annotation.OptIn 注解。

    屏幕截图:如何添加“选择启用”注释
    图 2:使用 Android Studio 添加 @androidx.annotations.OptIn 注解。

可以通过添加 package-info.java 来选择整个软件包:

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

您可以通过在 lint.xml 中抑制特定的 lint 错误来选择整个项目。如需了解详情,请参阅 UnstableApi 注解的 JavaDoc

已弃用的 API

您可能会注意到,在 Android Studio 中,对已废弃 API 的调用带有删除线。我们建议将此类调用替换为适当的替代方案。 将鼠标悬停在该符号上即可查看指示改用哪个 API 的 JavaDoc。

屏幕截图:如何用已废弃方法的替代方法显示 JavaDoc
图 3:Android Studio 中的 JavaDoc 提示为任何已废弃的符号提供了替代符号。

代码示例和演示应用