音频音乐播放 Service
界面效果:
界面就一个播放的Button和一个进度条SeekBar,也可以自己加上两个显示时间的TextView;
点击播放时,有音乐声音,进度条也会自动更新,Button文字变成暂停;
当音乐播放完毕时,Button文字变成播放,进度条重新回原始点;
1、添加权限
这里需要可以读取音频文件的权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
2、添加依赖
本项目中使用EventBus在Service和Activity之间通讯,需要加入EventBus的库:
compile 'org.greenrobot:eventbus:3.0.0'
3、布局文件
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MusicActivity"> <SeekBar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.5" /> <Button android:id="@+id/button_play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.6" /> </android.support.constraint.ConstraintLayout>
4、Service
创建Service:new->Service
在Service中,创建一个MediaPlayer对象和一个Binder类,Binder可以用来与Activity之间传输数据(进度条,点击事件),但是只能Activity主动通过Binder进行相互;播放结束后,音乐跳回最开始,并用EventBus主动通知Activity“播放完成”;
代码以及注释如下:
package com.example.m_evolution.Service; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.Binder; import android.os.Environment; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import org.greenrobot.eventbus.EventBus; import java.io.IOException; public class MusicService extends Service { private MediaPlayer mediaPlayer = new MediaPlayer(); private String file_dir; private final int CODE_PLAY_MUSIC = 101; //播放音乐 private final int CODE_PAUSE_MUSIC = 102; //暂停音乐 private final int CODE_UPDATE_SEEKBAR = 103; //更新进度条 private final int CODE_CHANGE_SEEKBAR = 104; //拖动进度条更新音乐 public MusicService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. file_dir = intent.getExtras().getString("file_dir", Environment.getExternalStorageDirectory()+"/record/"+"a.mp3"); try { mediaPlayer.setDataSource(file_dir); mediaPlayer.prepare(); mediaPlayer.setLooping(false); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { try { mediaPlayer.stop(); mediaPlayer.prepare(); mediaPlayer.seekTo(0); //用EventBus通知Activity已完成播放 EventBus.getDefault().post("finish_music"); } catch (IOException e) { e.printStackTrace(); } } }); } catch (IOException e) { Log.d("MusicService", "未找到文件"); e.printStackTrace(); } return new MyBinder(); } //Service与Activity交互数据 public class MyBinder extends Binder{ @Override //code为操作代码 //data为从Activity中传到Service的数据 //reply为从Service传到Activity的数据 protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { switch (code) { case CODE_PLAY_MUSIC: //播放 StartMusic(); break; case CODE_PAUSE_MUSIC: //暂停 PauseMusic(); break; case CODE_UPDATE_SEEKBAR: //更新Activity中的进度条 int out_data[]=new int[2]; //传出的数据 out_data[0] = 0; out_data[1] = 0; if(mediaPlayer != null){ out_data[0] = mediaPlayer.getCurrentPosition(); //获取现在播放时间 out_data[1] = mediaPlayer.getDuration(); //获取总时长 } reply.writeIntArray(out_data); break; case CODE_CHANGE_SEEKBAR: int in_data = data.readInt(); //传入的数据:播放时间 if(mediaPlayer != null){ mediaPlayer.seekTo(in_data); } break; } return super.onTransact(code, data, reply, flags); } } public void StartMusic(){ if (mediaPlayer!=null && !mediaPlayer.isPlaying()){ mediaPlayer.start(); } } public void PauseMusic(){ if (mediaPlayer!=null && mediaPlayer.isPlaying()){ mediaPlayer.pause(); } } public void StopMusic(){ if (mediaPlayer!=null){ mediaPlayer.stop(); mediaPlayer = null; } } @Override public void onDestroy() { super.onDestroy(); StopMusic(); } }
5、Activity中的代码及注释如下。
启动Service的方式有两种:
1、startService(intent);
2、bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
第一种是纯粹开启一个Service,跟Activity没有联系,就算Activity退出了,Service也还会运行,但是只能同一个Service只能开启一个,即多个Activity开启某个Service,该Service只会Create一次。=
第二种方式中,Activity会与Service建立联系,Activity销毁时,Service也会销毁,执行onDestroy函数,但这不代表音乐就停止了,你需要在onDestroy函数中手动停止音乐。
PS:这里面的IBinder对象,在开启Service后不是马上就有值的,所以不要在StartService之后立马对IBinder进行操作,因为它是null的;可以像我下面这样,建一个线程做个死循环,当IBinder对象不为空时再去进行操作。
package com.example.m_evolution; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Parcel; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.SeekBar; import com.example.m_evolution.Service.MusicService; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; public class MusicActivity extends AppCompatActivity { private SeekBar seekBar; private Button button_play; private String file_dir; //音频文件路径 private ServiceConnection serviceConnection; //用来连接Service private IBinder mBinder; //用来与Service交互数据 private boolean isPlaying; //是否正在进行播放 private final int CODE_PLAY_MUSIC = 101; //播放音乐 private final int CODE_PAUSE_MUSIC = 102; //暂停音乐 private final int CODE_UPDATE_SEEKBAR = 103; //更新进度条 private final int CODE_CHANGE_SEEKBAR = 104; //拖动进度条更新音乐 private final int INIT_VIEW = 0; private final int UPDATE_SEEKBAR = 1; //更新进度条的线程 private Thread thread_update_view; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_music); //注册EventBus,以便Service通知Activity意播放完毕 EventBus.getDefault().register(this); FindView(); InitData(); StartService(); //开启Service //初始化进度条以及设置监听器 new Thread(new Runnable() { @Override public void run() { while(mBinder == null) ; //等待mBinder被赋值,表示Service成功开始 Message msg = new Message(); msg.what = 0; mHandler.sendMessage(msg); } }).start(); } private void FindView(){ seekBar = findViewById(R.id.seekbar); button_play = findViewById(R.id.button_play); } private void InitData(){ file_dir = Environment.getExternalStorageDirectory()+"/record/"+"a.mp3"; //这是提前放在手机路径“record”文件夹中的音频文件a.mp3 isPlaying = false; } //开启Service private void StartService(){ serviceConnection = new ServiceConnection() { //与Service建立连接 @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mBinder = iBinder; //绑定Binder } @Override public void onServiceDisconnected(ComponentName componentName) { serviceConnection = null; } }; Intent intent = new Intent(this, MusicService.class); Bundle bundle = new Bundle(); bundle.putString("file_dir", file_dir); intent.putExtras(bundle); //startService(intent); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } private void SetListener(){ button_play.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(!isPlaying){ //如果现在是暂停状态 StartMusic(); } else{ //如果现在是播放状态 PauseMusic(); } isPlaying = !isPlaying; } }); //进度条的事件监听器,手指抬起时根据进度条改变音乐播放时间 seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { int code = CODE_CHANGE_SEEKBAR; Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInt(seekBar.getProgress()); //写入进度条现在的数据 try { mBinder.transact(code, data, reply, 0); } catch (RemoteException e) { e.printStackTrace(); } } }); } private void StartMusic(){ button_play.setText("暂停"); int code = CODE_PLAY_MUSIC; Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { mBinder.transact(code,data, reply, 0); //告诉Service进行播放 } catch (RemoteException e) { e.printStackTrace(); } thread_update_view = new Thread(new Runnable() { @Override public void run() { while (isPlaying && mBinder!=null){ Message msg = new Message(); msg.what = UPDATE_SEEKBAR; mHandler.sendMessage(msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread_update_view.start(); } private void PauseMusic(){ button_play.setText("播放"); int code = CODE_PAUSE_MUSIC; Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { mBinder.transact(code,data, reply, 0); //告诉Service进行暂停 } catch (RemoteException e) { e.printStackTrace(); } } //更新Activity中的进度条或TextView等 private void UpdateView(){ try { int code = CODE_UPDATE_SEEKBAR; Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); mBinder.transact(code, data, reply, 0); int read_data[] = new int[2]; //读取到的音乐播放时间以及总长度 reply.readIntArray(read_data); seekBar.setProgress(read_data[0]); //音乐正在播放的时间 seekBar.setMax(read_data[1]); //音乐总长度 //这里还可以设置显示时间的TextView //...... } catch (RemoteException e) { e.printStackTrace(); } } private Handler mHandler = new Handler(){ public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { //判断标志位 case INIT_VIEW: //初始化进度条以及监听器 UpdateView(); SetListener(); break; case UPDATE_SEEKBAR: UpdateView(); break; } } }; @Override protected void onDestroy() { if(serviceConnection != null){
unbindService(serviceConnection); serviceConnection = null;
} if(EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().unregister(this); } super.onDestroy(); } //已完成播放 @Subscribe(threadMode = ThreadMode.MAIN) public void Event(String str) { if(str.equals("finish_music")){ if(thread_update_view!=null){ thread_update_view.interrupt(); thread_update_view = null; } button_play.setText("播放"); seekBar.setProgress(0); isPlaying = false; } } }
6、音频文件
自备一个音频文件,这里我放在了Environment.getExternalStorageDirectory()+"/record/"+"a.mp3"。