即使應用程式不在畫面上 (例如使用者正在與其他應用程式互動),您仍可在背景播放媒體。
如要這樣做,請將 MediaPlayer 嵌入 MediaBrowserServiceCompat
服務中,並讓其與其他活動中的 MediaBrowserCompat
互動。
請謹慎實作此用戶端和伺服器設定。我們對在背景服務中執行的播放器如何與系統其他部分互動有一定的期望。如果應用程式無法滿足這些期待,使用者可能會獲得不佳的體驗。詳情請參閱「建構音訊應用程式」。
本頁面說明在服務中實作 MediaPlayer 時,如何管理 MediaPlayer 的特殊操作說明。
以非同步方式執行
與 Activity
一樣,根據預設,Service
中的所有工作都會在單一執行緒中完成。事實上,當您從同一個應用程式執行活動和服務時,預設會使用相同的執行緒 (「主執行緒」)。
服務必須快速處理傳入的意圖,並在回應時絕不執行冗長的運算。您必須以非同步方式執行任何繁重工作或阻斷呼叫:從您自行實作的另一個執行緒,或使用架構的許多非同步處理設施。
舉例來說,如果您從主執行緒使用 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
類別的完整狀態圖。
使用 Wake Lock
在背景播放或串流音樂時,您必須使用喚醒鎖定,以免系統干擾播放作業,例如讓裝置進入休眠狀態。
喚醒鎖定是一種信號,可向系統傳達應用程式正在使用功能,即使手機處於閒置狀態也應保持可用。
為確保在 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
,並手動取得及釋放。因此,當您開始使用遠端網址準備 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
,並在稍後重新建立。另一方面,如果您只想暫停播放一段很短的時間,建議您保留 MediaPlayer
,以免再次建立及準備的額外負擔。
瞭解詳情
如要在應用程式中播放媒體,建議您採用 Jetpack Media3 解決方案。請參閱更多資訊。
以下頁面將說明錄製、儲存及播放音訊和影片的相關主題: