在背景播放媒體

即使應用程式不在畫面上,您仍可在背景播放媒體,例如使用者與其他應用程式互動時。

如要這麼做,請將 MediaPlayer 嵌入 MediaBrowserServiceCompat 服務,並讓 MediaPlayer 與另一個活動中的 MediaBrowserCompat 互動。

請謹慎實作這項用戶端和伺服器設定。在背景服務中執行的播放器與系統其他部分互動時,會受到一些限制。如果應用程式無法滿足這些期望,使用者可能會感到不滿。詳情請參閱「建構音訊應用程式」。

本頁說明在服務中實作 MediaPlayer 時,如何管理 MediaPlayer 的特殊指示。

以非同步方式執行

Activity 類似,Service 中的所有工作預設都會在單一執行緒中完成。事實上,如果您從同一個應用程式執行活動和服務,預設會使用相同的執行緒 (即「主執行緒」)。

服務必須快速處理傳入的意圖,且在回應時絕不能執行長時間的運算。您必須以非同步方式執行任何繁重的工作或封鎖呼叫:可透過您自行實作的另一個執行緒,或使用架構的許多非同步處理設施。

舉例來說,如果您從主執行緒使用 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 會進入「錯誤」狀態,必須重設才能再次使用。詳情請參閱 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 在應用程式中播放媒體。進一步瞭解

這些頁面涵蓋音訊和影片的錄製、儲存及播放相關主題: