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

news2024/11/17 19:52:51

给定部分完成的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">
    <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

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格式的文件
在这里插入图片描述

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文件后,会自动进行注册的
在这里插入图片描述

<service android:name=".MusicService" />

步骤 2: 在 MainActivity 中连接 MusicService

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

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

@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,发送消息给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和进度条的位置,以及格式化音乐的总时长和进度。

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

    @Override
    public void onCreate() {
//……
            @Override
            public 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() {
        @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

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

}

实现效果

最重要的是能在后台播放音乐
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1115683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android端自定义铃声

随着移动应用竞争进入红海时代&#xff0c;如何在APP推送中别出心裁显得尤为重要。例如对自己的APP推送赋予独特的推送铃声&#xff0c;能够给用户更加理想的使用体验。 1、个性化提醒铃声有助于当收到特定类型的消息时&#xff0c;用户能够立刻识别出来。 2、不同的推送铃声…

xxl-job任务调度2.0.2升级到2.3.0版本,执行器改造过程中经验总结

xxl-job任务调度2.0.2升级到2.3.0版本 一、背景二、开始改造1、修改pom.xml2、修改Handler3、启动服务并验证4、解决异常5、再次启动服务并验证 一、背景 现在要对一批老项目中的执行器进行升级&#xff0c;原来老项目中的执行器&#xff0c;依赖的任务调度中心xxl-job版本是2…

金和OA SQL注入漏洞

一、漏洞描述 金和OA协同办公管理系统C6软件&#xff0c;为北京金和网络股份有限公司 开发系统&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#xff0c;充分体现企事业单位规范管理、提高办公效率的核心思想&#xff0c;为…

Go包介绍与初始化:搞清Go程序的执行次序

Go包介绍与初始化&#xff1a;搞清Go程序的执行次序 文章目录 Go包介绍与初始化&#xff1a;搞清Go程序的执行次序一、main.main 函数&#xff1a;Go 应用的入口函数1.1 main.main 函数1.2 main.main 函数特点 二、包介绍2.1 包介绍与声明2.2 非 main包的 main 函数2.3 包的命名…

剑指Offer || 050.路径总和|||

题目 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点到子节…

DETR原理与代码超详细解读

文章目录 前言一、DETR论文原理1、DETR整体介绍2、DETR论文贡献3、DETR模型框架4、DETR基于二分图匹配的LOSS 二、DETR环境安装1、安装基础环境2、pycocotools安装3、其它环境安装4、环境验证5、训练与推理效果显示 三、数据准备1、coco 数据格式2、修改数据 四、DETR加载数据代…

Git的安装

前置 知道自己电脑上跑的是什么系统 查看电脑位数 省事的一种办法 Windows 在cmd中输入如下命令 wmic os get osarchitecture看命令结果即可 省事的一种办法 Linux 直接在终端中输入如下命令 uname -m若结果是x86_64就是64位的&#xff0c;反之32位 图形化的办法 Wind…

Peter算法小课堂—正整数拆分

大家可能会想&#xff1a;正整数拆分谁不会啊&#xff0c;2年级就会了&#xff0c;为啥要学啊 例题 正整数拆分有好几种&#xff0c;这里我们列举两种讲。 关系 我们看着第一幅图&#xff0c;头向左转90&#xff0c;记住你看到的图&#xff0c;再来看第二幅图&#xff0c;你…

lnmp架构部署Discuz论坛并配置重定向转发

lnmp架构部署Discuz论坛并配置重定向转发 文章目录 lnmp架构部署Discuz论坛并配置重定向转发环境说明部署Discuz论坛系统下载Discuz论坛系统代码包&#xff0c;官网地址如下&#xff1a;部署Discuz论坛系统步骤&#xff1a;解压安装Discuz源码包配置虚拟主机进入Discuz安装界面…

pytest利用request fixture实现个性化测试需求详解

这篇文章主要为大家详细介绍了pytest如何利用request fixture实现个性化测试需求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下− 前言 在深入理解 pytest-repeat 插件的工作原理这篇文章中&#xff0c;我们看到pytest_repeat源码中有这样一段 import pyt…

YOLOv5算法改进(14)— 如何去更换主干网络(3)(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。为了给后面YOLOv5算法的进阶改进奠定基础,本篇文章就继续通过案例的方式给大家讲解如何在YOLOv5算法中更换主干网络,本篇文章的特色就是比较浅显易懂,附加了很多的网络结构图,通过结构图的形式向大家娓娓道来,希望大家学习之后能够有所收获…

msvcr120.dll缺失怎么修复,快速修复msvcr120.dll丢失的三个有效方法

随着计算机技术的不断发展&#xff0c;我们在使用软件或游戏时经常会遇到各种错误提示&#xff0c;其中找不到msvcr120.dll就是一种常见的错误。那么&#xff0c;msvcr120.dll是什么&#xff1f;它的作用是什么&#xff1f;如何修复这一错误呢&#xff1f;本文将为您详细介绍几…

Python学习基础笔记七十九——Socket编程2

应用消息格式&#xff1a; 为什么要定义消息格式&#xff1f; 我们发送的消息就是要传递的内容&#xff0c;比如字符串。 我们在企业中开发的程序通讯&#xff0c;消息往往是有格式定义的。消息格式的定义可以归入OSI网络模型的表示层。 比如&#xff1a;定义的消息包括消息…

电子技术基础(三)__第7章 时序逻辑电路_第7篇之解题方法与步骤

我们必须牢记这样一句&#xff1a; 先列出输入逻辑式&#xff0c;即驱动方程&#xff0c; 然后将驱动方程代入触发器的特征方程&#xff0c; 得到状态方程(也称 次态方程)&#xff0c; 最后写出输出方程。 二 看例题 2022.4 真题 第38题 分析: 此题乍一看&#xff0c;可能有点…

jvm 各个版本支持的参数 容器化部署

知道一些 jvm 调优参数&#xff0c;但是没有找到官网对应的文档&#xff0c;在网上的一些文章偶然发现&#xff0c;记录一下。 https://docs.oracle.com/en/java/javase/ 包含各个版本 jdk 8 分为 windows 和 unix 系统 https://docs.oracle.com/javase/8/docs/technotes/too…

Volatile 可以保证什么特性?有什么作用?

Volatile 可以保证什么特性&#xff1f;有什么作用&#xff1f; 可以保证可见性&#xff0c;有序性&#xff0c;禁止指令重排序。但是不能保证原子性。线程上下文切换的时候&#xff0c;还是有可能出现线程安全问题。 cpu 都是把数据从内存拉取到自己的缓存中进行运算&#xff…

在Espressif-IDE中使用Wokwi仿真ESP32

陈拓 2023/10/17-2023/10/19 1. 概述 在Espressif-IDE v2.9.0版本之后可直接在IDE中使用Wokwi模拟器。 1.1 什么是 Wokwi 模拟器&#xff1f; Wokwi 是一款在线电子模拟器&#xff0c;支持模拟各种开发板、元器件和传感器&#xff0c;例如乐鑫产品 ESP32。 Wokwi 提供基于浏…

Vue3.0的设计目标是什么?做了哪些优化

一、设计目标 不以解决实际业务痛点的更新都是耍流氓&#xff0c;下面我们来列举一下Vue3之前我们或许会面临的问题 随着功能的增长&#xff0c;复杂组件的代码变得越来越难以维护缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制类型推断不够友好bundle的时间太久…

游戏行业多元化发展,手游品牌强势出圈的秘诀是什么?

消费观念的转变和社会风气的逐渐开放使手游市场日趋成熟&#xff0c;如今的手游市场面临不少机遇与挑战&#xff0c;游戏行业的多元化发展使玩家们对手游的质量要求更进一步&#xff0c;那么在竞争激烈的手游市场中&#xff0c;手游品牌如何在拥挤的市场中获取更多曝光机会呢&a…

零基础也能制作电子期刊,这个网站你一定不能错过

对于那些想要制作电子期刊但又没有任何基础的人来说&#xff0c;这个网站是一个非常不错的选择。它提供了一系列简单易用的工具和资源&#xff0c;可以帮助你轻松地创建出专业水准的电子期刊。这个网站就是FLBOOK在线制作电子杂志平台。 首先&#xff0c;FLBOOK提供了丰富的模板…