当前位置: 首页 > news >正文

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();}
}

相关变量描述:

  1. MusicService 是一个 Android 服务类,用于处理音乐播放相关的功能。

  2. mediaPlayer 是用于播放音乐的 MediaPlayer 对象,它负责加载音乐资源、播放、暂停和继续播放音乐。

  3. MusicBinder 内部类继承自 Binder,用于绑定服务与其他组件之间的通信。

  4. onBind 方法用于返回 MusicBinder 对象,以便其他组件可以与服务进行绑定。

  5. onCreate 方法在服务创建时被调用,它初始化了 mediaPlayer 并加载音乐资源。在这个示例中,音乐资源是从 R.raw.music 中加载的。

  6. playMusic 方法用于播放音乐,如果音乐未在播放状态,则调用 mediaPlayer.start() 来开始播放。

  7. pauseMusic 方法用于暂停音乐,如果音乐正在播放,则调用 mediaPlayer.pause() 来暂停。

  8. continueMusic 方法用于继续播放音乐,如果音乐已暂停,则调用 mediaPlayer.start() 来继续播放。

  9. 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。以下是代码的描述:

  1. getTotalDuration 函数用于获取音乐的总时长。它通过 mediaPlayer.getDuration() 方法获取音乐的总时长,然后返回该值。

  2. getCurrentPosition 函数用于获取音乐的当前播放进度。通过 mediaPlayer.getCurrentPosition() 方法获取音乐的当前播放进度,然后返回该值。

  3. seekTo 函数用于设置音乐的播放进度。接受一个整数参数 position,表示要设置的音乐播放进度,并使用 mediaPlayer.seekTo(position) 方法来实现进度的跳转。

  4. updateUI 函数用于发送消息给 MainActivity,以便更新UI元素。它接受两个参数,分别是当前播放进度 progress 和音乐总时长 totalDuration。它创建一个 Message 对象,并通过 handler.sendMessage(message) 发送消息给 MainActivity,以便更新UI元素,比如进度条和文本。

  5. 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 之间的通信,以便实现音乐播放进度的动态更新和音乐总时长的显示。以下是代码的描述:

  1. MainActivity 中定义了一个 handler,这是一个静态的 Handler 对象,它用于处理从 MusicService 发送的消息,以便更新UI元素。通过 UPDATE_UI 常量来标识消息类型。

  2. updateUI 函数是用于更新UI元素的核心方法。接受两个参数,分别是当前播放进度 progress 和音乐总时长 totalDuration。在这个方法中,进度条的位置会被设置为当前播放进度,左侧的文本 tv_progress 会被更新为格式化后的播放进度,而左侧的总时长文本 tv_total 会被更新为格式化后的音乐总时长。

  3. updateProgress 方法是一个辅助方法,用于更新播放进度。接受一个参数 progress,并更新左侧的文本 tv_progress 为格式化后的播放进度。

  4. formatDuration 方法是一个辅助方法,用于格式化音乐时长。接受一个整数 duration,表示音乐的时长(以毫秒为单位),然后将其格式化为分:秒的形式。

  5. updateTotalDuration 方法用于更新总时长。它接受一个参数 duration,表示音乐的总时长,并更新左侧的总时长文本 tv_total 为格式化后的音乐总时长,并设置进度条的最大值为音乐的总时长。

  6. serviceConnection 中,当 MusicServiceMainActivity 连接成功后,会获取音乐的总时长并调用 updateTotalDuration 方法来更新UI元素。

  7. sb(SeekBar)的事件监听器中,通过 onProgressChanged 方法,当进度条的进度发生变化时,会调用 updateProgress 方法来更新左侧的播放进度文本。在 onStopTrackingTouch 方法中,当用户拖动进度条时,会调用 musicService.seekTo(progress) 方法来定位音乐的进度。

这些代码改动使 MainActivity 能够与 MusicService 协同工作,以实现音乐播放进度的动态更新和音乐总时长的显示。这对于提供用户友好的音乐播放体验至关重要。

步骤 6: 增加音乐结束后的处理细节
对于这些新的问题,我们可以进行以下修改和处理:

  1. 停止动画: 随着音乐播放的完成,动画应该随之停止。我们在 MusicService 中添加了一个音乐播放完成回调,以便在音乐结束时暂停动画。这样,用户可以看到音乐已经完成,同时动画不再旋转,提供了明确的视觉指示。如下所示:
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画}
});
  1. 左侧动的文字内容无法到达最大值: 为了确保在音乐播放完成后左侧的时间文本达到最大值,我们在音乐播放完成回调中更新了左侧的时间文本。通过调用 tv_progress.setText(formatDuration(getTotalDuration())),我们将左侧的时间文本设置为音乐的总时长,以表明音乐已完成
    @Overridepublic void onCreate() {
//……@Overridepublic void onCompletion(MediaPlayer mp) {// 音乐播放完成时的处理animator.pause(); // 停止动画tv_progress.setText(formatDuration(getTotalDuration()));}});}
  1. 在音乐播放完成后没有提醒: 我们添加了一种通知用户音乐已完成的方式。在 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项目&#xff0c;实现其中未完成的service部分&#xff1a; 1、创建MusicService类&#xff0c;通过service组件实现后台播放音乐的功能&#xff1b; 2、在MainActivity中通过ServiceConnection连接MusicService&#xff0c;实现对音乐播放的控制&…...

使用Windows平台的Hyper-V虚拟机安装CentOS7的详细过程

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

某马机房预约系统 C++项目(二) 完结

8.4、查看机房 8.4.1、添加机房信息 根据案例&#xff0c;我们还是先在computerRoom.txt中直接添加点数据 //几机房 机器数量 1 20 2 50 3 1008.4.2、机房类创建 ​ 同样我们在头文件下新建一个computerRoom.h文件 添加如下代码&#xff1a; #pragma once #include<i…...

npm 安装到指定文件夹

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

自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例

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

Vue图片路径问题(动态引入)

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

项目部署Linux步骤

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

UG\NX二次开发 在资源栏(左侧面板)中添加按钮

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

Proteus仿真--量程自动切换数字电压表(仿真+程序)

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

​如何使用ArcGIS Pro制作一张地形图

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

人工智能三要数之算法Transformer

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

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层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生&#xff0c;也可以由其他主机产生。在后一种情况下&#xff0c;主机必须配置成一个路由器&#xff0c;否则通过网络接口接收到的数据报&#xff0c;如果目的地址不…...

使用udevil自动挂载U盘或者USB移动硬盘

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

学习笔记二十二:K8s控制器Replicaset

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

2023-10-25 精神分析-领悟新技术的错误做法-持续数年的错误做法-记录与分析

摘要: 过去数年对于领悟技术, 采取的做法不能说是对达到目的毫无裨益&#xff0c;但是对突破技术和将技术融为自身这个目的来说, 没有达到。 而且随着时间的流逝, 过去已经熟悉的技术, 竟然会被忘掉&#xff01;就像是没有涉猎过一样&#xff01; 根本原因出在对技术的领悟的…...

Arrays 中的 asList()方法

public static <T> List<T> asList&#xff08; T . . . a &#xff09;{ return new ArrayList<>&#xff08;a&#xff09;&#xff1b; } 返回由指定数组支持的固定大小的 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框架中&#xff0c;Mixin是一种非常重要和强大的功能&#xff0c;它允许开发者创建可复用的代码片段&#xff0c;并将其应用到一个或多个组件中。Vue提供了两种方式来使用Mixin&#xff0c;分别…...

reqable(小黄鸟)+雷电抓包安卓APP

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

高等数学啃书汇总重难点(七)微分方程

同济高数上册的最后一章&#xff0c;总的来说&#xff0c;这篇章内容依旧是偏记忆为主&#xff0c;说难不难说简单不简单&#xff1a; 简单的是题型比较死&#xff0c;基本上就是记公式&#xff0c;不会出现不定积分一般花样繁多的情况&#xff1b;然而也就是背公式并不是想的…...

阿里云对象存储OSS文件无法预览,Bucket设置了Referer

您发起的请求头中没有Referer字段或Referer字段为空&#xff0c;与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…...

数字孪生技术:工业数字化转型的引擎

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

算法刷题-哈希表

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

2023NOIP A层联测17 黑暗料理

题目大意 给出一个长度为 n n n的序列 a i a_i ai​&#xff0c;要求删去若干个数&#xff0c;使得剩下的数中任意两个数都不是质数。在满足条件的情况下最多能保留几个数。 有 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配置更新未能获取到导致启动报错 排查思路&#xff1a; 1、是否添加了nacos的启动pom依赖 参考&#xff1a; <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><…...

【QT】其他常用控件1

新建项目 scrollArea 滚动 toolBox 插入 tabWidget stackedWidget 切换 索引是0 运行后&#xff0c;没有切换按钮&#xff0c;结合pushbutton&#xff0c;加两个Button 代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent)…...

交换机/防火墙-基础配置-23.10.11

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

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…...