在Android系統中,有兩種播放聲音的方式,一種是通過MediaPlayer,另外一種是通過SoundPool。前者主要用於播放長時間的音樂,而後者用於播放小段小段的音效,像按鍵音這種,其優點是資源佔用了小,同時能夠載入多個聲音片段,再根據需要選擇播放。下面分別介紹這兩種方式:
1、MediaPlayer
MediaPlayer有兩種建立方式,方式一:
MediaPlayer mp = new MediaPlayer()
通過這種方式建立MediaPlayer對象,必須在建立對象之後使用下面的語句:
mp.setDataSource("filePath");mp.prepare(); 然後就是調用start()方法。注意使用這種方法設定播放的音樂的時候需要使用的是路徑,這樣對於一般的應用使用起來就不是很方便,不能直接打包到apk中。
方式二:
mp = MediaPlayer.create(this, R.raw.music);
通過這種方式建立MediaPlayer對象,就不需要setDataSource()和prepare()了,直接start()就好了。
當需要停止播放音樂的時候,使用下面的方式:
if(mp.isPlaying()){mp.stop();}mp.reset(); 需要注意的是,在stop之前一定要確認mp現正播放,因為如果已經停止播放了,再次stop會引發錯誤的,原因就是MediaPlayer有一個嚴格的生命週期,在錯誤的時期調用錯誤的方法,是一定會報錯的。
2、SoundPool
SoundPool在播放音效的時候特別的好用,為什麼呢?因為它可以一次load較多的聲音片段。下面首先介紹其基本的使用方法:
//建立SoundPool sp = new SoundPool(10, StreamType.MUSIC, 5);//載入soundPoolMap = new HashMap();soundPoolMap.put(0, soundPool.load(context, R.raw.sound1, 1));//播放soundPool.play(soundPoolMap.get(0), 1, 1, 0, 0, 1);
各個函數中參數的具體意義不是重點,這裡就不做介紹了,各位自己去查一下。我想要說的是,如果各位在主線程中直接這麼做,很可能會報錯(Sample X Not Ready),這是為什麼呢?
原來,SoundPool.load()這個函數是立即返回的,也就是說,不管載入好了沒有,這個函數都會返回,但是實際上非常有可能聲音尚未載入結束,那應該怎麼做呢?實際上,系統在load完成之後,會發送廣播,這時候才能播放。又因為這是一個費時的操作,所以最好建立一個線程來實現。下面是我的實現方案:
Handler handler;static SoundPool soundPool;static HashMap soundPoolMap;String TAG = "wtianok";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new PlaceholderFragment()).commit();}soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 5);soundPoolMap = new HashMap();// 載入soundPoolnew LoadSoundPoolThread().run(this);handler = new Handler() {public void handleMessage(android.os.Message msg) {if (msg.what == MessageType.SOUND_POOL_READY.ordinal()) {Log.d(TAG, "finish load message received");//do somethind}}}}private class LoadSoundPoolThread extends Thread {public void run(Context context) {Log.d(TAG, "start to load soundpool");soundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());soundPool.load(context, R.raw.sound1, 1);soundPool.load(context, R.raw.sound2, 1);soundPool.load(context, R.raw.sound3, 1);soundPool.load(context, R.raw.sound4, 1);soundPool.load(context, R.raw.sound5, 1);soundPool.load(context, R.raw.sound6, 1);soundPool.load(context, R.raw.sound7, 1);}private class MyOnLoadCompleteListener implementsOnLoadCompleteListener {int count = 1;@Overridepublic void onLoadComplete(SoundPool soundPool, int sampleId,int status) { //注意這裡的形參Log.d(TAG, "load " + count + " sound");soundPoolMap.put(count, sampleId);if (count == 7) {Log.d(TAG, "finish load soundpool");MainActivity.soundPool = soundPool;sendMsg(MessageType.SOUND_POOL_READY);count = 1;}count++;}}}private void sendMsg(MessageType micTestStartRecord) {Message message = Message.obtain();message.what = micTestStartRecord.ordinal();handler.sendMessage(message);}
需要注意的是,在完成載入之後,我使用了下面這一句:
MainActivity.soundPool = soundPool;
如果不加這一句,我在主線程中play的時候,依然會報Sample X Not Ready錯誤,而如果在收到廣播之後調用play是沒有問題的。仔細看,發現在接收廣播的onLoadComplete()函數中,實際上調用的是函數的局部參數soundPool,而不是全域的soundPool,但是從代碼來看,load的時候調用的確實全域的soundPool,那為什麼用全域的soundPool就不能播放呢?我到現在還是沒有想通,但是項目還是要繼續,只好加了上面那一句,將全域的soundPool重新賦值,事實證明,這樣做可以。