如果你有一台Android裝置,就會注意到當你按下增大或降低音量按鈕時,你所控制的不同音量設定取決於你正在啟動並執行應用程式。在通話中,你控制的是輸入語音流的音量;在視頻播放器中,你控制的是視頻音訊音量;在主畫面上,你控制的是鈴聲的音量。Android為不同的目的提供不同音頻流。當我們在遊戲中播放音頻時,可使用類來輸出音效和音樂到特定的音樂流。不過,在我們想播放音效或音樂之前,需要確定音量按鈕控制了正確的音頻流。為此,我們使用Context介面的另一個方法:context.setVolumeControlStream(AudioManager.STREAM_MUSIC);一如既往,Context的實現仍然由我們的活動來負責。調用該方法之後,音量按鈕就控制了該音樂流,後面我們就可使用它來輸出我們的音效和音樂。在活動的生命週期內我們只需要調用該方法一次,最好是在Activity.onCreate()方法中調用它。首先我們要分清音樂流和音效的不同。後者一般是儲存在記憶體中且其長度不會超過幾秒鐘。Android系統給我們提供了一個SoundPool類,使用它可以很容易實現音效播放。我們可以很簡單地初始化一個新的SoundPool執行個體,如下所示:SoundPool soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);第一個參數指定在同一時刻我們最多能播放多少個音效。這並不是說我們不能載入更多的音效檔案,它只不過是限制可同時播放的音效個數。第二個參數指定了SoundPool使用什麼音頻流來輸出該音頻。我們在這裡選擇音樂流,同時也已經為它設定好音量控制項。最後一個參數現在沒有使用,它應該為預設值0.為了從一個音頻檔案載入音效到堆記憶體中,我們可使用SoundPool.load()方法。所有的檔案都儲存在assets/目錄下,因此我們需要重載SoundPool.load()方法。所有的檔案都儲存在assets/目錄下,因此我們需要重載SoundPool.load()方法來獲得一個AssetFileDescriptor。我們怎麼獲得AssetFileDescriptor呢?使用AssetManager。這裡我們使用SoundPool從assets/目錄載入一個名為explosion.ogg的OGG檔案:AssetFileDescriptor descriptor = assetManager.openFd("explosion.ogg");int explosionId = soundPool.load(descriptor, 1);通過AssetManager.openFd()方法可直接獲得AssetFileDescriptor,而通過SoundPool可很容易地載入音效,第二個參數用於指定該音效的優先順序。這個參數目前未使用,為了以後的相容應設定為1.SoundPool.load()方法將返回一個整型值,它將作為一個控制代碼用於載入的音效。當我們想播放音效時,只需要指定該控制代碼,SoundPool就知道該播放哪個音頻。soundPool.play(explosionId, 1.0f, 1.0f, 0, 0, 1);第一個參數是從SoundPool.load()方法接受控制代碼。接下來兩個參數分別用於指定左右通道的音量,其值應該從0(靜音)到1(最大)接下來兩個參數我們很少使用,其中第一個參數是優先順序,目前沒有使用,並且應該設定為0.而另一個參數用於指定音效迴圈播放的頻率,一般不建議迴圈播放音效,因此設定為0。最後一個參數是播放速率,將其設定為大於1時,音效播放的速度將會比其在錄製時快;而將它設定為小於1時,播放該音效就會比較慢。當我們不再需要一個音效並希望釋放記憶體時,可使用SoundPool.unload()方法:soundPool.unload(explosionId);我們只需要將從SoundPool.load()方法接收的音效控制代碼傳入即可,該方法會將音效從記憶體卸載。當我們完成所有的音效輸出且不再需要SoundPool時,需要調用SoundPool.release()方法來釋放SoundPool所佔用的所有資源。當然,在釋放之後,我們不能再使用SoundPool,而且SoundPool所載入的所有音效也會被釋放。現在編寫一個簡單的測試活動,每當單擊螢幕時它就播放一個爆炸音效。代碼如下:[java] package org.example.ch04_android_basics; import java.io.IOException; import android.app.Activity; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.media.AudioManager; import android.media.SoundPool; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.TextView; public class SoundPoolTest extends Activity implements OnTouchListener{ SoundPool soundPool; int explosionId = -1; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); TextView textView = new TextView(this); textView.setOnTouchListener(this); setContentView(textView); setVolumeControlStream(AudioManager.STREAM_MUSIC); soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0); try{ AssetManager assetManager = getAssets(); AssetFileDescriptor descriptor = assetManager .openFd("explosion.ogg"); explosionId = soundPool.load(descriptor, 1); }catch(IOException e){ textView.setText("Couldn't load sound effect from asset, " + e.getMessage()); } } @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction() == MotionEvent.ACTION_UP){ if(explosionId != -1){ soundPool.play(explosionId, 1, 1, 0, 0, 1); } } return true; } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); soundPool.release(); } } SoundPool在處理MP3檔案或長的音頻檔案時會有問題,長檔案的定義超過5或6秒鐘。一般建議使用OGG音頻檔案來代替MP3檔案,並儘可能使用低的採樣率和期間,同時保持音效品質。 短小的音效很時候放在Android應用程式從作業系統分配到的堆記憶體中,而包含較長音樂檔案的大音頻檔案就很不適合了。為此,我們就需要將音樂以流的方式輸出到音頻硬體上,這就意味著每次我們只能讀入一小塊資料,該資料足於解碼成原生的PCM資料並輸出到音頻晶片上。這聽起來挺嚇人的。不過幸運的是,我們有MediaPlayer類,它能處理的所有事情。初始化MediaPlayer類:MediaPlayer mediaPlayer = new MediaPlayer();接下來我們需要告訴MediaPlayer播放什麼檔案,這同樣通過AssetFileDescriptor來實現:AssetFileDescriptor descriptor = assetManager.openFd("music.ogg");mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());這裡稍微比SoundPool中複雜一點。MediaPlayer.setDataSource()方法並沒有直接擷取AssetFileDescriptor,而是使用一個FileDescriptor,通過AssetFileDescriptor().getFileDescriptor()方法擷取該描述符,除此之外我們還需要指定音頻檔案的位移量和長度。為什麼是位移量?實際上資源是以單個檔案形式儲存的,為了讓MediaPlayer獲得檔案的起始地址,我們需要將asset檔案夾中的檔案的位移量提供給它。在播放該音樂檔案之前,需要再調用一個方法為MediaPlayer的播放做準備:MediaPlayer.prepare();這將實際地開啟檔案,檢查它是否可以讀取並用MediaPlayer執行個體來進行播放。從這裡開始,我們就可以隨意地播放、暫停和停止音頻檔案,也可以設定迴圈播放和改變音量。啟動播放可通過調用下面的方法進行:mediaPlayer.start();注意,該方法必須在成功調用MediaPlayer.prepare()方法之後才能調用(你將注意到它是否會拋出一個運行時異常)。開始播放後,我們可以通過調用pause()方法來暫停播放:MediaPlayer.pause();只有我們成功準備好MediaPlayer並已啟動播放,調用此方法才會生效。為了恢複一個暫停MediaPlayer,可再次調用MediaPlayer.start()方法而無需任何準備。通過調用下面的方法可停止播放:MediaPlayer.stop();注意,當我們想啟動一個停止的MediaPlayer時,需要先再次調用MediaPlayer.prepare()方法。我們可通過下面方法設定迴圈播放:MediaPlayer.setLooping(true);可通過下面的方法來調整音樂播放的音量:MediaPlayer.setVolume(1, 1);這會重新設定左右聲道的音量,文檔中沒有指定這兩個參數的定義範圍。從多次嘗試的結果看,有效值應該是0-1。最後,我們需要一個方法來檢查該播放是否完成,有兩種方式可實現這一點。對於第一種方式,可向MediaPlayer註冊一個OnCompletionListener,當播放完成時它就會被調用:mediaPlayer.setOnCompletionListener(listener);如果我們想輪詢MediaPlayer的狀態,則可使用下面方法:boolean isPlaying = mediaPlayer.isPlaying();注意,如果MediaPlayer被設定成迴圈播放,前面兩個方法都無法指示該MediaPlayer已停止。最後,如果MediaPlayer執行個體完成了所有的操作,就需要通過下面的方法來確保它所佔用的資源得以釋放:mediaPlayer.release();在丟棄一個執行個體前,執行這個操作應是很好的實踐。如果我們沒有將MediaPlayer設定成迴圈且播放已結束,就可以通過MediaPlayer.prepare()和MediaPlayer.start()方法重啟MediaPlayer。大多數這些方法都是非同步,因此當調用MediaPlayer.stop()時,MediaPlayer.isPlaying()方法可能還需要一點時間才能返回。現在編寫一個測試活動,用迴圈模式播放assets/目錄下的一個音頻檔案。該音效將根據活動的生命週期實現暫停和恢複,當活動暫停時,音樂也要暫停;而活動恢複時,音樂也要從上次暫停地方開始播放。代碼如下:[java] package org.example.ch04_android_basics; import java.io.IOException; import android.app.Activity; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.widget.TextView; public class MediaPlayerTest extends Activity { MediaPlayer mediaPlayer; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); TextView textView = new TextView(this); setContentView(textView); setVolumeControlStream(AudioManager.STREAM_MUSIC); mediaPlayer = new MediaPlayer(); try{ AssetManager assetManager = getAssets(); AssetFileDescriptor descriptor = assetManager.openFd("music.ogg"); mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()); mediaPlayer.prepare(); mediaPlayer.setLooping(true); }catch(IOException e){ textView.setText("Couldn't load music file, " + e.getMessage()); mediaPlayer = null; } } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); if(mediaPlayer != null){ mediaPlayer.pause(); if(isFinishing()){ mediaPlayer.stop(); mediaPlayer.release(); } www.2cto.com } } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); if(mediaPlayer != null) mediaPlayer.start(); } } 在onResume()方法中,我們只需啟動MediaPlayer(如果已經成功建立它)。onResume()方法是一個處理該操作的完美地方,因為它在onCreate()方法和onPause()方法之後被調用。在第一種情況下,它將第一次啟動播放;在第二種情況下,它將簡單地恢複已暫停MediaPlayer。在onPause()方法中,我們暫停MediaPlayer。如果該活動被銷毀,我們還需要停止該MediaPlayer並釋放所有資源