经过前两节我们已经把音乐播放器2.0改造成了使用 Service 播放音乐,当我们回到桌面也是可以继续播放的。

接下来我们可以再添加一个退出时的对话框提醒。

首先声明 AlertDialog

private AlertDialog alertDialog;

在onCreate中创建,其中传入 ​​this​​​ 需要让 Activity 实现 ​​DialogInterface.OnClickListener​​,后边会写。

alertDialog = new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("你确定要退出程序吗")
.setPositiveButton("确定", this)
.setNegativeButton("取消", this)
.setNeutralButton("后台播放", this)
.create();

按下返回键的时候显示

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
alertDialog.show();
//返回
return true;
}
return super.onKeyDown(keyCode, event);
}

给确定和取消按钮增加点击事件,让 Activity 实现 ​​DialogInterface.OnClickListener​​​,并实现这个接口的方法。注意区分​​View.OnClickListener​​​、​​DialogInterface.OnClickListener​​,

/**
* @param dialog 点击的哪一个Dialog
* @param which 点击的哪一个按钮
*/
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
finish();
break;
case DialogInterface.BUTTON_NEGATIVE:
alertDialog.dismiss();
break;
case DialogInterface.BUTTON_NEUTRAL:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
break;
}
}

运行程序,分别点击 取消、后台播放和确定按钮
【达内课程】音乐播放器2.0(下)_ide
最后附上完整的 MainActivity 和 PlayMusicService 代码

MainActivity.java

public class MainActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
private List<Music> musics;
private ListView listView;
private MusicAdapter adapter;

private TextView tvCurrentMusicTitle;
private ImageButton ibPlay;//播放/暂停按钮
private ImageButton ibPrevious;//上一首按钮
private ImageButton ibNext;//下一首按钮

private SeekBar seekBar;
private TextView tvCurrentTime;
private TextView tvDuration;
//是否正在拖拽进度条
private boolean isTrackingTouch;

private AlertDialog alertDialog;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//绑定Service
Intent intent = new Intent(this, PlayMusicService.class);
conn = new InnerServiceConnection();
bindService(intent, conn, BIND_AUTO_CREATE);

//获取数据
IDao<Music> dao = MusicDaoFactory.newInstance();
musics = dao.getData();
//初始化控件
initView();
//添加点击事件
initListener();
//创建退出时的对话框
createDialog();
}

private void initView() {
listView = findViewById(R.id.listview);
adapter = new MusicAdapter(this, musics);
listView.setAdapter(adapter);

tvCurrentMusicTitle = findViewById(R.id.tv_current_music_title);
ibPlay = findViewById(R.id.ib_play);
ibPrevious = findViewById(R.id.ib_previous);
ibNext = findViewById(R.id.ib_next);

seekBar = findViewById(R.id.sk_music);
tvCurrentTime = findViewById(R.id.tv_music_current_time);
tvDuration = findViewById(R.id.tv_music_duration);
}

private void initListener() {
InnerClassOnClickListener listener = new InnerClassOnClickListener();
ibPlay.setOnClickListener(listener);
ibPrevious.setOnClickListener(listener);
ibNext.setOnClickListener(listener);

//为listview添加点击事件
OnItemClickListener onItemClickListener = new OnItemClickListener();
listView.setOnItemClickListener(onItemClickListener);

//为Seekbar添加监听
OnSeekBarChangeListener onSeekBarChangeListener = new OnSeekBarChangeListener();
seekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
}

private void createDialog() {
alertDialog = new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("你确定要退出程序吗")
.setPositiveButton("确定", this)
.setNegativeButton("取消", this)
.setNeutralButton("后台播放", this)
.create();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
alertDialog.show();
//返回
return true;
}
return super.onKeyDown(keyCode, event);
}

/**
* @param dialog 点击的哪一个Dialog
* @param which 点击的哪一个按钮
*/
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
finish();
break;
case DialogInterface.BUTTON_NEGATIVE:
alertDialog.dismiss();
break;
case DialogInterface.BUTTON_NEUTRAL:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
break;
}
}

//按钮监听
private class InnerClassOnClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.ib_play:
if (player.isPlaying()) {
player.pause();
ibPlay.setImageResource(android.R.drawable.ic_media_play);
startThread();
} else {
player.play();
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
startThread();
}
break;
case R.id.ib_previous:
player.previous();
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
startThread();
break;
case R.id.ib_next:
player.next();
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
startThread();
break;
}
}
}

//ListView点击事件
private class OnItemClickListener implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
player.play(i);
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
startThread();
}
}

//seekbar监听
private class OnSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {

@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//当进度发生改变时【不适用】
// 第三个参数boolean fromUser,区分是程序代码改变了进度,还是人为改变了进度
//在人为拖拽时,进度在不停的改变,但是不需要持续相应
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//当开始拖拽进度条时
isTrackingTouch = true;
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (player.isStartWorking()) {
//当结束拖拽进度条时
player.seekTo(seekBar.getProgress());
//标记为停止拖拽
isTrackingTouch = false;
ibPlay.setImageResource(android.R.drawable.ic_media_pause);
startThread();
}else{
seekBar.setProgress(0);
}
}
}

@Override
protected void onDestroy() {
//解除与Service的绑定
unbindService(conn);
//停止更新进度的线程
stopThread();
super.onDestroy();
}

@Override
protected void onStop() {
//停止语句放在super之前
startThread();
//super语句不要删掉
super.onStop();
}

@Override
protected void onRestart() {
//super语句不要删掉,启动工作放在super之后
//启动的时候,让系统把自己的任务做完,然后再执行自己的代码
super.onRestart();
startThread();
}

/**
* 与Service实现绑定时建立的连接对象
* 声明成内部类或接口类型都可以
*/
private ServiceConnection conn;
/**
* 调用Service中实现的功能的对象
*/
private IMusicPlayer player;

private class InnerServiceConnection implements ServiceConnection {

@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
player = (IMusicPlayer) iBinder;
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

}
}

//更新进度的线程
//在Activity中开启线程
//【线程任务】
//1、长期的循环
//2、每一次循环休眠1s
//3、每次循环时,调用service的功能,获取相关数据
//--当前播放歌曲的索引
//--当前播放歌曲的时长
//--当前播放歌曲的时间
//4、每次循环时,在主线程中更新UI
private class UpdateProgressThread extends Thread {
private boolean isRunning;

//设置线程是否循环执行
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}

int currentMusicIndex;
int currentPosition;
int duration;

public void run() {
Runnable runnable = new Runnable() {
@Override
public void run() {
//更新控件
//更新歌曲标题
String musicTitle = musics.get(currentMusicIndex).getTitle();
tvCurrentMusicTitle.setText("正在播放:" + musicTitle);
//更新歌曲播放到的时间
tvCurrentTime.setText(CommonUtils.getFormattedTime(currentPosition));
//判断是否正在拖拽.当没有拖拽进度条时才更新
if (!isTrackingTouch) {
//更新进度条
int progress = currentPosition * 100 / duration;
seekBar.setProgress(progress);
}
//更新总时长
tvDuration.setText(CommonUtils.getFormattedTime(duration));
}
};

while (isRunning) {
//判断是否播放,解决暂停后再播放播放时间可能一瞬间变成0的bug
if (player.isPlaying()) {
//获取数据
currentMusicIndex = player.getCurrentMusicPosition();
currentPosition = player.getCurrentPosition();
duration = player.getDuration();

Log.d("MUSIC", "CurrentPosition " + currentPosition + ",Duration " + duration);
}
//更新UI
runOnUiThread(runnable);

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//更新进度的线程
private UpdateProgressThread updateProgressThread;

//开启线程
private void startThread() {
if (updateProgressThread == null) {
updateProgressThread = new UpdateProgressThread();
updateProgressThread.setRunning(true);
updateProgressThread.start();
}
}

//停止更新线程
private void stopThread() {
if (updateProgressThread != null) {
updateProgressThread.setRunning(false);
//设置null,线程并不会停
updateProgressThread = null;
}
}
}

PlayMusicService

public class PlayMusicService extends Service {
private List<Music> musics;//音乐列表
private int currentMusicIndex;//当前播放歌曲索引
private int pausePosition;//歌曲暂停时间
private boolean isPlayerWorking;//播放器是否在工作
private MediaPlayer mediaPlayer;
//播放工具是否已经开始工作,用于解决没有播放任何歌曲前就拖拽进度条的bug
private boolean isStartWork;

public PlayMusicService() {
//初始化播放工具
mediaPlayer = new MediaPlayer();
//获取歌曲列表
IDao<Music> dao = MusicDaoFactory.newInstance();
musics = dao.getData();
}

@Override
public IBinder onBind(Intent intent) {
InnerBinder binder = new InnerBinder();
return binder;
}

private class InnerBinder extends Binder implements IMusicPlayer {

@Override
public void play() {
PlayMusicService.this.play();
}

@Override
public void play(int position) {
PlayMusicService.this.play(position);
}

@Override
public void pause() {
PlayMusicService.this.pause();
}

@Override
public void previous() {
PlayMusicService.this.previous();
}

@Override
public void next() {
PlayMusicService.this.next();
}

@Override
public int getCurrentMusicPosition() {
return currentMusicIndex;
}

@Override
public int getCurrentPosition() {
return mediaPlayer.getCurrentPosition();
}

@Override
public int getDuration() {
return mediaPlayer.getDuration();
}

@Override
public boolean isPlaying() {
return isPlayerWorking;
}

@Override
public void seekTo(int percent) {
PlayMusicService.this.seekTo(percent);
}

@Override
public boolean isStartWorking() {
return isStartWork;
}
}

public void play() {
try {
mediaPlayer.reset();
mediaPlayer.setDataSource(musics.get(currentMusicIndex).getPath());
mediaPlayer.prepare();
mediaPlayer.seekTo(pausePosition);
mediaPlayer.start();
//标记正在播放
isPlayerWorking = true;
//标记为已经开始工作,可以对进度条进行拖拽
isStartWork = true;
} catch (IOException e) {
e.printStackTrace();
}
}

public void play(int position) {
currentMusicIndex = position;
pausePosition = 0;
play();
}

public void pause() {
pausePosition = mediaPlayer.getCurrentPosition();
mediaPlayer.pause();
isPlayerWorking = false;
}

public void previous() {
currentMusicIndex--;
if (currentMusicIndex < 0) {
currentMusicIndex = musics.size() - 1;
}
//清空暂停时间
pausePosition = 0;
play();
}

public void next() {
currentMusicIndex++;
if (currentMusicIndex > musics.size() - 1) {
currentMusicIndex = 0;
}
//清空暂停时间
pausePosition = 0;
play();
}

private void seekTo(int percent) {
if (isStartWork) {
pausePosition = mediaPlayer.getDuration() * percent / 100;
play();
}
}

@Override
public void onDestroy() {
//释放资源
mediaPlayer.release();
mediaPlayer = null;
super.onDestroy();
}
}

源码下载

​源码地址​