在后台播放媒体

即使您的应用不在屏幕上,例如用户正在与其它应用交互时,您也可以在后台播放媒体。

为此,您需要将 MediaPlayer 嵌入到 MediaBrowserServiceCompat 服务中,并使其与另一个 Activity 中的 MediaBrowserCompat 进行交互。

实现此客户端和服务器设置时请务必谨慎。后台服务中运行的播放器如何与系统其余部分交互存在预期。如果您的应用不满足这些预期,用户可能会获得糟糕的体验。有关详细信息,请参阅构建音频应用

本页面介绍了在服务中实现 MediaPlayer 时管理 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 类的完整状态图。

使用唤醒锁

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

唤醒锁是向系统发出的信号,表明您的应用正在使用即使在手机空闲时也应保持可用的功能。

为确保在您的 MediaPlayer 播放时 CPU 持续运行,请在初始化 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 保持唤醒状态。如果您正在通过网络流式传输媒体并使用 Wi-Fi,您可能还需要持有 WifiLock,您必须手动获取和释放它。因此,当您开始使用远程 URL 准备 MediaPlayer 时,您应该创建并获取 Wi-Fi 锁。

例如

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,因为它只对内存需求敏感,而对其他与媒体相关的资源短缺不敏感。因此,在使用服务的情况下,您应始终重写 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 是您的应用中媒体播放的推荐解决方案。了解更多

这些页面涵盖了录制、存储和播放音频和视频的相关主题