这篇文章将为大家详细讲解有关android10.0如何实现本地音乐播放?,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
1.概述
本篇文章仅是android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小小的赞。这个项目中还有很多不足的地方,如:在按键中设置图片文字,这些正常的应该交给handler处理,我只是粗略地完成这个项目。测试环境:android10.0。实现:自动播放下一首,正常音乐的功能,全屏显示。
android10.0是内外分存了的,应用是没有权限读取内存的,需要在配置文件中application中加上属性:android:requestlegacyexternalstorage=“true”,不加可能可以读取歌曲,但是无法播放。
2.效果截图
截图显示不同是因为这不是同一时间截的,只是一个效果图
3.读取本地音乐以及保存歌曲
①先在androidmanifest文件里面配置权限
②目前基本上的手机使用静态权限是不够的,需要动态获取权限,因此需要在mainactivity里面动态获取,在oncreate方法里调用方法
private void check(){ if (build.version.sdk_int >= build.version_codes.m) { if (checkselfpermission(manifest.permission.write_external_storage) != packagemanager.permission_granted ) { requestpermissions(new string[]{manifest.permission.write_external_storage}, 1); log.d(tag,"---------------------写权限不够-----------------"); } if(checkselfpermission(manifest.permission.read_external_storage)!= packagemanager.permission_granted ){ requestpermissions(new string[]{manifest.permission.read_external_storage}, 2); log.d(tag,"---------------------读权限不够-----------------"); } } }
③再去实现权限的回调方法,与activity的oncreate方法是同一级别的
@override public void onrequestpermissionsresult(int requestcode, @nonnull string[] permissions, @nonnull int[] grantresults) { super.onrequestpermissionsresult(requestcode, permissions, grantresults); switch (requestcode) { case 1: if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) { log.d(tag, "---------------------写权限够了-----------------------------"); } break; case 2: if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) { log.d(tag, "---------------------读权限够了-----------------------------"); } break; } }
④创建一个工具类mp3info,用来保存音乐信息的,里面主要是一些get和set方法
public class mp3info { private string url;//路径 private string title;//歌曲名 private string artist;//艺术家 private long duration;//歌曲时长 private long id; private long album;//专辑图片 }
⑤创建一个musicutil类,通过contentporvider的接口获取歌曲信息
public class musicutil { //获取专辑封面的ui private static final string tag="musicutil"; private static final uri albumarturi=uri.parse("content://media/external/audio/albumart"); //生成歌曲列表 public static listgetmp3infolist(context context){ cursor cursor=context.getcontentresolver().query(mediastore.audio.media.external_content_uri, null, null, null,null); list mp3infolist=new arraylist<>(); while(cursor.movetonext()){ mp3info mp3info=new mp3info(); mp3info.set));//path mp3info.settitle(cursor.getstring(cursor.getcolumnindex(mediastore.audio.media.title))); mp3info.setartist(cursor.getstring(cursor.getcolumnindex(mediastore.audio.media.artist))); mp3info.setduration(cursor.getlong(cursor.getcolumnindex(mediastore.audio.media.duration))); mp3info.setid(cursor.getlong(cursor.getcolumnindex(mediastore.audio.media._id))); mp3info.setalbum(cursor.getint(cursor.getcolumnindex(mediastore.audio.media.album_id))); mp3infolist.add(mp3info); } return mp3infolist; } //格式化时间,转换为分/秒 public static string formattime(long time){ string min = time / (1000 * 60) ""; string sec = time % (1000 * 60) ""; if (min.length() < 2) { min = "0" time / (1000 * 60) ""; } else { min = time / (1000 * 60) ""; } if (sec.length() == 4) { sec = "0" (time % (1000 * 60)) ""; } else if (sec.length() == 3) { sec = "00" (time % (1000 * 60)) ""; } else if (sec.length() == 2) { sec = "000" (time % (1000 * 60)) ""; } else if (sec.length() == 1) { sec = "0000" (time % (1000 * 60)) ""; } return min ":" sec.trim().substring(0, 2); } //获取专辑图片,目前是只能获取手机自带歌曲的专辑图片,如果手机有酷狗,qq音乐之类的,可能无法获取专辑图片 //因为他们的uri不知道。 public bitmap getartwork(context context, long song_id, long album_id, boolean allowdefalut, boolean small){ if(album_id < 0) { if(song_id < 0) { bitmap bm = getartworkfromfile(context, song_id, -1); if(bm != null) { return bm; } } if(allowdefalut) { return getdefaultartwork(context, small); } return null; } contentresolver res = context.getcontentresolver(); uri uri = contenturis.withappendedid(albumarturi, album_id); if(uri != null) { inputstream in = null; try { in = res.openinputstream(uri); bitmapfactory.options options = new bitmapfactory.options(); //先制定原始大小 options.insamplesize = 1; //只进行大小判断 options.injustdecodebounds = true; //调用此方法得到options得到图片的大小 bitmapfactory.decodestream(in, null, options); /** 我们的目标是在你n pixel的画面上显示。 所以需要调用computesamplesize得到图片缩放的比例 **/ /** 这里的target为800是根据默认专辑图片大小决定的,800只是测试数字但是试验后发现完美的结合 **/ if(small){ options.insamplesize = computesamplesize(options, 40); } else{ options.insamplesize = computesamplesize(options, 600); } // 我们得到了缩放比例,现在开始正式读入bitmap数据 options.injustdecodebounds = false; options.indither = false; options.inpreferredconfig = bitmap.config.argb_8888; in = res.openinputstream(uri); return bitmapfactory.decodestream(in, null, options); } catch (filenotfoundexception e) { bitmap bm = getartworkfromfile(context, song_id, album_id); if(bm != null) { if(bm.getconfig() == null) { bm = bm.copy(bitmap.config.rgb_565, false); if(bm == null && allowdefalut) { return getdefaultartwork(context, small); } } } else if(allowdefalut) { bm = getdefaultartwork(context, small); } return bm; } finally { try { if(in != null) { in.close(); } } catch (ioexception e) { e.printstacktrace(); } } } return null; } /** * 从文件当中获取专辑封面位图 * @param context * @param songid * @param albumid * @return */ private static bitmap getartworkfromfile(context context, long songid, long albumid){ bitmap bm = null; if(albumid < 0 && songid < 0) { throw new illegalargumentexception("---------------------" tag "must specify an album or a song id"); } try { bitmapfactory.options options = new bitmapfactory.options(); filedescriptor fd = null; if(albumid < 0){ uri uri = uri.parse("content://media/external/audio/media/" songid "/albumart"); parcelfiledescriptor pfd = context.getcontentresolver().openfiledescriptor(uri, "r"); if(pfd != null) { fd = pfd.getfiledescriptor(); } } else { uri uri = contenturis.withappendedid(albumarturi, albumid); parcelfiledescriptor pfd = context.getcontentresolver().openfiledescriptor(uri, "r"); if(pfd != null) { fd = pfd.getfiledescriptor(); } } options.insamplesize = 1; // 只进行大小判断 options.injustdecodebounds = true; // 调用此方法得到options得到图片大小 bitmapfactory.decodefiledescriptor(fd, null, options); // 我们的目标是在800pixel的画面上显示 // 所以需要调用computesamplesize得到图片缩放的比例 options.insamplesize = 100; // 我们得到了缩放的比例,现在开始正式读入bitmap数据 options.injustdecodebounds = false; options.indither = false; options.inpreferredconfig = bitmap.config.argb_8888; //根据options参数,减少所需要的内存 bm = bitmapfactory.decodefiledescriptor(fd, null, options); } catch (filenotfoundexception e) { e.printstacktrace(); } return bm; } /** * 获取默认专辑图片 * @param context * @return */ @suppresslint("resourcetype") public static bitmap getdefaultartwork(context context, boolean small) { bitmapfactory.options opts = new bitmapfactory.options(); opts.inpreferredconfig = bitmap.config.rgb_565; if(small){ //返回小图片 //return bitmapfactory.decodestream(context.getresources().openrawresource(r.drawable.default_picture), null, opts); } return bitmapfactory.decodestream(context.getresources().openrawresource(r.drawable.default_picture), null, opts); } /** * 对图片进行合适的缩放 * @param options * @param target * @return */ public static int computesamplesize(bitmapfactory.options options, int target) { int w = options.outwidth; int h = options.outheight; int candidatew = w / target; int candidateh = h / target; int candidate = math.max(candidatew, candidateh); if(candidate == 0) { return 1; } if(candidate > 1) { if((w > target) && (w / candidate) < target) { candidate -= 1; } } if(candidate > 1) { if((h > target) && (h / candidate) < target) { candidate -= 1; } } return candidate; } }
⑥为列表设置adapter,新建一个myadapter类继承baseadapter,然后在重写的getview里面设置显示的控件
@override public view getview(int position, view convertview, viewgroup parent) { if(convertview==null){ holder=new viewholder(); convertview=view.inflate(context, r.layout.list_item,null); holder.tv_title=convertview.findviewbyid(r.id.tv_title); holder.tv_artist=convertview.findviewbyid(r.id.tv_artist); holder.tv_duration=convertview.findviewbyid(r.id.tv_duration); holder.tv_position=convertview.findviewbyid(r.id.tv_position); convertview.settag(holder); }else { holder= (viewholder) convertview.gettag(); } holder.tv_title.settext(list.get(position).gettitle()); holder.tv_artist.settext(list.get(position).getartist()); long duration = list.get(position).getduration(); string time= musicutil.formattime(duration); holder.tv_duration.settext(time); holder.tv_position.settext(position 1 ""); if(currentitem == position){ holder.tv_title.setselected(true); holder.tv_position.setselected(true); holder.tv_duration.setselected(true); holder.tv_artist.setselected(true); }else{ holder.tv_title.setselected(false); holder.tv_position.setselected(false); holder.tv_duration.setselected(false); holder.tv_artist.setselected(false); } return convertview; } class viewholder{ textview tv_title;//歌曲名 textview tv_artist;//歌手 textview tv_duration;//时长 textview tv_position;//序号 }
4.使用service实现后台播放
使用的是bindservice,这样service的生命周期就和activity的生命周期绑定在一起了。创建一个musicservice。注意:销毁service的时候需要将音乐对象release。
①service实现功能,在onbind方法里面实例化音乐播放对象
@override public ibinder onbind(intent intent) { log.d(tag,"onbind is call"); mybinder=new mybinder(); return mybinder; }
②在mybinder()里面实现音乐的各种功能,使用的是内部类,初始化部分请看源代码包
public class mybinder extends binder{ private int index=0;//歌曲索引 //播放音乐 public void playmusic(int index){ this.index=index; try { file file=new file(list.get(this.index).get); if(!file.exists()){ log.d(tag,"------------------------------文件不存在------------------------------"); return ; }else{ log.d(tag,"------------------------------文件:" file.getpath() "存在 ------------------------------"); } if(mediaplayer!=null){ mediaplayer.reset(); mediaplayer.release(); } mediaplayer=new mediaplayer(); string str=list.get(this.index).get; mediaplayer.setdatasource(str); log.d(tag,list.get(this.index).get ""); mediaplayer.prepare(); mediaplayer.start(); } catch (ioexception e) { e.printstacktrace(); } } //暂停音乐 public void pausemusic(){ if(mediaplayer.isplaying()){ mediaplayer.pause(); } } //关闭音乐 public void closemusic(){ if(mediaplayer!=null){ mediaplayer.release(); } } //下一首 public void nextmusic(){ if(index>=list.size()-1){ this.index=0; }else{ this.index =1; } playmusic(this.index); } //上一首 public void preciousmusic(){ if(index<=0){ this.index=list.size()-1; }else{ this.index-=1; } playmusic(this.index); } //获取歌曲时长 public int getprogress(int dex){ return (int)list.get(dex).getduration(); } public int getprogress(){ return (int)list.get(index).getduration(); } //获取当前播放位置 public int getplayposition(){ return mediaplayer.getcurrentposition(); } //移动到当前点播放 public void seektoposition(int m){ mediaplayer.seekto(m); } }
③在mainactivity里面绑定
a.先实例化一个serviceconnection对象
private serviceconnection connection=new serviceconnection() { @override public void onserviceconnected(componentname name, ibinder service) { mybinder= (musicservice.mybinder) service; seekbar.setmax(mybinder.getprogress()); seekbar.setonseekbarchangelistener(new seekbar.onseekbarchangelistener() { @override public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { //这里是判断进度条移动是不是用户所为 if(fromuser){ mybinder.seektoposition(seekbar.getprogress()); } } @override public void onstarttrackingtouch(seekbar seekbar) { } @override public void onstoptrackingtouch(seekbar seekbar) { } }); handler.post(runnable); log.d(tag, "service与activity已连接"); } @override public void onservicedisconnected(componentname name) { } };
b.还需要一个handler来控制ui组件的变化,实例化放在了oncreate方法里面。
c.用一个runnable对象进行seekbar的前进
private runnable runnable=new runnable() { @override public void run() { seekbar.setprogress(mybinder.getplayposition()); tv_lefttime.settext(time.format(mybinder.getplayposition()) ""); tv_righttime.settext(time.format(mybinder.getprogress()-mybinder.getplayposition()) ""); if(mybinder.getprogress()-mybinder.getplayposition()<1000){//时间不够了自动触发下一首 runonuithread(new runnable() {//使用ui线程来触发按键点击事件,不知道这样有没有什么危害 @override public void run() { ib_next.performclick(); } }); } handler.postdelayed(runnable,1000); } };
d.在oncreate方法里进行绑定
mediaserviceintent =new intent(this,musicservice.class);//mediaserviceintent为一个intent bindservice(mediaserviceintent,connection,bind_auto_create);
5.使用notification通知栏通知
注意::如果点击通知栏是从mainactivity跳转到mainactivity,需要在配置文件的activity android:name=".mainactivity"
android:launchmode=“singletask”,设置为单任务。
布局在源代码包里,在api26级以上需要使用notificationchannel
①设置通知所触发的pandingintent,通过action识别,action为自己定义的常量,setsound无声音。通过remoteviews去实现通知栏组件的按钮实现
//设置通知 private void setnotification(){ string channelid="cary"; if(build.version.sdk_int>=build.version_codes.o){ notificationchannel channel=new notificationchannel(channelid,"xxx",notificationmanager.importance_low); manager.createnotificationchannel(channel); } intent intent=new intent(mainactivity.this,mainactivity.class); pendingintent pi=pendingintent.getactivity(mainactivity.this,0,intent,0); if (build.version.sdk_int >= build.version_codes.o) { notify=new notification.builder(mainactivity.this,channelid) .setwhen(system.currenttimemillis()) .setsound(null) .build(); } notify.icon=android.r.drawable.btn_star; notify.contentintent=pi; notify.contentview=remoteviews; notify.flags=notification.flag_ongoing_event; remoteviews.setonclickpendingintent(r.id.notice,pi); //上一首 intent previntent=new intent(button_prev_id); pendingintent prevpendingintent=pendingintent.getbroadcast(this,0,previntent,0); remoteviews.setonclickpendingintent(r.id.widget_prev,prevpendingintent); //播放暂停 intent playintent=new intent(button_play_id); pendingintent playpendingintent=pendingintent.getbroadcast(this,0,playintent,0); remoteviews.setonclickpendingintent(r.id.widget_play,playpendingintent); //下一首 intent nextintent=new intent(button_next_id); pendingintent nextpendingintent=pendingintent.getbroadcast(this,0,nextintent,0); remoteviews.setonclickpendingintent(r.id.widget_next,nextpendingintent); //关闭 intent closeintent=new intent(button_close_id); pendingintent closependingintent=pendingintent.getbroadcast(this,0,closeintent,0); remoteviews.setonclickpendingintent(r.id.widget_close,closependingintent); }
②动态注册广播
//注册广播 private void initbuttonreceiver(){ buttonbroadcastreceiver=new buttonbroadcastreceiver(); intentfilter intentfilter=new intentfilter(); intentfilter.addaction(button_prev_id); intentfilter.addaction(button_play_id); intentfilter.addaction(button_next_id); intentfilter.addaction(button_close_id); registerreceiver(buttonbroadcastreceiver,intentfilter); }
③显示广播,需要注意的是,每次在activity里面点击上一首或者下一首都需要调用这个方法,刷新通知栏的标题,以及状态专辑
//展示通知 private void shownotification(){ if(isplaying){ remoteviews.setimageviewresource(r.id.widget_play,r.drawable.stop); }else{ remoteviews.setimageviewresource(r.id.widget_play,r.drawable.start); } remoteviews.setimageviewbitmap(r.id.widget_album,utils.getartwork(mainactivity.this,list.get(music_index).getid(),list.get(music_index).getalbum(),true,false)); remoteviews.setimageviewresource(r.id.widget_close,android.r.drawable.ic_menu_close_clear_cancel); remoteviews.settextviewtext(r.id.widget_title,list.get(music_index).gettitle()); remoteviews.settextviewtext(r.id.widget_artist,list.get(music_index).getartist()); remoteviews.settextcolor(r.id.widget_title,color.black); remoteviews.settextcolor(r.id.widget_artist,color.black); notify.contentview=remoteviews; manager.notify(100,notify); }
④通知栏动作接收,使用的是内部类
public class buttonbroadcastreceiver extends broadcastreceiver{ @override public void onreceive(context context, intent intent) { string action=intent.getaction(); log.d(tag,"--------------------收到action:" action "--------------------------"); if(action.equals(button_prev_id)){ runonuithread(new runnable() { @override public void run() { ib_precious.performclick(); return; } }); } if(action.equals(button_play_id)){ runonuithread(new runnable() { @override public void run() { ib_state.performclick(); return; } }); } if(action.equals(button_next_id)){ runonuithread(new runnable() { @override public void run() { ib_next.performclick(); return; } }); } if(action.equals(button_close_id)){ handler.removecallbacks(runnable); mybinder.closemusic(); unbindservice(connection); if(remoteviews!=null){ manager.cancel(100); } unregisterreceiver(buttonbroadcastreceiver); finish(); } } }
6.全屏显示
①在androidmanifest文件里面配置主题样式android:theme="@style/theme.appcompat.light.noactionbar">
然后在oncreate方法里在setcontentview(r.layout.activity_main);之前
设置:
if(build.version.sdk_int>=21){ view decorview=getwindow().getdecorview(); decorview.setsystemuivisibility(view.system_ui_flag_layout_fullscreen|view.system_ui_flag_layout_stable); getwindow().setstatusbarcolor(color.transparent); }
7.设置歌曲选中后的样式
①在res目录下的drawable资源下新建一个类型为selector的xml文件,里面设置属性
②在adapter里面设置getview
currentitem == position){ holder.tv_title.setselected(true); holder.tv_position.setselected(true); holder.tv_duration.setselected(true); holder.tv_artist.setselected(true); }else{ holder.tv_title.setselected(false); holder.tv_position.setselected(false); holder.tv_duration.setselected(false); holder.tv_artist.setselected(false); }
注意:在使用的时候可能需要手动去设置里面打开权限
关于android10.0如何实现本地音乐播放?就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。