Android實(shí)現(xiàn)簡單音樂播放器(MediaPlayer)
Android實(shí)現(xiàn)簡單音樂播放器(MediaPlayer),供大家參考,具體內(nèi)容如下
開發(fā)工具:Andorid Studio 1.3
運(yùn)行環(huán)境:Android 4.4 KitKat
工程內(nèi)容
實(shí)現(xiàn)一個(gè)簡單的音樂播放器,要求功能有:
- 播放、暫停功能;
- 進(jìn)度條顯示播放進(jìn)度功能
- 拖動(dòng)進(jìn)度條改變進(jìn)度功能;
- 后臺(tái)播放功能;
- 停止功能;
- 退出功能;
代碼實(shí)現(xiàn)
導(dǎo)入歌曲到手機(jī)SD卡的Music目錄中,這里我導(dǎo)入了4首歌曲:仙劍六里面的《誓言成暉》、《劍客不能說》、《鏡中人》和《浪花》,也推薦大家聽喔(捂臉
然后新建一個(gè)類MusicService繼承Service,在類中定義一個(gè)MyBinder,有一個(gè)方法用于返回MusicService本身,在重載onBind()方法的時(shí)候返回
public class MusicService extends Service { public final IBinder binder = new MyBinder(); public class MyBinder extends Binder{ MusicService getService() { return MusicService.this; } } @Override public IBinder onBind(Intent intent) { return binder; } }
在MusicService中,聲明一個(gè)MediaPlayer變量,進(jìn)行設(shè)置歌曲路徑,這里我選擇歌曲1作為初始化時(shí)候的歌曲
private String[] musicDir = new String[]{ Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《誓言成暉》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《劍客不能說》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《鏡中人》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《浪花》.mp3"}; private int musicIndex = 1; public static MediaPlayer mp = new MediaPlayer(); public MusicService() { try { musicIndex = 1; mp.setDataSource(musicDir[musicIndex]); mp.prepare(); } catch (Exception e) { Log.d("hint","can't get to the song"); e.printStackTrace(); } }
設(shè)計(jì)一些歌曲播放、暫停、停止、退出相應(yīng)的邏輯,此外我還設(shè)計(jì)了上一首和下一首的邏輯
public void playOrPause() { if(mp.isPlaying()){ mp.pause(); } else { mp.start(); } } public void stop() { if(mp != null) { mp.stop(); try { mp.prepare(); mp.seekTo(0); } catch (Exception e) { e.printStackTrace(); } } } public void nextMusic() { if(mp != null && musicIndex < 3) { mp.stop(); try { mp.reset(); mp.setDataSource(musicDir[musicIndex+1]); musicIndex++; mp.prepare(); mp.seekTo(0); mp.start(); } catch (Exception e) { Log.d("hint", "can't jump next music"); e.printStackTrace(); } } } public void preMusic() { if(mp != null && musicIndex > 0) { mp.stop(); try { mp.reset(); mp.setDataSource(musicDir[musicIndex-1]); musicIndex--; mp.prepare(); mp.seekTo(0); mp.start(); } catch (Exception e) { Log.d("hint", "can't jump pre music"); e.printStackTrace(); } } }
注冊MusicService并賦予權(quán)限,允許讀取外部存儲(chǔ)空間
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <service android:name="com.wsine.west.exp5_AfterClass.MusicService" android:exported="true"></service>
在MainAcitvity中聲明ServiceConnection,調(diào)用bindService保持與MusicService通信,通過intent的事件進(jìn)行通信,在onCreate()函數(shù)中綁定Service
private ServiceConnection sc = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { musicService = ((MusicService.MyBinder)iBinder).getService(); } @Override public void onServiceDisconnected(ComponentName componentName) { musicService = null; } }; private void bindServiceConnection() { Intent intent = new Intent(MainActivity.this, MusicService.class); startService(intent); bindService(intent, sc, this.BIND_AUTO_CREATE); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("hint", "ready to new MusicService"); musicService = new MusicService(); Log.d("hint", "finish to new MusicService"); bindServiceConnection(); seekBar = (SeekBar)this.findViewById(R.id.MusicSeekBar); seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setMax(musicService.mp.getDuration()); musicStatus = (TextView)this.findViewById(R.id.MusicStatus); musicTime = (TextView)this.findViewById(R.id.MusicTime); btnPlayOrPause = (Button)this.findViewById(R.id.BtnPlayorPause); Log.d("hint", Environment.getExternalStorageDirectory().getAbsolutePath()+"/You.mp3"); }
bindService函數(shù)回調(diào)onSerciceConnented函數(shù),通過MusiceService函數(shù)下的onBind()方法獲得binder對(duì)象并實(shí)現(xiàn)綁定
通過Handle實(shí)時(shí)更新UI,這里主要使用了post方法并在Runnable中調(diào)用postDelay方法實(shí)現(xiàn)實(shí)時(shí)更新UI,Handle.post方法在onResume()中調(diào)用,使得程序剛開始時(shí)和重新進(jìn)入應(yīng)用時(shí)能夠更新UI
在Runnable中更新SeekBar的狀態(tài),并設(shè)置SeekBar滑動(dòng)條的響應(yīng)函數(shù),使歌曲跳動(dòng)到指定位置
public android.os.Handler handler = new android.os.Handler(); public Runnable runnable = new Runnable() { @Override public void run() { if(musicService.mp.isPlaying()) { musicStatus.setText(getResources().getString(R.string.playing)); btnPlayOrPause.setText(getResources().getString(R.string.pause).toUpperCase()); } else { musicStatus.setText(getResources().getString(R.string.pause)); btnPlayOrPause.setText(getResources().getString(R.string.play).toUpperCase()); } musicTime.setText(time.format(musicService.mp.getCurrentPosition()) + "/" + time.format(musicService.mp.getDuration())); seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { musicService.mp.seekTo(seekBar.getProgress()); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); handler.postDelayed(runnable, 100); } }; @Override protected void onResume() { if(musicService.mp.isPlaying()) { musicStatus.setText(getResources().getString(R.string.playing)); } else { musicStatus.setText(getResources().getString(R.string.pause)); } seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setMax(musicService.mp.getDuration()); handler.post(runnable); super.onResume(); Log.d("hint", "handler post runnable"); }
給每個(gè)按鈕設(shè)置響應(yīng)函數(shù),在onDestroy()中添加解除綁定,避免內(nèi)存泄漏
public void onClick(View view) { switch (view.getId()) { case R.id.BtnPlayorPause: musicService.playOrPause(); break; case R.id.BtnStop: musicService.stop(); seekBar.setProgress(0); break; case R.id.BtnQuit: handler.removeCallbacks(runnable); unbindService(sc); try { System.exit(0); } catch (Exception e) { e.printStackTrace(); } break; case R.id.btnPre: musicService.preMusic(); break; case R.id.btnNext: musicService.nextMusic(); break; default: break; } } @Override public void onDestroy() { unbindService(sc); super.onDestroy(); }
在Button中賦予onClick屬性指向接口函數(shù)
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/BtnPlayorPause" android:text="@string/btnPlayorPause" android:onClick="onClick"/>
效果圖
打開界面->播放一會(huì)兒進(jìn)度條實(shí)時(shí)變化->拖動(dòng)進(jìn)度條->點(diǎn)擊暫停->點(diǎn)擊Stop->點(diǎn)擊下一首(歌曲時(shí)間變化)->點(diǎn)擊上一首->點(diǎn)擊退出
一些總結(jié)
- 讀取SD卡內(nèi)存的時(shí)候,應(yīng)該使用android.os.Environment庫中的getExternalStorageDirectory()方法,然而并不能生效。應(yīng)該再使用getAbsolutePath()獲取絕對(duì)路徑后讀取音樂才生效。
- 切換歌曲的時(shí)候try塊不能正確執(zhí)行。檢查過后,也是執(zhí)行了stop()函數(shù)后再重新setDataSource()來切換歌曲的,但是沒有效果。查閱資料后,發(fā)現(xiàn)setDataSource()之前需要調(diào)用reSet()方法,才可以重新設(shè)置歌曲。
了解Service中startService(service)和bindService(service, conn, flags)兩種模式的執(zhí)行方法特點(diǎn)及其生命周期,還有為什么這次要一起用
startService方法是讓Service啟動(dòng),讓Service進(jìn)入后臺(tái)running狀態(tài);但是這種方法,service與用戶是不能交互的,更準(zhǔn)確的說法是,service與用戶不能進(jìn)行直接的交互。
因此需要使用bindService方法綁定Service服務(wù),bindService返回一個(gè)binder接口實(shí)例,用戶就可以通過該實(shí)例與Service進(jìn)行交互。
Service的生命周期簡單到不能再簡單了,一條流水線表達(dá)了整個(gè)生命周期。
service的活動(dòng)生命周期是在onStart()之后,這個(gè)方法會(huì)處理通過startServices()方法傳遞來的Intent對(duì)象。音樂service可以通過開打intent對(duì)象來找到要播放的音樂,然后開始后臺(tái)播放。注: service停止時(shí)沒有相應(yīng)的回調(diào)方法,即沒有onStop()方法,只有onDestroy()銷毀方法。
onCreate()方法和onDestroy()方法是針對(duì)所有的services,無論它們是否啟動(dòng),通過Context.startService()和Context.bindService()方法都可以訪問執(zhí)行。然而,只有通過startService()方法啟動(dòng)service服務(wù)時(shí)才會(huì)調(diào)用onStart()方法。
圖片來自網(wǎng)絡(luò),忘記出處了
簡述如何使用Handler實(shí)時(shí)更新UI
方法一:
Handle的post方法,在post的Runable的run方法中,使用postDelay方法再次post該Runable對(duì)象,在Runable中更新UI,達(dá)到實(shí)時(shí)更新UI的目的
方法二:
多開一個(gè)線程,線程寫一個(gè)持續(xù)循環(huán),每次進(jìn)入循環(huán)內(nèi)即post一次Runable,然后休眠1000ms,亦可做到實(shí)時(shí)更新UI
工程下載
傳送門:下載
更多關(guān)于播放器的內(nèi)容請點(diǎn)擊《java播放器功能》進(jìn)行學(xué)習(xí)。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼,需要的朋友可以參考下2017-09-09Android開發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法示例
這篇文章主要介紹了Android開發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法,結(jié)合實(shí)例形式分析了Android FloatingActionButton懸浮按鈕的基本功能、布局、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2019-03-03Eclipse工程轉(zhuǎn)為兼容Android Studio模式的方法步驟圖文詳解
這篇文章主要介紹了Eclipse工程轉(zhuǎn)為兼容Android Studio模式的方法步驟,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12Android開發(fā)實(shí)現(xiàn)跟隨手指的小球效果示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)跟隨手指的小球效果,涉及Android圖形繪制、事件響應(yīng)、界面布局等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04解決Android MediaRecorder錄制視頻過短問題
本文主要介紹Android MediaRecorder,在使用MediaRecorder時(shí)經(jīng)常會(huì)遇到視頻錄制太短問題,這里提供解決問題的實(shí)例代碼以供大家參考2016-07-07