Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】
给定部分完成的MusicPlayer项目,实现其中未完成的service部分:
1、创建MusicService类,通过service组件实现后台播放音乐的功能;
2、在MainActivity中通过ServiceConnection连接MusicService,实现对音乐播放的控制;
3、使用Handler机制在MainActivity和MusicService之间进行通信。
目前已有代码:
相关的资源文件,可自行寻找
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/music_bg"android:gravity="center"android:orientation="vertical"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="160dp"tools:ignore="UselessParent"><RelativeLayoutandroid:id="@+id/rl_title"android:layout_width="300dp"android:layout_height="70dp"android:layout_centerHorizontal="true"android:background="@drawable/title_bg"android:gravity="center_horizontal"android:paddingStart="80dp"tools:ignore="RtlSymmetry"><TextViewandroid:id="@+id/tv_music_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:text="@string/song_name"android:textSize="12sp"android:textStyle="bold"android:textColor="@android:color/black"/><TextViewandroid:layout_marginTop="4dp"android:id="@+id/tv_type"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_music_title"android:layout_alignStart="@id/tv_music_title"android:text="@string/pop_music"android:textSize="10sp"tools:ignore="SmallSp" /><SeekBarandroid:id="@+id/sb"android:layout_width="150dp"android:layout_height="wrap_content"android:layout_below="@id/rl_time"android:layout_alignParentBottom="true"android:thumb="@null" /><RelativeLayoutandroid:layout_marginTop="4dp"android:id="@+id/rl_time"android:layout_width="150dp"android:layout_height="wrap_content"android:layout_below="@id/tv_type"><TextViewandroid:id="@+id/tv_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/zero_time"android:textSize="10sp"tools:ignore="SmallSp" /><TextViewandroid:id="@+id/tv_total"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:text="@string/zero_time"android:textSize="10sp"tools:ignore="RelativeOverlap,SmallSp" /></RelativeLayout></RelativeLayout><LinearLayoutandroid:layout_width="340dp"android:layout_height="90dp"android:layout_below="@id/rl_title"android:layout_centerHorizontal="true"android:background="@drawable/btn_bg"android:gravity="center_vertical"android:paddingStart="120dp"android:paddingEnd="10dp"><Buttonandroid:id="@+id/btn_play"android:layout_width="0dp"android:layout_height="25dp"android:layout_margin="4dp"android:layout_weight="1"android:background="@drawable/btn_bg_selector"android:text="@string/play"android:textSize="10sp"tools:ignore="ButtonStyle,SmallSp" /><Buttonandroid:id="@+id/btn_pause"android:layout_width="0dp"android:layout_height="25dp"android:layout_margin="4dp"android:layout_weight="1"android:background="@drawable/btn_bg_selector"android:text="@string/pause"android:textSize="10sp"tools:ignore="ButtonStyle,SmallSp" /><Buttonandroid:id="@+id/btn_continue_play"android:layout_width="0dp"android:layout_height="25dp"android:layout_margin="4dp"android:layout_weight="1"android:background="@drawable/btn_bg_selector"android:text="@string/cont"android:textSize="10sp"tools:ignore="ButtonStyle,SmallSp" /><Buttonandroid:id="@+id/btn_exit"android:layout_width="0dp"android:layout_height="25dp"android:layout_margin="4dp"android:layout_weight="1"android:background="@drawable/btn_bg_selector"android:text="@string/exit"android:textSize="10sp"tools:ignore="ButtonStyle,SmallSp" /></LinearLayout><ImageViewandroid:id="@+id/iv_music"android:layout_width="100dp"android:layout_height="100dp"android:layout_centerVertical="true"android:layout_marginStart="35dp"android:layout_marginBottom="50dp"android:src="@drawable/img_music"android:contentDescription="@string/iv" /></RelativeLayout>
</LinearLayout>
MainActivity.java
package cn.itcast.musicplayer;import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static SeekBar sb;private static TextView tv_progress, tv_total;private ObjectAnimator animator;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);init();}private void init() {tv_progress = findViewById(R.id.tv_progress);tv_total = findViewById(R.id.tv_total);sb = findViewById(R.id.sb);findViewById(R.id.btn_play).setOnClickListener(this);findViewById(R.id.btn_pause).setOnClickListener(this);findViewById(R.id.btn_continue_play).setOnClickListener(this);findViewById(R.id.btn_exit).setOnClickListener(this);//为滑动条添加事件监听sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, booleanfromUser) { //滑动条进度改变时,会调用此方法if (progress == seekBar.getMax()) { //当滑动条滑到末端时,结束动画animator.pause(); //停止播放动画}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//滑动条开始滑动时调用}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) { //滑动条停止滑动时调用//根据拖动的进度改变音乐播放进度int progress = seekBar.getProgress();//获取seekBar的进度}});ImageView iv_music = findViewById(R.id.iv_music);animator = ObjectAnimator.ofFloat(iv_music, "rotation", 0f, 360.0f);animator.setDuration(10000); //动画旋转一周的时间为10秒animator.setInterpolator(new LinearInterpolator());animator.setRepeatCount(-1); //-1表示设置动画无限循环}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_play: //播放按钮点击事件animator.start(); //播放动画break;case R.id.btn_pause: //暂停按钮点击事件animator.pause(); //暂停播放动画break;case R.id.btn_continue_play: //继续播放按钮点击事件animator.start(); //播放动画break;case R.id.btn_exit: //退出按钮点击事件finish(); //关闭音乐播放界面break;}}@Overrideprotected void onDestroy() {super.onDestroy();//解绑服务}
}
当前已经有一个用户界面,其中包括了播放、暂停、继续播放和退出按钮,以及一个旋转动画效果。现在,我们需要将MusicService与MainActivity连接起来,以实现音乐的播放和控制功能。
步骤1:创建MusicService类
单击鼠标右键并选择【New】–>【Service】–>【Service】
步骤 1: 创建 MusicService 类
首先,你需要创建一个名为 MusicService
的类,该类将负责处理音乐播放和与 MainActivity 之间的通信。
这里你需要准备一个mp3格式的文件
package cn.itcast.musicplayer;import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;public class MusicService extends Service {private MediaPlayer mediaPlayer;public MusicService() {}@Overridepublic IBinder onBind(Intent intent) {return new MusicBinder();}public class MusicBinder extends Binder {MusicService getService() {return MusicService.this;}}@Overridepublic void onCreate() {super.onCreate();mediaPlayer = new MediaPlayer();// 在这里设置音乐资源,例如 mediaPlayer.setDataSource(your_music_uri);mediaPlayer = MediaPlayer.create(this, R.raw.music); // 加载音乐资源}// 添加播放音乐的方法public void playMusic() {if (!mediaPlayer.isPlaying()) {mediaPlayer.start();}}// 添加暂停音乐的方法public void pauseMusic() {if (mediaPlayer.isPlaying()) {mediaPlayer.pause();}}// 添加继续播放音乐的方法public void continueMusic() {if (!mediaPlayer.isPlaying()) {mediaPlayer.start();}}@Overridepublic void onDestroy() {if (mediaPlayer != null) {mediaPlayer.release();mediaPlayer = null;}super.onDestroy();}
}
相关变量描述:
-
MusicService
是一个 Android 服务类,用于处理音乐播放相关的功能。 -
mediaPlayer
是用于播放音乐的 MediaPlayer 对象,它负责加载音乐资源、播放、暂停和继续播放音乐。 -
MusicBinder
内部类继承自 Binder,用于绑定服务与其他组件之间的通信。 -
onBind
方法用于返回MusicBinder
对象,以便其他组件可以与服务进行绑定。 -
onCreate
方法在服务创建时被调用,它初始化了mediaPlayer
并加载音乐资源。在这个示例中,音乐资源是从R.raw.music
中加载的。 -
playMusic
方法用于播放音乐,如果音乐未在播放状态,则调用mediaPlayer.start()
来开始播放。 -
pauseMusic
方法用于暂停音乐,如果音乐正在播放,则调用mediaPlayer.pause()
来暂停。 -
continueMusic
方法用于继续播放音乐,如果音乐已暂停,则调用mediaPlayer.start()
来继续播放。 -
onDestroy
方法在服务销毁时被调用,它释放了mediaPlayer
对象的资源,确保不会产生内存泄漏。
服务允许其他组件与其绑定,以控制音乐的播放、暂停和继续播放;载了一个音乐资源(在这个示例中是 R.raw.music
),并使用 MediaPlayer
对象进行音乐播放
在AndroidManifest.xml中注册MusicService(检查)
确保在AndroidManifest.xml文件中注册MusicService,以便应用能够正常启动该服务。
一般在我们创建service文件后,会自动进行注册的
<service android:name=".MusicService" />
步骤 2: 在 MainActivity 中连接 MusicService
在 MainActivity
中,添加代码来连接 MusicService
并控制音乐的播放、暂停和继续。
private MusicService musicService;
private boolean isBound = false;private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {MusicService.MusicBinder binder = (MusicService.MusicBinder) service;musicService = binder.getService();isBound = true;}@Overridepublic void onServiceDisconnected(ComponentName name) {isBound = false;}
};@Override
protected void onStart() {super.onStart();Intent intent = new Intent(this, MusicService.class);bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}@Override
protected void onStop() {super.onStop();if (isBound) {unbindService(serviceConnection);isBound = false;}
}
步骤 3: 在 MainActivity 中调用 MusicService 的方法
在 onClick
方法中调用 MusicService
的方法来控制音乐的播放、暂停和继续。
@Override
public void onClick(View v) {switch (v.getId()) {case R.id.btn_play: // 播放按钮点击事件if (isBound) {musicService.playMusic();}animator.start(); // 播放动画break;case R.id.btn_pause: // 暂停按钮点击事件if (isBound) {musicService.pauseMusic();}animator.pause(); // 暂停播放动画break;case R.id.btn_continue_play: // 继续播放按钮点击事件if (isBound) {musicService.continueMusic();}animator.start(); // 播放动画break;case R.id.btn_exit: // 退出按钮点击事件finish(); // 关闭音乐播放界面break;}
}
当前,我们就已经初步完成了简单音乐播放器的播放、暂停、继续、退出功能;
你可以尝试此时运行项目测试效果!
步骤 4: 修改MusicService(以实现通信更新UI)
添加获取相关信息函数
// 获取音乐总时长public int getTotalDuration() {return mediaPlayer.getDuration();}// 获取音乐当前播放进度public int getCurrentPosition() {return mediaPlayer.getCurrentPosition();}// 设置音乐播放进度public void seekTo(int position) {mediaPlayer.seekTo(position);}// 更新UI,发送消息给MainActivityprivate void updateUI(int progress, int totalDuration) {if (handler != null) {Message message = Message.obtain(handler, UPDATE_UI, progress, totalDuration);handler.sendMessage(message);}}@Overridepublic void onCreate() {super.onCreate();mediaPlayer = MediaPlayer.create(this, R.raw.music); // 加载音乐资源mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理}});// 定时发送消息以更新UIRunnable runnable = new Runnable() {@Overridepublic void run() {if (mediaPlayer != null && mediaPlayer.isPlaying()) {int progress = mediaPlayer.getCurrentPosition();int totalDuration = mediaPlayer.getDuration();updateUI(progress, totalDuration);}handler.postDelayed(this, DELAY_MILLIS);}};handler.postDelayed(runnable, DELAY_MILLIS);}
这段代码是为 MusicService
添加了一些重要的功能,以实现与 MainActivity
之间的通信并更新UI。以下是代码的描述:
-
getTotalDuration
函数用于获取音乐的总时长。它通过mediaPlayer.getDuration()
方法获取音乐的总时长,然后返回该值。 -
getCurrentPosition
函数用于获取音乐的当前播放进度。通过mediaPlayer.getCurrentPosition()
方法获取音乐的当前播放进度,然后返回该值。 -
seekTo
函数用于设置音乐的播放进度。接受一个整数参数position
,表示要设置的音乐播放进度,并使用mediaPlayer.seekTo(position)
方法来实现进度的跳转。 -
updateUI
函数用于发送消息给MainActivity
,以便更新UI元素。它接受两个参数,分别是当前播放进度progress
和音乐总时长totalDuration
。它创建一个Message
对象,并通过handler.sendMessage(message)
发送消息给MainActivity
,以便更新UI元素,比如进度条和文本。 -
在
onCreate
方法中,定时发送消息以更新UI。通过一个Runnable
定时任务,在其中获取当前播放进度和音乐总时长,然后调用updateUI
函数发送消息给MainActivity
,以实现不断更新UI元素的目的。
这些函数和逻辑使 MusicService
能够与 MainActivity
进行通信,传递音乐播放进度和总时长,以便 MainActivity
能够更新UI元素,提供用户友好的音乐播放体验。
步骤 5: 修改MainActivity(以实现通信更新UI)
在MusicService中获取音乐总时长,并在MainActivity中更新tv_total和进度条的位置,以及格式化音乐的总时长和进度。
private static final int UPDATE_UI = 1;public final static Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == UPDATE_UI) {int progress = msg.arg1;int totalDuration = msg.arg2;updateUI(progress, totalDuration);}}};public static void updateUI(int progress, int totalDuration) {sb.setProgress(progress);tv_progress.setText(formatDuration(progress));// 更新左侧显示的总时间tv_total.setText(formatDuration(totalDuration));}// 辅助方法来更新进度private void updateProgress(int progress) {tv_progress.setText(formatDuration(progress));}// 辅助方法来格式化音乐时长private static String formatDuration(int duration) {int minutes = (duration / 1000) / 60;int seconds = (duration / 1000) % 60;return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);}// 添加方法来更新总时长private void updateTotalDuration(int duration) {tv_total.setText(formatDuration(duration));sb.setMax(duration);}private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {MusicService.MusicBinder binder = (MusicService.MusicBinder) service;musicService = binder.getService();isBound = true;// 获取音乐总时长并更新UIint totalDuration = musicService.getTotalDuration();updateTotalDuration(totalDuration);}@Overridepublic void onServiceDisconnected(ComponentName name) {isBound = false;}};private void init() {// 初始化控件和按钮点击事件监听tv_progress = findViewById(R.id.tv_progress);tv_total = findViewById(R.id.tv_total);sb = findViewById(R.id.sb);// ...sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {if (progress == seekBar.getMax()) {animator.pause();}updateProgress(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 更新音乐播放进度int progress = seekBar.getProgress();musicService.seekTo(progress); // 添加 seekTo 方法用于定位音乐进度}});// ...
这段代码是为 MainActivity
添加了与 MusicService
之间的通信,以便实现音乐播放进度的动态更新和音乐总时长的显示。以下是代码的描述:
-
在
MainActivity
中定义了一个handler
,这是一个静态的Handler
对象,它用于处理从MusicService
发送的消息,以便更新UI元素。通过UPDATE_UI
常量来标识消息类型。 -
updateUI
函数是用于更新UI元素的核心方法。接受两个参数,分别是当前播放进度progress
和音乐总时长totalDuration
。在这个方法中,进度条的位置会被设置为当前播放进度,左侧的文本tv_progress
会被更新为格式化后的播放进度,而左侧的总时长文本tv_total
会被更新为格式化后的音乐总时长。 -
updateProgress
方法是一个辅助方法,用于更新播放进度。接受一个参数progress
,并更新左侧的文本tv_progress
为格式化后的播放进度。 -
formatDuration
方法是一个辅助方法,用于格式化音乐时长。接受一个整数duration
,表示音乐的时长(以毫秒为单位),然后将其格式化为分:秒的形式。 -
updateTotalDuration
方法用于更新总时长。它接受一个参数duration
,表示音乐的总时长,并更新左侧的总时长文本tv_total
为格式化后的音乐总时长,并设置进度条的最大值为音乐的总时长。 -
在
serviceConnection
中,当MusicService
与MainActivity
连接成功后,会获取音乐的总时长并调用updateTotalDuration
方法来更新UI元素。 -
在
sb
(SeekBar)的事件监听器中,通过onProgressChanged
方法,当进度条的进度发生变化时,会调用updateProgress
方法来更新左侧的播放进度文本。在onStopTrackingTouch
方法中,当用户拖动进度条时,会调用musicService.seekTo(progress)
方法来定位音乐的进度。
这些代码改动使 MainActivity
能够与 MusicService
协同工作,以实现音乐播放进度的动态更新和音乐总时长的显示。这对于提供用户友好的音乐播放体验至关重要。
步骤 6: 增加音乐结束后的处理细节
对于这些新的问题,我们可以进行以下修改和处理:
- 停止动画: 随着音乐播放的完成,动画应该随之停止。我们在
MusicService
中添加了一个音乐播放完成回调,以便在音乐结束时暂停动画。这样,用户可以看到音乐已经完成,同时动画不再旋转,提供了明确的视觉指示。如下所示:
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画}
});
- 左侧动的文字内容无法到达最大值: 为了确保在音乐播放完成后左侧的时间文本达到最大值,我们在音乐播放完成回调中更新了左侧的时间文本。通过调用
tv_progress.setText(formatDuration(getTotalDuration()))
,我们将左侧的时间文本设置为音乐的总时长,以表明音乐已完成
@Overridepublic void onCreate() {
//……@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画tv_progress.setText(formatDuration(getTotalDuration()));}});}
- 在音乐播放完成后没有提醒: 我们添加了一种通知用户音乐已完成的方式。在
MusicService
中的音乐播放完成回调中,我们使用showToast
函数显示一个短暂的提示消息。这种提醒可以根据你的需求进行扩展,例如,你可以选择显示通知、执行其他操作或添加更多的用户反馈。
private void showToast(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}@Overridepublic void onCreate() {
//……@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画showToast("音乐已完成"); // 显示音乐播放完成的提示}});}
完整代码
MainActivity.java
package cn.itcast.musicplayer;import static cn.itcast.musicplayer.MusicService.mediaPlayer;import android.animation.ObjectAnimator;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Bundle;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;import java.util.Locale;public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static SeekBar sb;public static TextView tv_progress, tv_total;public static ObjectAnimator animator;private MusicService musicService;private boolean isBound = false;private static final int UPDATE_UI = 1;// 辅助方法来格式化音乐时长public static String formatDuration(int duration) {int minutes = (duration / 1000) / 60;int seconds = (duration / 1000) % 60;return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);}// 添加方法来更新总时长private void updateTotalDuration(int duration) {tv_total.setText(formatDuration(duration));sb.setMax(duration);}// 辅助方法来更新进度private void updateProgress(int progress) {tv_progress.setText(formatDuration(progress));}private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {MusicService.MusicBinder binder = (MusicService.MusicBinder) service;musicService = binder.getService();isBound = true;// 获取音乐总时长并更新UIint totalDuration = musicService.getTotalDuration();updateTotalDuration(totalDuration);}@Overridepublic void onServiceDisconnected(ComponentName name) {isBound = false;}};public final static Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == UPDATE_UI) {int progress = msg.arg1;int totalDuration = msg.arg2;updateUI(progress, totalDuration);}}};public static void updateUI(int progress, int totalDuration) {sb.setProgress(progress);tv_progress.setText(formatDuration(progress));tv_total.setText(formatDuration(totalDuration));}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);init();}private void init() {// 初始化控件和按钮点击事件监听tv_progress = findViewById(R.id.tv_progress);tv_total = findViewById(R.id.tv_total);sb = findViewById(R.id.sb);findViewById(R.id.btn_play).setOnClickListener(this);findViewById(R.id.btn_pause).setOnClickListener(this);findViewById(R.id.btn_continue_play).setOnClickListener(this);findViewById(R.id.btn_exit).setOnClickListener(this);// ...sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {if (progress == seekBar.getMax()) {animator.pause();}updateProgress(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 更新音乐播放进度int progress = seekBar.getProgress();musicService.seekTo(progress); // 添加 seekTo 方法用于定位音乐进度}});// 初始化动画ImageView iv_music = findViewById(R.id.iv_music);animator = ObjectAnimator.ofFloat(iv_music, "rotation", 0f, 360.0f);animator.setDuration(10000); //动画旋转一周的时间为10秒animator.setInterpolator(new LinearInterpolator());animator.setRepeatCount(-1); //-1表示设置动画无限循环}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_play:if (isBound) {musicService.playMusic();}animator.start();break;case R.id.btn_pause:if (isBound) {musicService.pauseMusic();}animator.pause();break;case R.id.btn_continue_play:if (isBound) {musicService.continueMusic();}animator.start();break;case R.id.btn_exit:finish();break;}}@Overrideprotected void onStart() {super.onStart();Intent intent = new Intent(this, MusicService.class);bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);}@Overrideprotected void onDestroy() {super.onDestroy();//解绑服务if (isBound) {unbindService(serviceConnection);isBound = false;}}
}
MusicService.java
package cn.itcast.musicplayer;import static cn.itcast.musicplayer.MainActivity.formatDuration;
import static cn.itcast.musicplayer.MainActivity.handler;
import static cn.itcast.musicplayer.MainActivity.animator;
import static cn.itcast.musicplayer.MainActivity.tv_progress;import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.Toast;public class MusicService extends Service {public static MediaPlayer mediaPlayer;private final IBinder binder = new MusicBinder();private final int UPDATE_UI = 1;private final int DELAY_MILLIS = 1000; // 延迟1秒发送消息public MusicService() {}@Overridepublic IBinder onBind(Intent intent) {return binder;}public class MusicBinder extends Binder {MusicService getService() {return MusicService.this;}}private void showToast(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}@Overridepublic void onCreate() {super.onCreate();mediaPlayer = MediaPlayer.create(this, R.raw.music); // 加载音乐资源mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画tv_progress.setText(formatDuration(getTotalDuration()));showToast("音乐已完成"); // 显示音乐播放完成的提示}});// 定时发送消息以更新UIRunnable runnable = new Runnable() {@Overridepublic void run() {if (mediaPlayer != null && mediaPlayer.isPlaying()) {int progress = mediaPlayer.getCurrentPosition();int totalDuration = mediaPlayer.getDuration();updateUI(progress, totalDuration);}handler.postDelayed(this, DELAY_MILLIS);}};handler.postDelayed(runnable, DELAY_MILLIS);}// 更新UI,发送消息给MainActivityprivate void updateUI(int progress, int totalDuration) {if (handler != null) {Message message = Message.obtain(handler, UPDATE_UI, progress, totalDuration);handler.sendMessage(message);}}// 添加播放音乐的方法public void playMusic() {if (!mediaPlayer.isPlaying()) {mediaPlayer.start();}}// 添加暂停音乐的方法public void pauseMusic() {if (mediaPlayer.isPlaying()) {mediaPlayer.pause();}}// 添加继续播放音乐的方法public void continueMusic() {if (!mediaPlayer.isPlaying()) {mediaPlayer.start();}}@Overridepublic void onDestroy() {if (mediaPlayer != null) {mediaPlayer.release();mediaPlayer = null;}super.onDestroy();}// 获取音乐总时长public int getTotalDuration() {return mediaPlayer.getDuration();}// 获取音乐当前播放进度public int getCurrentPosition() {return mediaPlayer.getCurrentPosition();}// 设置音乐播放进度public void seekTo(int position) {mediaPlayer.seekTo(position);}}
实现效果
最重要的是能在
后台播放音乐
相关文章:

Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】
给定部分完成的MusicPlayer项目,实现其中未完成的service部分: 1、创建MusicService类,通过service组件实现后台播放音乐的功能; 2、在MainActivity中通过ServiceConnection连接MusicService,实现对音乐播放的控制&…...

使用Windows平台的Hyper-V虚拟机安装CentOS7的详细过程
Hyper-V虚拟机安装CentOS7 前言常见Linux系统CentOSUbuntuDebianKaliFedoraArch LinuxMintManjaroopenSUSE Hyper-V开启Hyper-V打开Hyper-V Hyper-V的使用新建虚拟机开始安装分区配置开始安装 修改yum源为阿里源 前言 作为一名开发者,就服务器而言,接触最…...

某马机房预约系统 C++项目(二) 完结
8.4、查看机房 8.4.1、添加机房信息 根据案例,我们还是先在computerRoom.txt中直接添加点数据 //几机房 机器数量 1 20 2 50 3 1008.4.2、机房类创建 同样我们在头文件下新建一个computerRoom.h文件 添加如下代码: #pragma once #include<i…...

npm 安装到指定文件夹
创建一个文件夹,用vscode或者cmd打开, 执行 npm install --prefix ./ 路径 包名, npm install --prefix ./ 包名 , 就会将包安装在当前文件夹, 例如: npm install --prefix ./ -g oppo-minigame…...

自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例
自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例 在matlab中有自带的离散傅里叶变换程序,即fft程序,但该程序是封装的,无法看到源码。为了比较清楚的了解matlab自带的实现过程,本文通过自建程序实现matlab程序&…...

Vue图片路径问题(动态引入)
vue项目中我们经常会遇到动态路径的图片无法显示的问题,以下是静态路径和动态路径的常见使用方法。 1.静态路径 在日常的开发中,图片的静态路径通过相对路径和绝对路径的方式引入。 相对路径:以.开头的,例如./、../之类的。就是…...

项目部署Linux步骤
1、最小化安装centos7-环境准备 安装epel-release 安装epel-release,因为有些rpm包在官方库中找不到。前提是保证可以联网 yum install -y epel-release 修改IP net-tools net-tool:工具包集合,包含ifconfig等命令 yum install -y net-…...

UG\NX二次开发 在资源栏(左侧面板)中添加按钮
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 apolloryd 订阅本专栏,非常感谢。 简介 UG\NX二次开发 在资源栏(左侧面板)中添加按钮,下面提供了帮助说明,在 UGOPEN 文件夹下有示例。 C++语言在UG二次…...

Proteus仿真--量程自动切换数字电压表(仿真+程序)
本文主要介绍基于51单片机的量程自动切换数字电压表Proteus仿真设计(完整仿真源文件及代码见文末链接) 简介 硬件电路主要分为单片机主控模块、AD转换模块、量程选择模块以及数码管显示模块 (1)单片机主控模块:单片…...

如何使用ArcGIS Pro制作一张地形图
01数据来源 本教程所使用的数据是从水经微图中下载的DEM数据,除了DEM数据,常见的GIS数据都可以从水经微图中下载,你可以通过关注“水经注GIS”,然后在后台回复“微图”即可获取软件下载地址,当然也可以直接在水经注…...

人工智能三要数之算法Transformer
1. 人工智能三要数之算法Transformer 人工智能的三个要素是算法、数据和计算资源。Transformer 模型作为一种机器学习算法,可以应用于人工智能系统中的数据处理和建模任务。 算法: Transformer 是一种基于自注意力机制的神经网络模型,用于处理序列数据的…...

Java ThreadPoolExecutor 线程池
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ArrayBlockingQueue;public class ThreadPoolExample {public static void main(String[] args) {// 创建线程池对象ThreadPoolExecutor threadPool new…...

网络协议--IP选路
9.1 引言 选路是IP最重要的功能之一。图9-1是IP层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生,也可以由其他主机产生。在后一种情况下,主机必须配置成一个路由器,否则通过网络接口接收到的数据报,如果目的地址不…...

使用udevil自动挂载U盘或者USB移动硬盘
最近在折腾用树莓派(实际上是平替香橙派orangepi zero3)搭建共享文件服务器,有一个问题很重要,如何在系统启动时自动挂载USB移动硬盘。 1 使用/etc/fstab 最开始尝试了用/etc/fstab文件下增加:"/dev/sda1 /home/orangepi/s…...

学习笔记二十二:K8s控制器Replicaset
K8s控制器Replicaset Replicaset控制器:概念、原理解读Replicaset概述Replicaset工作原理:如何管理PodReplicaset控制器三个组成部分 Replicaset资源清单文件编写技巧Replicaset使用案例:部署Guestbook留言板编写一个ReplicaSet资源清单资源清…...

2023-10-25 精神分析-领悟新技术的错误做法-持续数年的错误做法-记录与分析
摘要: 过去数年对于领悟技术, 采取的做法不能说是对达到目的毫无裨益,但是对突破技术和将技术融为自身这个目的来说, 没有达到。 而且随着时间的流逝, 过去已经熟悉的技术, 竟然会被忘掉!就像是没有涉猎过一样! 根本原因出在对技术的领悟的…...

Arrays 中的 asList()方法
public static <T> List<T> asList( T . . . a ){ return new ArrayList<>(a); } 返回由指定数组支持的固定大小的 list集合。对数组所做的更改将在返回的 l…...

基于自动化工具autox.js的抢票(猫眼)
1.看到朋友圈抢周杰伦、林俊杰演唱会票贼难信息,特研究了一段时间,用autox.js写了自动化抢票脚本,购票页面自动点击下单(仅限安卓手机)。 2.脚本运行图 3.前期准备工作 (1)autox.js社区官网:AutoX.js (2)b站上学习资料:10分钟学会AutoX.js hello world_哔哩哔哩_bi…...

Java架构师内功计算机网络
目录 1 导学2 网络功能和分类3 OSI七层模型3.1 局域网和广域网协议4 TCP/IP协议5 通信技术和交换技术5.1 通信技术5.2 交换技术5.2.1 路由技术5.2.2 传输介质6 通信方式和交换方式7 IP地址7.1 IP地址表示7.2 子网划分8 IPv69 网络规划与设计10 网络存储技术10.1 廉价磁盘几余阵…...

vue 中 mixin 和 mixins 区别
目录 前言 用法 全局Mixin 局部Mixin 代码 理解 高质量的Mixin使用 在Vue.js框架中,Mixin是一种非常重要和强大的功能,它允许开发者创建可复用的代码片段,并将其应用到一个或多个组件中。Vue提供了两种方式来使用Mixin,分别…...

reqable(小黄鸟)+雷电抓包安卓APP
x 下载证书保存到雷电模拟器根目录(安装位置) 为什么? Android7以上,系统允许每个应用可以定义自己的可信CA集,部分的应用默认只会信任系统预装的CA证书,而不会信任用户安装的证书,之前的方法安装Burp/Fiddler证书都是用户证书…...

高等数学啃书汇总重难点(七)微分方程
同济高数上册的最后一章,总的来说,这篇章内容依旧是偏记忆为主,说难不难说简单不简单: 简单的是题型比较死,基本上就是记公式,不会出现不定积分一般花样繁多的情况;然而也就是背公式并不是想的…...

阿里云对象存储OSS文件无法预览,Bucket设置了Referer
您发起的请求头中没有Referer字段或Referer字段为空,与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…...

数字孪生技术:工业数字化转型的引擎
数字孪生是一种将物理实体数字化为虚拟模型的技术,这些虚拟模型与其物理对应物相互关联。这种虚拟模型通常是在数字平台上创建的,它们复制了实际设备、工厂、甚至整个供应链的运作方式。这使工业企业能够实现以下益处: 1. 实时监测和分析 数…...

算法刷题-哈希表
算法刷题-哈希表 242. 有效的字母异位词 给定两个字符串 *s* 和 *t* ,编写一个函数来判断 *t* 是否是 *s* 的字母异位词。 **注意:**若 *s* 和 *t* 中每个字符出现的次数都相同,则称 *s* 和 *t* 互为字母异位词。 思路 用一个哈希表来记…...

2023NOIP A层联测17 黑暗料理
题目大意 给出一个长度为 n n n的序列 a i a_i ai,要求删去若干个数,使得剩下的数中任意两个数都不是质数。在满足条件的情况下最多能保留几个数。 有 T T T组数据。 1 ≤ T ≤ 4 , 1 ≤ n ≤ 750 , 1 ≤ a i ≤ 1 0 9 1\leq T\leq 4,1\leq n\leq 75…...

关于nacos的配置获取失败及服务发现问题的排坑记录
nacos配置更新未能获取到导致启动报错 排查思路: 1、是否添加了nacos的启动pom依赖 参考: <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><…...

【QT】其他常用控件1
新建项目 scrollArea 滚动 toolBox 插入 tabWidget stackedWidget 切换 索引是0 运行后,没有切换按钮,结合pushbutton,加两个Button 代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent)…...

交换机/防火墙-基础配置-23.10.11
update 优化了目录逻辑 -10.24.2023 一.前置知识 1.MAC地址 交换机在给主机之间传递信息包时,通过MAC地址来标识每台主机 主机间发生信息包交换时,交换机就会将通信过的主机的mac地址存下 dis mac-address 交换机转发的数据包中,会包含一…...

alibaba.fastjson的使用(四)-- Json字符 与 JsonObject 的相互转化
目录 1. Json字符串转JsonObject 2. JsonObject转Json字符串 1. Json字符串转JsonObject 使用到的方法1: static JSONObject parseObject(String text) 使用到的方法2: public String getString(String key) /*** 将Json字符串转为JsonObject对象* 取值不存在时,返回null…...