音乐播放器Service和Activity交互实践

源码地址https://github.com/helloworld107/ShangGuiGu321Meida.git
效果图
音乐播放器Service和Activity交互实践_第1张图片
音乐播放器使用的是系统原生MediaPlayer,使用流程跟视频播放的videoview几乎一致,麻烦的地方在于服务和活动的交互,一来音乐播放作为后台可以的听的自然要写到服务里,这样就涉及到了活动和服务的通信问题,多了一个传输通道显然要麻烦很多。其次歌词的解析很,音律的解析都是自定义控件和算法,当然看完代码及详细的笔记,相信再难都能掌握喔
布局文件比较简单,总得垂直线性布局,然后上面一个相对布局包括自定义音律条和歌手歌曲名,中间放一个自定义控件歌词,先不不要惊讶,这么大个布局是t自定义extview喔,最下面就是进度条,按钮都没什么难度,详情看源码就明白了,先看看核心的代码吧


看懂源码先说几个知识
Activity和服务交互的方式很多,比如下面的情况
广播,Intent,Handler,接口回调,Application,EventBus,AIDL

这里使用了aidl,广播,EventBus.

Aidl使用方法
①在服务中写好服务和活动可能会调用的方法,注册清单文件
②光标指定到app右键新建aidl文件,aidl文件其实就是一个接口,在该文件中把服务的方法复制进来,因为都是抽象方法,不需要实现,大括号去掉,如图

interface IMusicPlayerService {

voidopenAudio(intposition);
voidstart();
voidpause() ;
voidstop() ;
省略。。。。。

③在服务里创建该接口,注意创建的方式,要加一个mstub,注意是在服务里创建,是个内部接口,然后实现接口方法,这个时候引用自己服务再调用服务的方法,看起来同一个方法写了竟然两次,没办法,通过aidl就是这么麻烦,不过活动用起来很方便

private IMusicPlayerService.Stub mStub=new IMusicPlayerService.Stub() {
MusicPlayerService mPlayerService= MusicPlayerService.this;

@Override
public void openAudio(intposition) throws RemoteException {
mPlayerService.openAudio(position);
}
省略。。。。。。
}

最关键的是重写onbinder方法返回喔
@Nullable
@Override
public IBinder onBind(Intent intent) {
//一定要返回自己binder,只有返回binder活动才可以调用服务的方法
returnmStub;
}

最后就可以在活动中调用了,具体调用看活动代码


3eventbus用法 原理是反射机制,模式像是观察者
1.注册 需要接收信息的类注册
EventBus.getDefault().register(this);
2.自定义对应的类,其实就是传递的参数,实体类
  
   public class FirstEvent {

    private String mMsg;
    public FirstEvent(String msg) {
        mMsg = msg;
    }
    public String getMsg(){
        return mMsg;
    }
  }


4订阅对应的方法
对于3.0以前的版本就是说接收后执行的方法只能在下面几个方法中执行,名字也不能随便改,就用原生的,区别在线程位置不同具体看下面的说明,好在3.0后可以随便自定义方法了,更加灵活,这将要可以废弃了
    public void onEventMainThread(FirstEvent event) {
    }
    public void onEventMainThread(SecondEvent event) {
    }
    public void onEventBackgroundThread(SecondEvent event){
    }
    public void onEventAsync(SecondEvent event){
    }
    public void onEvent(ThirdEvent event) {
    }
5.订阅取消 在注册类的销毁相关方法取消
 EventBus.getDefault().unregister(this);
6.发消息
对于此项目,就是服务执行此方法,活动注册取消,实现处理方法
    EventBus.getDefault().post(实体类);
onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。

onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么-onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.
新版本特效
EventBus3.0
1.方法名,不需要onEven开头,自己随便修改,只需要用@注解一下,真是太爽了
方法的线程模式ThreadMode 可以配置, 两个方法通用一个参数,可以设置优先级priority,值越大,优先收到

    @Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
    public void showData(MediaItem mediaItem) {
        showViewData();
        checkPlaymode();
    }

传统的传送消息还有广播接收者,非常简单我就不说了,相比而言eventbus代码量简洁了很多呐

public class MusicPlayerService extends Service {

private intmPosition;
privateListmMediaItems=newArrayList();
privateMediaPlayermMediaPlayer;
privateMediaItemmMediaItem;
//四种播放模式
public static final int REPEAT_NORMAL=1;
public static final intREPEAT_SINGLE=2;
public static final intREPEAT_ALL=3;
private intmPLaymode=REPEAT_NORMAL;
private intmDuration;
privateNotificationManagermNotifiManger;

@Override
public voidonCreate() {
super.onCreate();
//拿取本地数据
getDataFromLocal();
//做了一个sharedpreference的缓存,用来保存歌曲的播放模式
mPLaymode= CacheUtils.getInt(this,"musicmode");
}

@Nullable
@Override
publicIBinder onBind(Intent intent) {
//一定要返回自己binder,只有返回binder活动才可以调用服务的方法
returnmStub;
}

private voidgetDataFromLocal() {
//服务默认是在主线程中进行,所以耗时操作应该放到子线程里去
newThread(newRunnable() {
@Override
public voidrun() {

ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;视频和音频就差一个字母
String[] objs = {
MediaStore.Audio.Media.DISPLAY_NAME,//视频文件在sdcard的名称
MediaStore.Audio.Media.DURATION,//视频总时长
MediaStore.Audio.Media.SIZE,//视频的文件大小
MediaStore.Audio.Media.DATA,//视频的绝对地址
MediaStore.Audio.Media.ARTIST,//歌曲的演唱者

};
Cursor cursor = contentResolver.query(uri, objs,null,null,null);
if(cursor != null) {
while(cursor.moveToNext()) {
MediaItem mediaItem =newMediaItem();
String name = cursor.getString(0);//视频的名称
mediaItem.setName(name);

longduration = cursor.getLong(1);//视频的时长
mediaItem.setDuration(duration);

longsize = cursor.getLong(2);//视频的文件大小
mediaItem.setSize(size);

String data = cursor.getString(3);//视频的播放地址
mediaItem.setData(data);

String artist = cursor.getString(4);//艺术家
mediaItem.setArtist(artist);

mMediaItems.add(mediaItem);//写在上面
}
cursor.close();
}
}
}).start();

}


/**
*根据位置打开对应的音频文件,并且播放,播放的入口
*/
private voidopenAudio(intposition) {
mPosition= position;
//打开文件之后就要开始播放了
if(mMediaItems!=null&&mMediaItems.size() >0) {
mMediaItem=mMediaItems.get(position);
//启动系统的播放器吧
if(mMediaPlayer!=null) {
mMediaPlayer.release();//两个方法重复,一起调用会出现bug,坑
mMediaPlayer.reset();
mMediaPlayer=null;
}

mMediaPlayer=newMediaPlayer();
//设置各种监听
mMediaPlayer.setOnPreparedListener(newMyOnPreparedListener());
mMediaPlayer.setOnCompletionListener(newMyOnCompletionListener());
mMediaPlayer.setOnErrorListener(newMyOnErrorListener());
//开始设置资源播放
try{
mMediaPlayer.setDataSource(mMediaItem.getData());播放地址
//准备
mMediaPlayer.prepareAsync();
}catch(IOException e) {
e.printStackTrace();
}

}else{
Toast.makeText(this,"no data baby", Toast.LENGTH_SHORT).show();
}
}

/**
*播放音乐
*/
private voidstart() {
mMediaPlayer.start();
//播放的时候就可以显示通知栏了
//看来只有这个版本以上才支持新的通知方法,以下的版本没有去写
if(android.os.Build.VERSION.SDK_INT>= android.os.Build.VERSION_CODES.JELLY_BEAN) {

mNotifiManger= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//这个意图表示点击后调回音乐播放
Intent intent =newIntent(this, AudioPlayerActivity.class);
//解决bug冲突
intent.putExtra("notification",true);//标识来自状态拦
PendingIntent pending = PendingIntent.getActivity(this,1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//pendingintent表示携带数据的意图 可以是活动,服务等等
Notification notification =newNotification.Builder(this)
.setSmallIcon(R.drawable.notification_music_playing)
.setContentTitle("华科音乐")
.setContentText("正在播放"+ getName())
.setContentIntent(pending)
.build();

mNotifiManger.notify(1, notification);
}
}

/**
*播暂停音乐
*/
private voidpause() {
mMediaPlayer.pause();
mNotifiManger.cancel(1);取消通知,感觉放到stop更好
}

/**
*停止
*/
private voidstop() {
mMediaPlayer.stop();
}

/**
*得到当前的播放进度
*
*@return
*/
private intgetCurrentPosition() {

returnmMediaPlayer.getCurrentPosition();
}


/**
*得到当前音频的总时长
*
*@RETURN
*/
private intgetDuration() {
if(mMediaPlayer!=null) {
returnmMediaPlayer.getDuration();
}else{
return0;
}
// Toast.makeText(this, "为啥为空", Toast.LENGTH_SHORT).show();
}

/**
*得到歌曲名字
*
*@return
*/
privateString getName() {
returnmMediaItem.getName();
}

/**
*控制进度条
*
*@return
*/
private voidseekTo(intposition) {
mMediaPlayer.seekTo(position);

}

privateString getArtist() {
returnmMediaItem.getArtist();
}

/**
*得到歌曲播放的路径
*
*@return
*/
privateString getAudioPath() {
returnmMediaItem.getData();
}

/**
*播放下一个视频
*/
private voidnext() {
mPosition++;
if(mPosition>mMediaItems.size() -1) {
mPosition=0;
}
openAudio(mPosition);
}


/**
*播放上一个视频
*/
private voidpre() {
mPosition--;
if(mPosition<0) {
mPosition=mMediaItems.size() -1;
}
openAudio(mPosition);
}

/**
*设置播放模式
*
*@paramplaymode
*/
private voidsetPlayMode(intplaymode) {
mPLaymode= playmode;
CacheUtils.putInt(this,"musicmode",mPLaymode);
}

/**
*得到播放模式
*
*@return
*/
private intgetPlayMode() {
returnmPLaymode;
}


/**
*是否在播放音频
*
*@return
*/
private booleanisPlaying() {
returnmMediaPlayer.isPlaying();
}

classMyOnErrorListenerimplementsMediaPlayer.OnErrorListener {

@Override
public booleanonError(MediaPlayer mp,intwhat,intextra) {
System.out.println("erro");
return true;
}

}

classMyOnCompletionListenerimplementsMediaPlayer.OnCompletionListener {

@Override
public voidonCompletion(MediaPlayer mp) {
//根据不同的播放模式,决定不同的情况,无非就是判断一个播放歌曲序列而已

switch(mPLaymode) {
caseREPEAT_NORMAL:
mPosition++;
if(mPosition>mMediaItems.size() -1) {
mPosition=mMediaItems.size() -1;

}else{
openAudio(mPosition);
}
break;
caseREPEAT_SINGLE:
openAudio(mPosition);
break;
caseREPEAT_ALL:
mPosition++;
if(mPosition>mMediaItems.size() -1) {
mPosition=0;
}
openAudio(mPosition);
break;
}
}
}

classMyOnPreparedListenerimplementsMediaPlayer.OnPreparedListener {


@Override
public voidonPrepared(MediaPlayer mp) {
//获取时长在这里最好,如果在其他地方随意获取,很可能播放器并没有创建或者不在准备状态,坑
//1.广播接收者
// notiChange();
//2.还可以用新框架eventbus,好坑爹
EventBus.getDefault().post(mMediaItem);
start();
}
}
//通知广播用的,用evenyBus代替咯
private voidnotiChange() {
Intent intent =newIntent();
intent.setAction("audioplayer_music");
sendBroadcast(intent);
}



privateIMusicPlayerService.StubmStub=newIMusicPlayerService.Stub() {
MusicPlayerServicemPlayerService= MusicPlayerService.this;

@Override
public voidopenAudio(intposition)throwsRemoteException {
mPlayerService.openAudio(position);
}

@Override
public voidstart()throwsRemoteException {
mPlayerService.start();
}

@Override
public voidpause()throwsRemoteException {
mPlayerService.pause();
}

@Override
public voidstop()throwsRemoteException {
mPlayerService.stop();
}

@Override
public intgetCurrentPosition()throwsRemoteException {
returnmPlayerService.getCurrentPosition();
}

@Override
public intgetDuration()throwsRemoteException {
returnmPlayerService.getDuration();
}

@Override
publicString getArtist()throwsRemoteException {
returnmPlayerService.getArtist();
}

@Override
publicString getName()throwsRemoteException {
returnmPlayerService.getName();
}

@Override
publicString getAudioPath()throwsRemoteException {
returnmPlayerService.getAudioPath();
}

@Override
public voidnext()throwsRemoteException {
mPlayerService.next();
}

@Override
public voidpre()throwsRemoteException {
mPlayerService.pre();
}

@Override
public voidsetPlayMode(intplaymode)throwsRemoteException {
mPlayerService.setPlayMode(playmode);
}

@Override
public intgetPlayMode()throwsRemoteException {
returnmPlayerService.getPlayMode();
}

@Override
public booleanisPlaying()throwsRemoteException {
returnmPlayerService.isPlaying();
}

@Override
public voidseekTo(intposition)throwsRemoteException {
mPlayerService.seekTo(position);
}

@Override
public intgetAudioSessionId()throwsRemoteException {
returnmMediaPlayer.getAudioSessionId();
}
};

}

你可能感兴趣的:(音乐播放器Service和Activity交互实践)