当前位置: 首页 > 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;分别…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

Spring AOP代理对象生成原理

代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】&#xff0c;这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...

用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章

用 Rust 重写 Linux 内核模块实战&#xff1a;迈向安全内核的新篇章 ​​摘要&#xff1a;​​ 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言&#xff0c;受限于 C 语言本身的内存安全和并发安全问题&#xff0c;开发复杂模块极易引入难以…...