这次在 Android 上实现一个可以选歌以及后台播放的音乐播放器,主要涉及到的技术有 Android 中的 Service 以及RxJava
。
实现内容
实现一个简单的播放器,要求功能有:
- 播放、暂停、停止、退出功能,按停止键会重置封面转角,进度条和播放按钮;按退出键将停止播放并退出程序
- 后台播放功能,按手机的返回键和 home 键都不会停止播放,而是转入后台进行播放
- 进度条显示播放进度、拖动进度条改变进度功能
- 播放时图片旋转,显示当前播放时间功能,圆形图片的实现使用的是一个开源控件 CircleImageView
附加内容(加分项,加分项每项占 10 分)
1.选歌
用户可以点击选歌按钮自己选择歌曲进行播放,要求换歌后不仅能正常实现上述的全部功能,还要求选歌成功后不自动播放,重置播放按钮,重置进度条,重置歌曲封面转动角度,最重要的一点:需要解析 mp3 文件,并更新封面图片。
应用截图
应用主界面 | 开始播放 |
---|---|
暂停播放 | 暂停播放 |
![Screenshot_20181125-004302_Sound picker](Android_Multimedia/Screenshot_20181125-004302_Sound picker.jpg) 选歌 | 选歌成功 |
界面
界面主要是采用约束布局,使用约束布局,很容易就可以设计出一个可以适应各种屏幕大小的界面。
选择音乐
和之前的选择图片差不多,不过这次的选择音乐需要文件读取权限。
使用ACTION_PICK
的 Intent,并且设置好需要的内容类型,系统就会弹出指定类型媒体的选择器。
1 | public void onClickSelect(View v) { |
选择完之后,我们得到的是一个内容访问器的 URI
1 | void onAudioResult(Intent data) { |
然后就需要处理这个 URI,读取出相关信息(文件路径、标题、歌手)等等
1 | public MusicInfo openMusicUri(Uri uri) { |
这里是通过ContentResolver
使用 URI 读取,还读取出他的专辑 ID,有了这个专辑 ID,就可以再次使用ContentResolver
来查询专辑图片的路径,URI 为MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI
,然后加上 ID 的过滤条件,就可以得到专辑图片路径。
1 | Cursor albumCursor = getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, |
获取完数据之后,就可以通过路径把数据设置到MediaPlayer
里面
1 | try { |
Service
既然需要后台播放,自然就需要用到 Service
首先写一个MusicService
,使用IBinder
在服务与Activity
之间的通讯。
1 | public class MusicService extends Service{ |
然后在Activity
的OnCreate
的时候,使用ServiceConnection
与Service
连接起来
1 | // 开启服务 |
这样,我们就得到了一个IBinder
对象,通过transact
方法可以调用服务中的各种事件
首先定义好各种事件的参数
1 | final static int SET_MUSIC_PATH = 1; |
然后再Binder
的onTransact
函数里面响应各种事件的操作:
1 | public class MusicBinder extends Binder { |
然后调用IBinder
的transact
方法就可以控制服务,data
传输参数、reply
获取结果。
1 | public void setMusicUri(String uri) { |
然后把这些方法封装在MusicServiceConnection
里面,在Activity
里面只需要调用
1 | MusicSC.setMusicUri(uri); |
就可以设置音乐的 URI
Handler
对于进度条的更新,我们需要新开一个进程进行处理,这里就使用到了Handler
首先定义一个Runnable
,循环调用自身,使其每过一定时间执行一次。
1 | runnable = new Runnable() { |
然后使用Handler
来管理和调用它
1 | handler.removeCallbacks(runnable); // 停止执行 |
图片旋转
这个可以使用ObjectAnimator
实现
首先定义一个旋转动画,并且绑定到指定的控件上
1 | animation = ObjectAnimator.ofFloat(binding.musicImage, "rotation", 0f, 360f); |
然后就可以随心所欲地控制它了鸭
1 | animation.start(); // 开始动画 |
RXJava
这个项目需要使用 RxJava,因此在更新进度条的时候就使用了 RxJava
首先新建一个Observable
对象,在subscrible
里面循环获取歌曲播放进度交给观察者的onNext
处理,如果播放已经结束,就调用onComplete
1 | //RxJava |
然后建立一个DisposableObserver
,观察歌曲播放进去并更新 UI,然后使用subscrible
在mainThread
里面订阅可观察者。
1 | // 更新进度 |
退出的时候需要主动解除订阅状态
1 | disposableObserver.dispose(); |
遇到的困难以及解决思路
这次实验第一次使用到Service
,虽然一开始不知道怎么使用,但是通过一番查阅资料后还是大概知道了服务的使用方法。
一开始遇到的困难主要是播放器无法播放所选择的音乐,MediaPlayer
一直都报错说无法解析文件。
解决方法:最后发现原来是setDataSource
之前忘记了MediaPlayer.reset()
,它还保留着之前的数据,导致新的数据无法加载。
后来也遇到了一个问题,加载资源的时候一直报错MediaPlayer Error(-38, 0)
,通过查阅资料发现这个错误是因为播放器还没有加载完毕资源之前对资源进行了操作,导致播放器崩溃。
解决方法:我使用了onPrepare
的回调来做加载之后的操作,就没有问题了。
总结
这次实验第一次使用到了 Service,Service 作为 Android 应用的四大部件之一,其重要性是不言而喻的。有了 Service,我们就可以使应用在后台处理一些工作,比如接受来自服务器的信息(如 QQ,微信),或者是播放音乐。但是,如果不正确地使用 Service,就会成为流氓软件,不仅占用计算资源而且还耗电。因此对于 Service 的使用必须要遵循 Android 开发的规范。