即使您的应用不在屏幕上,例如用户正在与其它应用交互时,您也可以在后台播放媒体。
为此,您需要将 MediaPlayer 嵌入到 MediaBrowserServiceCompat
服务中,并使其与另一个 Activity 中的 MediaBrowserCompat
进行交互。
实现此客户端和服务器设置时请务必谨慎。后台服务中运行的播放器如何与系统其余部分交互存在预期。如果您的应用不满足这些预期,用户可能会获得糟糕的体验。有关详细信息,请参阅构建音频应用。
本页面介绍了在服务中实现 MediaPlayer 时管理 MediaPlayer 的特殊说明。
异步运行
与 Activity
一样,Service
中的所有工作默认都在单个线程中完成。事实上,当您从同一个应用运行 Activity 和 Service 时,它们默认使用相同的线程(“主线程”)。
服务必须快速处理传入的 Intent,并且在响应时绝不能执行耗时计算。您必须异步执行任何繁重工作或阻塞调用:可以通过自己实现的另一个线程,或者使用框架的许多异步处理工具。
例如,当您从主线程使用 MediaPlayer
时,您应该
- 调用
prepareAsync()
而不是prepare()
,并且 - 实现
MediaPlayer.OnPreparedListener
以便在准备完成并可以开始播放时收到通知。
例如
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 是您的应用中媒体播放的推荐解决方案。了解更多。
这些页面涵盖了录制、存储和播放音频和视频的相关主题