使用服務的MediaPlayer
如果想要的媒體在背景播放,即使應用程式不在螢幕上,也就是說在使用者跟其他應用程式互動時,媒體檔案也能繼續播放,那麼就必須啟動一個服務(Service),並且在服務中控制MediaPlayer執行個體。你應該關注這種情況,因為使用者和系統都希望運行後台服務的應用程式應該跟系統的空閑時間互動。如果應用程式不能滿足這種期望,那麼使用者可能會有很壞的體驗。本節會向你介紹這些主要的問題,並提供解決它們的方法。
非同步運行
首先,實際上預設情況下,Activity以及在Service中的所有工作都是在一個單一線程中執行的,如果在同一個應用程式中運行一個Activity和Service,那麼預設情況下,它們都會使用同一個線程(main線程)。因此,服務需要快速的處理輸入的請求,並且在響應請求時,不要處理長時的計算處理。如果有繁重的工作或阻塞調用,就必須採用非同步方式:既可以採用另一個線程來執行你的處理,也可以使用很多由架構提供的便利的非同步處理。
例如,在main線程中使用MediaPlayer時,應該調用prepareAsync()方法,而不是prepare(),並且為了在準備完成時便於通知你啟動播放處理,你要實現MediaPlayer.OnPrepare監聽器,例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // initialize it here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
處理非同步錯誤
在同步操作上,通常錯誤會發出一個異常訊號或錯誤碼,但是在使用非同步資源時,應該確保應用程式擷取正確的錯誤通知。在MediaPlayer情境中,通過實現MediaPlayer.OnError監聽器,並把它設定給MediaPlayer執行個體來達到目的:
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.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狀態,在你能夠在再次使用它之前,你必須要對它進行重設。
使用喚醒鎖(wake lock)
在設計在背景播放媒體的應用程式時,在服務還在運行時,裝置可能進入休眠狀態。因為Android系統會在裝置休眠時來嘗試儲存電量,所以系統會嘗試關掉所有不需要的電話功能,包括CPU和WiFi等硬體。但是,如果你的服務現正播放或流化音樂,就要防止系統對你的播放動作的幹擾。
為了確保你的服務能夠在這些條件下能夠繼續運行,就必須使用“wake locks”。喚醒鎖是一種以訊號的方式,把應用程式正在使用的功能發送給系統,要求系統即使是在系統閒置時候也要保留這些功能的有效性。
注意:應該盡量少的使用wake locks,只在真正需要的時候才持有它們,因為它們會直接減少電池的使用時間。
在MediaPlayer播放過程中要保證CPU繼續運行,就要在MediaPlayer初始化時,調用setWakeMode()方法。一旦你調用了這個方法,MediaPlayer對象就在播放時持有了這個特定的鎖,並且要在掛起或終止時釋放這個鎖:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
但是,在這個例子中所獲得的喚醒鎖只保證CPU保持工作狀態。如果現正播放的流媒體是基於網路的,並且你正在使用Wi-Fi功能,那麼還要持有WifiLock,這個鎖必須要手動的擷取和釋放。因此在使用遠端URL來開始準備MediaPlayer時,應該建立和擷取Wi-Fi鎖。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
在服務掛起或終止媒體播放時,或在不在需要網路時,你就應該釋放這個鎖:
wifiLock.release();