即使應用程式不在畫面上,您仍可在背景播放媒體,例如使用者與其他應用程式互動時。
如要這麼做,請將 MediaPlayer 嵌入 MediaBrowserServiceCompat 服務,並讓 MediaPlayer 與另一個活動中的 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 會進入「錯誤」狀態,必須重設才能再次使用。詳情請參閱 MediaPlayer 類別的完整狀態圖。
使用 Wake Lock
在背景播放或串流播放音樂時,您必須使用喚醒鎖定,防止系統干擾播放作業,例如讓裝置進入休眠狀態。
Wake Lock 會向系統發出信號,指出應用程式正在使用某些功能,即使手機處於閒置狀態,這些功能也應保持可用。
為確保 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);
不過,本範例中取得的 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,以免再次建立及準備時產生額外負擔。
瞭解詳情
建議您使用 Jetpack Media3 在應用程式中播放媒體。進一步瞭解。
這些頁面涵蓋音訊和影片的錄製、儲存及播放相關主題: