安卓MP3播放器開發實例(3)之進度條和歌詞更新的實現
上一次談了音樂播放的實現,這次說下最復雜的進度條和歌詞更新。因為須要在播放的Activity和播放的Service間進行交互,所以就涉及了Activity對Service的綁定以及綁定後數據的傳輸,這個須要對服務綁定熟悉才幹夠理解。原理不復雜。可是步驟略微繁瑣,代碼貼起來可能會非常混亂。
進度條和歌詞放在一起說比較好,不然比較混亂。進度條的調整大家都懂的,就是進度條調到哪裏歌曲的播放就跟到哪裏,歌詞也得跟到哪裏。首先看下上一篇看過的開始button監聽事件中服務的綁定代碼:
//綁定播放服務 bindService(intent, conn,BIND_AUTO_CREATE); //Runnable對象增加消息隊列,在handler綁定的線程中運行,用於歌詞更新 handler.post(updateTimeCallback); start = System.currentTimeMillis();
/** * 傳入bindService()中的回調接口 */ ServiceConnection conn = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName arg0, IBinder binder) {//綁定成功後調用該方法 PlayActivity.this.binder = (Binder)binder; } @Override public void onServiceDisconnected(ComponentName arg0) { } };
handler.post(updateTimeCallback);是將一個Runnable對象增加隊列中。這個Runnable對象updateTimeCallback
就是實現進度條和歌詞更新的核心實現代碼,只是看這個類之前。我們先看下進度條的監聽事件:
/** * 經過試驗假設把進度條調動之前和調動之後合並為一種方式實現歌詞的更新的話會奔潰,原因可能是不同線程間資源的同步問題,僅僅好拆成 * 進度條調動之前(change == false)和調動之後(change == true)兩部分在歌詞更新的線程中運行 * @author yan * */ class SeekBarListener implements OnSeekBarChangeListener{ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar arg0) { Log.d("yinan", "start--------"+seekBar.getProgress()); } @Override public void onStopTrackingTouch(SeekBar arg0) { if(stopMusic == false){ change = true;//進度條進度人為改變。將改變後的進度發送給播放服務 Intent intent = new Intent(); intent.setClass(PlayActivity.this, PlayService.class); intent.putExtra("progress", seekBar.getProgress()); startService(intent); }else{ seekBar.setProgress(0); } } }
如今來看看實現的核心代碼Runnable對象updateTimeCallback的類:
/** * 異步調整進度條位置與歌曲進度一致和顯示歌詞的類 * @author yinan * */ class UpdateTimeCallback implements Runnable{ Queue times = null; Queue messages = null; ArrayList<Queue> queues = null; public UpdateTimeCallback(ArrayList<Queue> queues){ times = queues.get(0); messages = queues.get(1); this.queues = queues; } /** * run方法因為沒有條用start所以並沒有開辟子線程。所以在內部開啟子線程 */ @Override public void run() { total = PlayService.totalLength; if(change == true){ Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeString("change"); try { binder.transact(0, data, reply, 0); } catch (RemoteException e) { e.printStackTrace(); } //直到Service返回才運行這一句 float s = reply.readFloat();//s為歌曲播放的時間進度 float f = s*100; seekBar.setProgress((int)f);//轉化為進度條進度 //此時相應的播放時間點 final long t = (long) (s*total); preparelrc(mp3Info.getLrcName()); times = queues.get(0); messages = queues.get(1); System.out.println("times"+times.size()); System.out.println("messages"+messages.size()); if(stopMusic == false){ new Thread(){ public void run(){ while(nextTime < t){ //從頭遍歷歌詞,時間隊列直到時間點大於當前時間 System.out.println("nextTime"+nextTime);/////////////////////////////// lyric = message; if((times.size()>0)||messages.size()>0){ nextTime = (Long)times.poll(); message = (String)messages.poll(); }else{ lyric = null; stopMusic = true; }//保存時間點剛大於當前時間的上一條歌詞,即最接近當前的歌詞 System.out.println("nextTime"+nextTime); } } }.start(); System.out.println("lyric"+lyric); if(lyric != null){ lrcText.setText(lyric); } } } if(!change){ nowTime = System.currentTimeMillis() - start; seekBar.setProgress((int)(nowTime*100/total)); if(stopMusic == false){ new Thread(){ public void run(){ while(nextTime < nowTime){ //從頭遍歷歌詞。時間隊列直到時間點小於當前時間 System.out.println("nextTime"+nextTime); lyric = message;//保存時間點剛大於當前時間的上一條歌詞,即最接近當前的歌詞 if((times.size()>0)||messages.size()>0){ nextTime = (Long)times.poll(); message = (String)messages.poll(); }else{ lyric = null; stopMusic = true; } } } }.start(); if(lyric != null){ lrcText.setText(lyric); } } } if(stopMusic == true){ handler.removeCallbacks(updateTimeCallback); } handler.postDelayed( updateTimeCallback, 200);//每20毫秒運行一次線程 } }這段代碼大致是當獲取到服務返回的歌曲實時進度的數據時,將進度條的位置進行調整到相應為孩子的處理,然後將歌詞更新到相應的部分。因為進度條要實時跟進。全部UpdateTimeCallback類的run方法是每隔200毫秒就運行一次,到Service中獲取返回值。
這裏是將處理的情況分為進度條被改變了和未被改變兩種情況,用標誌位change表示(一旦進度條被改變了就運行了change為true那一段代碼)。
開子線程是由於對歌詞的處理耗時。不適合放在UI線程中。
詳細的流程是:
首先,Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeString("change");
binder.transact(0, data, reply, 0);
binder就是之前和Service綁定的IBinder對象,binder.transact是將一個Parcel對象(傳入"change"不過一個標誌)傳給Service,然後Service返回歌曲進度,在float s = reply.readFloat()這句中接收,s是一個當前進度占總進度的百分比數。然後將進度條位置設置一下就可以。
然後進行歌詞更新的處理。
首先是對歌詞的準備處理工作,preparelrc(mp3Info.getLrcName()),將lrc格式的歌詞中的時間點和相應的詳細歌詞拆分成兩個隊列。(詳細代碼請看源代碼)然後依據Service返回的歌曲播放進度。對兩個隊列進行遍歷。當有時間點超過進度相應的時間點時,將該時間點相應的歌詞取出,顯示在Activity上。
前面總是說Service返回歌曲實時進度,我們來看看Service中對於數據返回的處理。
當進度條被調整時。會進入Service的onStartCommand方法,進入到下面的if語句中:
if(intent.getIntExtra("progress", 0) != 0){/////////////////////// if(isPlay){ //進度條進度 int progress = intent.getIntExtra("progress", 0); if(progress != 0) //將音樂進度調整到相應位置 mediaPlayer.seekTo(progress*totalLength/100); } }Service又是怎樣實時返回歌曲進度的呢?Service中有一個Binder類。是IBinder的子類:
/* *運行 binder.transact()後運行 */ class FirstBinder extends Binder{ @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { String str = data.readString(); int currentPosition = mediaPlayer.getCurrentPosition(); float s = (float)currentPosition/mediaPlayer.getDuration(); if(isPlay) reply.writeFloat(s); return super.onTransact(code, data, reply, flags); }是的。它就是之前Activity與Service綁定後獲得的IBinder對象。每當Activity的binder.transact(0, data, reply, 0);被調用的時候。Service就會調用onTransact方法。將獲取到的數據裝入Parcel對象reply中,然後將整個IBinder對象返回Activity。
MP3播放器就這樣講完了,可能講的非常亂。希望對各位有幫助。源碼下載鏈接:http://download.csdn.net/detail/sinat_23092639/8933995
安卓MP3播放器開發實例(3)之進度條和歌詞更新的實現