釋放MediaPlayer
MediaPlayer可能消耗大量的系統資源.因此你應該總是採取一些額外的措失來確保在一個MediaPlayer執行個體上不會掛起太長的時間.當你用完MediaPlayer時,你應該總是調用release()來保證任何分配給MediaPlayer的系統資源被正確地釋放.例如,如果你正在使用MediaPlayer並且你的activity收到了一個對onStop()的調用,你必須釋放MediaPlayer,因為當你的activtiy不再與使用者互動時繼續保持MediaPlayer會使使用者有一點慢的感覺(除非你在背景播放媒體).當你的activityis resumed或restarted,你理所當然的需要建立一個新的MediaPlayer並且在恢複播放前重新準備它.
下面是如何釋放MediaPlayer:
mediaPlayer.release();mediaPlayer = null;
作為一個例子,想像一下如果當你的activitystopped時你忘記了釋放MediaPlayer,而activity重新start時又建立了一個新的MediaPlayer這樣的問題.就像你知道的,當使用者改變螢幕的方向(或用另外的方法改變了裝置的配置),系統處理的方式是重啟activity(預設情況),於是當使用者來迴旋轉裝置時你可能消耗掉了所有的系統資源,因為在每次方向改變時,你都建立了一個新的MediaPlayer但是從不釋放它.
你現在可能對如何在沒有activity時仍然在背景播放媒體感興趣了,請看下一章.
使用帶有MediaPlayer的service
如果你希望你的媒體在你的應用不出現在螢幕上時仍能在背景播放—也就是,你希望當使用者與其它應用互動時仍能繼續播放—那麼你必須啟動一個Service並且通過它控制MediaPlayer執行個體.但此方式下你應該小心慬慎,因為使用者和系統都對一個應用運行一個後台service時應該如何與剩餘的系統互動抱有期望值.如果你的應用不能滿足這些期望,使用者體驗可能很壞.本節描述你應該注意的主要問題並且給出如何達到要求的建議.
非同步運行
首先,跟Activity一樣,預設下所有的Service的工作都是在一個單獨的線程中完成—實際上,如果你從同一個應用中運行一個activity和一個service,它們預設使用同一個線程("主線程").因此,service需要快速處理進入的intent並且永不對它們執行長時間的計算.如果要執行某些重型工作和阻塞調用,你必須非同步地執行它們:可以在你自己實現的另外線程中,也可以使用架構的一些非同步處理工具.
例如,當在主線程中使用一個MediaPlayer,你應該調用prepareAsync()而不是prepare(),並且實現一個MediaPlayer.OnPreparedListener來監聽"準備"完成通知並開始播放.例如:
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.OnErrorListener並把它設定給你的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變為錯誤狀態,你必須在重新使用它之前重設它才行.
使用喚醒鎖
當設計在背景播放媒體的應用時,當你的service正在運行時,裝置可能進入休眠.因為Android系統在休眠時會試著節省電能,那麼系統會試著關閉電話的任何不必要的特性,包括CPU和WiFi.然而,如果你的service現正播放或接收音樂,你就想阻止系統幹涉你的播放工作.
為了在上述情況下保證你的service繼續運行,你必須使用"wakelocks".一個wakelock是一種通知系統在手機空閑時也應為你的應用保留所用特性的途徑.
注意:你總是應該保守的使用wakelocks並且僅在真證需要時才持有它.因為它們會顯著的減少裝置電池的壽命.
當你的MediaPlayer播放時,要保持CPU持續運行,在初始化MediaPlayer時需調用setWakeMode().一旦你這樣做了,MediaPlayer就會在播放時持有一個特定的鎖,並在暫停或停止時釋放它:
mMediaPlayer = new MediaPlayer();// ... other initialization here ...mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
然而,此例中所請求的wakelock只能保證CPU保持清醒.如果你正通過Wi-Fi從網路串流媒體資料,你可能也想持有WifiLock.對它你必須手動請求和釋放.所以,當你使用遠程URL準備MediaPlayer,你應該建立並請求Wi-Filock.例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");wifiLock.acquire();
當你暫停或停止你的媒體或當你不現需要網路時,你應該釋放這個鎖:
wifiLock.release();
作為前台服務運行
Services一般用於執行背景工作,比如擷取郵件,同步資料,下載內容以及其它工作.這些情況下,使用者不會太注意service的執行,並且可能跟本注意不到它們的中斷以及重新運行.
但是現在考慮一下用service播放音樂.很明顯,使用者會非常注意這個service並且一些中斷會嚴重影響使用者體驗.另外,這種service還是使用者在其執行期間想與之互動的.此情況下,此服務應作為一個"foregroundservice"運行.一個前台具有高重要性—系統永不會殺死它,因為它跟使用者直接相關.當運行於前台時,service還必須在狀態通知欄上提供一個通知來保證使用者能看到service正在運行並且允許他們開啟一個activity與service互動.
為了把你的service搞到前台,你必須為狀態列建立一個Notification並且調用startForeground().例如:
String songName;// assign the song name to songNamePendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);Notification notification = new Notification();notification.tickerText = text;notification.icon = R.drawable.play0;notification.flags |= Notification.FLAG_ONGOING_EVENT;notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample", "Playing: " + songName, pi);startForeground(NOTIFICATION_ID, notification);
當你的service在前台運行時,你所配置的通知就出現在裝置的通知區域.如果使用者選擇了這個通知,系統就會調用你提供的PendingIntent.在上例中,它開啟了一個activity(MainActivity).
圖 1示範了你的通知如何顯示給使用者:
圖 1.前台service的通知,左圖顯示了狀態列的通知,右圖顯示了通知開啟的view.
你應該只在使用者需要注意service的執行情況時才使它保持"前台service"的狀態,一旦此情況改變,你就應該調用stopForeground()把前台狀態釋放掉:
stopForeground(true);