在后台播放媒体内容

即使应用未在屏幕上显示(例如,当用户与其他应用互动时),您也可以在后台播放媒体内容。

为此,您需要将 MediaPlayer 嵌入到 MediaBrowserServiceCompat 服务中,并使其在其他 activity 中与 MediaBrowserCompat 进行互动。

请谨慎实现此客户端和服务器设置。我们对在后台服务中运行的播放器如何与系统的其他部分进行互动设定了预期。如果您的应用未满足这些预期,则可能会导致用户体验不佳。如需了解详情,请参阅构建音频应用

本页介绍了在 Service 内部实现 MediaPlayer 时如何对其进行管理的特殊说明。

异步运行

Activity 类似,Service 中的所有工作均默认在单个线程中完成。实际上,如果您从同一应用中运行 activity 和 service,则它们会默认使用相同的线程(“主线程”)。

服务必须快速处理传入的 intent,并且在响应它们时避免执行冗长的计算。您必须异步执行任何繁重工作或阻塞调用:从您自己实现的其他线程异步执行,或使用框架的诸多工具进行异步处理。

例如,从主线程中使用 MediaPlayer 时,您应:

例如:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

处理异步错误

在同步操作中,系统会通过异常或错误代码来指示错误。但是,当您使用异步资源时,应以适当的方式向应用发出错误通知。对于 MediaPlayer,您可以实现 MediaPlayer.OnErrorListener 并在 MediaPlayer 实例中对其进行设置:

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

出现错误时,MediaPlayer 会进入“Error”状态。您必须先进行重置,然后才能再次使用它。如需了解详情,请参阅 MediaPlayer 类的完整状态图。

使用唤醒锁定

在后台播放或流式传输音乐时,您必须使用唤醒锁定来防止系统干扰播放,例如,让设备进入休眠状态。

唤醒锁定可以告诉系统:您的应用正在使用一些即使在手机处于闲置状态时也应该可用的功能。

为确保 CPU 在 MediaPlayer 播放时继续运行,请在初始化 MediaPlayer 时调用 setWakeMode() 方法。MediaPlayer 会在播放时保持指定的锁定状态,并在暂停或停止播放时释放锁定:

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

不过,此示例中获取的唤醒锁定只能保证 CPU 保持唤醒状态。如果您使用 WLAN 并通过网络流式传输媒体内容,则您可能也希望保持 WifiLock,该锁定必须手动获取和释放。因此,当您开始使用远程网址准备 MediaPlayer 时,您应创建并获取 WLAN 锁定。

例如:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

当您暂停或停止媒体内容,或者当您不再需要网络时,应释放该锁定:

Kotlin

wifiLock.release()

Java

wifiLock.release();

执行清理

如前文所述,MediaPlayer 对象会消耗大量的系统资源,因此您应仅使其保留必要的时长,并在操作完成后调用 release()。请务必明确调用此清理方法,而非依赖于系统垃圾回收,因为垃圾回收器要经过一段时间才会回收 MediaPlayer,原因在于它仅对内存需求敏感,而对缺少其他媒体相关资源并不敏感。因此,当您使用 Service 时,应始终替换 onDestroy() 方法以确保释放 MediaPlayer

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

除了在关闭时释放您的 MediaPlayer 之外,您还应始终寻找其他释放机会。例如,如果您预计在很长一段时间都无法播放媒体内容(例如,在失去音频焦点后),则您应果断释放现有的 MediaPlayer 并在之后重新创建。另一方面,如果您预计只是短时间停止播放,则您可能应该保留 MediaPlayer,以避免再次创建和准备它所产生的开销。

了解详情

Jetpack Media3 是应用中播放媒体的推荐解决方案。详细了解

以下页面介绍了有关录制、存储以及播放音频和视频的主题: