Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】

给定部分完成的MusicPlayer项目,实现其中未完成的service部分:

1、创建MusicService类,通过service组件实现后台播放音乐的功能;

2、在MainActivity中通过ServiceConnection连接MusicService,实现对音乐播放的控制;

3、使用Handler机制在MainActivity和MusicService之间进行通信。

目前已有代码:

相关的资源文件,可自行寻找

activity_main.xml

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">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="160dp"
        tools:ignore="UselessParent">
        <RelativeLayout
            android: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">
            <TextView
                android: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"/>
            <TextView
                android: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" />
            <SeekBar
                android: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" />
            <RelativeLayout
                android:layout_marginTop="4dp"
                android:id="@+id/rl_time"
                android:layout_width="150dp"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_type">
                <TextView
                    android: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" />
                <TextView
                    android: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>
        <LinearLayout
            android: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">
            <Button
                android: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" />
            <Button
                android: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" />
            <Button
                android: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" />
            <Button
                android: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>
        <ImageView
            android: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

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;

    @Override
    protected 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() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean
                    fromUser) {                          //滑动条进度改变时,会调用此方法
                if (progress == seekBar.getMax()) { //当滑动条滑到末端时,结束动画
                    animator.pause();                   //停止播放动画
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {//滑动条开始滑动时调用
            }

            @Override
            public 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表示设置动画无限循环
    }

    @Override
    public 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;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑服务
    }
}

当前已经有一个用户界面,其中包括了播放、暂停、继续播放和退出按钮,以及一个旋转动画效果。现在,我们需要将MusicService与MainActivity连接起来,以实现音乐的播放和控制功能。

步骤1:创建MusicService类

单击鼠标右键并选择【New】-->【Service】-->【Service】

步骤 1: 创建 MusicService 类

首先,你需要创建一个名为 MusicService 的类,该类将负责处理音乐播放和与 MainActivity 之间的通信。

这里你需要准备一个mp3格式的文件

java 复制代码
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() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new MusicBinder();
    }

    public class MusicBinder extends Binder {
        MusicService getService() {
            return MusicService.this;
        }
    }

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

    @Override
    public 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文件后,会自动进行注册的

xml 复制代码
<service android:name=".MusicService" />

步骤 2: 在 MainActivity 中连接 MusicService

MainActivity 中,添加代码来连接 MusicService 并控制音乐的播放、暂停和继续。

java 复制代码
private MusicService musicService;
private boolean isBound = false;

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
        musicService = binder.getService();
        isBound = true;
    }

    @Override
    public 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 的方法来控制音乐的播放、暂停和继续。

java 复制代码
@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)

添加获取相关信息函数

java 复制代码
    // 获取音乐总时长
    public int getTotalDuration() {
        return mediaPlayer.getDuration();
    }

    // 获取音乐当前播放进度
    public int getCurrentPosition() {
        return mediaPlayer.getCurrentPosition();
    }

    // 设置音乐播放进度
    public void seekTo(int position) {
        mediaPlayer.seekTo(position);
    }
    
    // 更新UI,发送消息给MainActivity
    private void updateUI(int progress, int totalDuration) {
        if (handler != null) {
            Message message = Message.obtain(handler, UPDATE_UI, progress, totalDuration);
            handler.sendMessage(message);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mediaPlayer = MediaPlayer.create(this, R.raw.music); // 加载音乐资源
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                // 音乐播放完成时的处理
            }
        });

        // 定时发送消息以更新UI
        Runnable runnable = new Runnable() {
            @Override
            public 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和进度条的位置,以及格式化音乐的总时长和进度。

java 复制代码
    private static final int UPDATE_UI = 1;

    public final static Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public 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() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
            musicService = binder.getService();
            isBound = true;

            // 获取音乐总时长并更新UI
            int totalDuration = musicService.getTotalDuration();
            updateTotalDuration(totalDuration);
        }

        @Override
        public 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() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (progress == seekBar.getMax()) {
                    animator.pause();
                }
                updateProgress(progress);

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public 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 中添加了一个音乐播放完成回调,以便在音乐结束时暂停动画。这样,用户可以看到音乐已经完成,同时动画不再旋转,提供了明确的视觉指示。如下所示:
java 复制代码
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        // 音乐播放完成时的处理
        animator.pause(); // 停止动画
    }
});
  1. 左侧动的文字内容无法到达最大值: 为了确保在音乐播放完成后左侧的时间文本达到最大值,我们在音乐播放完成回调中更新了左侧的时间文本。通过调用 tv_progress.setText(formatDuration(getTotalDuration())),我们将左侧的时间文本设置为音乐的总时长,以表明音乐已完成
java 复制代码
    @Override
    public void onCreate() {
//......
            @Override
            public void onCompletion(MediaPlayer mp) {
                // 音乐播放完成时的处理
                animator.pause(); // 停止动画
                tv_progress.setText(formatDuration(getTotalDuration()));
            }
        });

    }
  1. 在音乐播放完成后没有提醒: 我们添加了一种通知用户音乐已完成的方式。在 MusicService 中的音乐播放完成回调中,我们使用showToast函数显示一个短暂的提示消息。这种提醒可以根据你的需求进行扩展,例如,你可以选择显示通知、执行其他操作或添加更多的用户反馈。
java 复制代码
    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCreate() {
//......
            @Override
            public void onCompletion(MediaPlayer mp) {
                // 音乐播放完成时的处理
                animator.pause(); // 停止动画
                showToast("音乐已完成"); // 显示音乐播放完成的提示
            }
        });

    }

完整代码
MainActivity.java

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() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
            musicService = binder.getService();
            isBound = true;

            // 获取音乐总时长并更新UI
            int totalDuration = musicService.getTotalDuration();
            updateTotalDuration(totalDuration);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
        }
    };

    public final static Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public 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));
    }


    @Override
    protected 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() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (progress == seekBar.getMax()) {
                    animator.pause();
                }
                updateProgress(progress);

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public 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表示设置动画无限循环
    }

    @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;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MusicService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑服务
        if (isBound) {
            unbindService(serviceConnection);
            isBound = false;
        }
    }
}

MusicService.java

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() {
    }
    @Override
    public 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();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mediaPlayer = MediaPlayer.create(this, R.raw.music); // 加载音乐资源
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                // 音乐播放完成时的处理
                animator.pause(); // 停止动画
                tv_progress.setText(formatDuration(getTotalDuration()));
                showToast("音乐已完成"); // 显示音乐播放完成的提示
            }
        });

        // 定时发送消息以更新UI
        Runnable runnable = new Runnable() {
            @Override
            public 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,发送消息给MainActivity
    private 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();
        }
    }

    @Override
    public 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);
    }

}

实现效果

最重要的是能在后台播放音乐

相关推荐
帅得不敢出门3 分钟前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
problc42 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
帅得不敢出门11 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了13 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任14 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山14 小时前
Android“引用们”的底层原理
android·java
迃-幵15 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶15 小时前
Android——从相机/相册获取图片
android
Rverdoser16 小时前
Android Studio 多工程公用module引用
android·ide·android studio
aaajj16 小时前
[Android]从FLAG_SECURE禁止截屏看surface
android