Android实现简单音乐播放器(MediaPlayer)
android实现简单音乐播放器(mediaplayer),供大家参考,具体内容如下
开发工具:andorid studio 1.3
运行环境:android 4.4 kitkat
工程内容
实现一个简单的音乐播放器,要求功能有:
- 播放、暂停功能;
- 进度条显示播放进度功能
- 拖动进度条改变进度功能;
- 后台播放功能;
- 停止功能;
- 退出功能;
代码实现
导入歌曲到手机sd卡的music目录中,这里我导入了4首歌曲:仙剑六里面的《誓言成晖》、《剑客不能说》、《镜中人》和《浪花》,也推荐大家听喔(捂脸
然后新建一个类musicservice继承service,在类中定义一个mybinder,有一个方法用于返回musicservice本身,在重载onbind()方法的时候返回
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中,声明一个mediaplayer变量,进行设置歌曲路径,这里我选择歌曲1作为初始化时候的歌曲
private string[] musicdir = new string[]{ environment.getexternalstoragedirectory().getabsolutepath() + "/music/仙剑奇侠传六-主题曲-《誓言成晖》.mp3", environment.getexternalstoragedirectory().getabsolutepath() + "/music/仙剑奇侠传六-主题曲-《剑客不能说》.mp3", environment.getexternalstoragedirectory().getabsolutepath() + "/music/仙剑奇侠传六-主题曲-《镜中人》.mp3", environment.getexternalstoragedirectory().getabsolutepath() + "/music/仙剑奇侠传六-主题曲-《浪花》.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(); } }
设计一些歌曲播放、暂停、停止、退出相应的逻辑,此外我还设计了上一首和下一首的逻辑
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并赋予权限,允许读取外部存储空间
<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,调用bindservice保持与musicservice通信,通过intent的事件进行通信,在oncreate()函数中绑定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函数回调onserciceconnented函数,通过musiceservice函数下的onbind()方法获得binder对象并实现绑定
通过handle实时更新ui,这里主要使用了post方法并在runnable中调用postdelay方法实现实时更新ui,handle.post方法在onresume()中调用,使得程序刚开始时和重新进入应用时能够更新ui
在runnable中更新seekbar的状态,并设置seekbar滑动条的响应函数,使歌曲跳动到指定位置
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"); }
给每个按钮设置响应函数,在ondestroy()中添加解除绑定,避免内存泄漏
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属性指向接口函数
<button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnplayorpause" android:text="@string/btnplayorpause" android:onclick="onclick"/>
效果图
打开界面->播放一会儿进度条实时变化->拖动进度条->点击暂停->点击stop->点击下一首(歌曲时间变化)->点击上一首->点击退出
一些总结
- 读取sd卡内存的时候,应该使用android.os.environment库中的getexternalstoragedirectory()方法,然而并不能生效。应该再使用getabsolutepath()获取绝对路径后读取音乐才生效。
- 切换歌曲的时候try块不能正确执行。检查过后,也是执行了stop()函数后再重新setdatasource()来切换歌曲的,但是没有效果。查阅资料后,发现setdatasource()之前需要调用reset()方法,才可以重新设置歌曲。
了解service中startservice(service)和bindservice(service, conn, flags)两种模式的执行方法特点及其生命周期,还有为什么这次要一起用
startservice方法是让service启动,让service进入后台running状态;但是这种方法,service与用户是不能交互的,更准确的说法是,service与用户不能进行直接的交互。
因此需要使用bindservice方法绑定service服务,bindservice返回一个binder接口实例,用户就可以通过该实例与service进行交互。
service的生命周期简单到不能再简单了,一条流水线表达了整个生命周期。
service的活动生命周期是在onstart()之后,这个方法会处理通过startservices()方法传递来的intent对象。音乐service可以通过开打intent对象来找到要播放的音乐,然后开始后台播放。注: service停止时没有相应的回调方法,即没有onstop()方法,只有ondestroy()销毁方法。
oncreate()方法和ondestroy()方法是针对所有的services,无论它们是否启动,通过context.startservice()和context.bindservice()方法都可以访问执行。然而,只有通过startservice()方法启动service服务时才会调用onstart()方法。
图片来自网络,忘记出处了
简述如何使用handler实时更新ui
方法一:
handle的post方法,在post的runable的run方法中,使用postdelay方法再次post该runable对象,在runable中更新ui,达到实时更新ui的目的
方法二:
多开一个线程,线程写一个持续循环,每次进入循环内即post一次runable,然后休眠1000ms,亦可做到实时更新ui
工程下载
传送门:下载
更多关于播放器的内容请点击《java播放器功能》进行学习。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。