使用MediaPlayer
媒體架構的最中重要的組件之一是MediaPlayer類。這個類的對象能夠用最小的步驟來擷取、解碼和播放音視頻。它支援以下幾種不同的媒體來源:
1. 本地資源;
2. 內部的統一資源標識(URI),如可能從內容解析器中來擷取;
3. 外部的URI(流)。
對於Android所支援的的媒體格式列表,請看“Android所支援的媒體格式”文檔。
http://developer.android.com/guide/appendix/media-formats.html
以下是一個如何播放本地原生的音頻資源的樣本,該資源儲存在應用的res/raw/目錄中。
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
在這個情境中,一個“原生”資源是一個系統不使用任何特殊方式來解析的檔案。但是,這種資源的內容不應該是原始的音頻,它應該是一個用其所支援的某種格式進行適當編碼和格式化的媒體檔案。
以下是一個如何使用系統中本地可用的URI來播放的樣本:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
以下是用遠端URL,通過HTTP流來播放的樣本:
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
注意:如果URL指向一個線上媒體檔案的流,那麼該檔案必須具有漸次下載的能力。
警告:在使用setDataSource()方法時,必須撲捉或傳遞IllegalArgumentException和IOException異常。
非同步準備
使用MediaPlayer在原理方面是簡單的,但是要把它正確的跟一個典型的Android應用整合,就需要記住幾件重要的事情。例如,對於prepare()方法的調用可能需要很長的執行時間,因為它可能需要擷取和解碼媒體資料。在這種情況下,任何方法都可能需要很長的執行時間,所以不應該在應用程式的UI線程中調用它。執行的的過程中可能導致UI的掛起,直到該方法返回,這種使用者體驗很壞,並且能夠導致一個ANR(Application
Not Responding)錯誤。即使你認為資源會被很快的載入,但是要記住任何超過十分之一秒響應,都會在UI介面上形成停頓,從而給使用者帶來應用程式執行慢的印象。
要避免UI線程的掛起,就要使用另一個線程來準備MediaPlayer,並且在執行完成的時候給主線程發一個通知。你能夠編寫自己的線程邏輯,但是這種使用MediaPlayer的方式很共同,因此架構通過使用prepareAsync()方法提供了一種便利的方式來完成這個任務。這個方法在後台啟動媒體的準備過程,並立即返回。當媒體被準備完成後,通過setOnPreparedListener()方法所配置的MediaPlayer.OnPreparedListener的onPrepared()方法會被調用。
管理狀態
要記住的MediaPlayer的另一個特點是:它是基於狀態的。也就是說,MediaPlayer有一個內部狀態,在編寫自己的代碼時必須要注意這個狀態,因為某個操作可能只在特定的狀態中才有效。如果在錯誤的狀態執行了一個操作,系統會拋出一個異常或導致其他的不希望的行為發生。
在MediaPlayer類的文檔中顯示了一個完整的狀態圖,它闡明了把MediaPlayer從一個種狀態轉移到另一種狀態的方法。例如,當建立一個新的MediaPlayer對象時,它是處於Idle狀態。在這個時點,應該通過調用setDataSource()方法來初始化,接下來是Initialized狀態。之後必須使用prepare()或prepareAsync()方法來準備媒體。在MediaPlayer完成準備工作時,它會進入Prepared狀態,這意味著能夠調用start()方法來播放媒體。這時,就像圖中示範的那樣,通過調用start()、pause()、和seekTo()方法在Started、Paused和PlaybackCompleted狀態之間進行切換。當調用stop()方法時,要注意在再次準備MediaPlayer之前不能夠再調用start()方法了。
在編寫跟MediaPlayer對象互動的代碼時,要始終記住這個狀態圖,因為在錯誤的狀態下調用它的方法是最常見的Bug。
釋放MediaPlayer對象
MediaPlayer能夠消化有價值的系統資源。因此,始終需要另外的措施來確保MediaPlayer不會因為長時間的執行個體化而被掛起。當處理完成時,應該始終調用release()方法來確保其所佔用的系統資源被正確的釋放。例如,如果你正在使用MediaPlayer,並且Activity收到了一個onStop()調用,你就必須釋放MediaPlayer對象,因為Activity已經不再跟使用者進行互動了,所以在持有這個MediaPlayer對象已經毫無意義了(除非要在背景播放媒體)。當Activity處於恢複態或重啟態的時候,你需要建立一個新的MediaPlayer對象,並且在恢複播放之前要再次準備它。
以下是應該如何釋放和取消MediaPlayer對象的樣本:
mediaPlayer.release();
mediaPlayer = null;
作為一個例子,也要考慮在Activity終止時忘記釋放MediaPlayer對象所可能發生的問題,因為在該Activity每次重啟時都要建立一個新的MediaPlayer對象。如你所知,當使用者改變螢幕的方向時(或者用另一種方式來改變裝置配置),系統都要通過重啟Activity(預設)來處理這種改變,因此當使用者反覆在橫豎屏之間切換時,就可能很快消耗所有的系統資源,因為每次方向的改變,都要建立一個新的MediaPlayer對象,而之前的還不曾釋放掉。
你可能會想到,如果使用者離開Activity,那麼在背景播放媒體所發生的事情,內建的Music應用程式就使用了這種行為。在這種情境中,需要一個Service來控制MediaPlayer對象。