引言:
这样的一个音乐播放器,用到了安卓四大组件的其中三个,等于说是一个比较综合性的小功能。实现方法其实有很多,我这里给出自己的方法,不喜勿喷。
需求分析
1.音乐播放器,那我们需要一个帮助类,来构建单例音乐播放器对象:

package com.example.jackandrose.entities;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;

import java.io.IOException;

public class MediaHelper {
    private static MediaHelper instance;
    private Context mContext;
    private MediaPlayer mMediaPlayer;
    private OnMediaHelperListener mOnMediaHelperListener;
    private int mResID=-5;

    public void setmOnMediaHelperListener(OnMediaHelperListener mOnMediaHelperListener) {
        this.mOnMediaHelperListener = mOnMediaHelperListener;
    }

    public static MediaHelper getInstance(Context context) {
        if(instance==null){
            synchronized (MediaHelper.class){
                if(instance==null){
                    instance=new MediaHelper(context);
                }
            }
        }
        return instance;
    }

    private MediaHelper(Context context){
        mContext=context;
        mMediaPlayer=new MediaPlayer();
    }

    /**
     * 当播放本地uri中音时调用
     * @param path
     */
    public void setPath(String path){
        if(mMediaPlayer.isPlaying()){
            mMediaPlayer.reset();
        }
        try {
            mMediaPlayer.setDataSource(mContext, Uri.parse(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaPlayer.prepareAsync();
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                if(mOnMediaHelperListener !=null){
                    mOnMediaHelperListener.onPrepared(mp);
                }
            }
        });
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if(mOnMediaHelperListener !=null){
                    mOnMediaHelperListener.onPauseState();
                }
            }
        });
    }

    /**
     * 当调用raw下的文件时使用
     * @param resid
     */
    public void setRawFile(int resid){
        if(resid==mResID&&mResID!=-5){
            //相同音乐id或者且不是第一次播放,就直接返回
            return;
        }
        //mOnInitMusicListener.initMode();
        mResID=resid;
        final AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(resid);
        try {
            mMediaPlayer.reset();
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            mMediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                if(mOnMediaHelperListener !=null){
                    mOnMediaHelperListener.onPrepared(mp);
                    try {
                        afd.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if(mOnMediaHelperListener !=null){
                    mOnMediaHelperListener.onPauseState();
                }
            }
        });
    }

    public void start(){
        if(mMediaPlayer.isPlaying()){
            return;
        }
        mMediaPlayer.start();
        if(mOnMediaHelperListener!=null){
            mOnMediaHelperListener.onPlayingState();
        }

    }

    public void pause(){
        if(!mMediaPlayer.isPlaying()){
            return;
        }
        mMediaPlayer.pause();
        if(mOnMediaHelperListener!=null){
            mOnMediaHelperListener.onPauseState();
        }
    }

    public boolean isPlaying(){
        if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
            return true;
        }
        return false;
    }

    public int getCurrentPosition(){
        return mMediaPlayer.getCurrentPosition();
    }

    public int getDuration(){
        return mMediaPlayer.getDuration();
    }

    public void seekTo(int progress){
        mMediaPlayer.seekTo(progress);
    }

    public interface OnMediaHelperListener {
        //音乐准备好之后调用
        void onPrepared(MediaPlayer mp);
        //音乐暂停状态
        void onPauseState();
        //音乐播放状态
        void onPlayingState();
    }

}

2.首先,要实现播放器显示在通知栏里面,毫无疑问,要创建通知,以前台服务的形式显示
<1>创建通知:

package com.example.jackandrose.entities;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

import androidx.core.app.NotificationCompat;

import com.example.jackandrose.R;
import com.example.jackandrose.activities.Main2Activity;
import com.example.jackandrose.activities.MainActivity;


public class NotifyHelper {
    private static NotifyHelper instance;
    private Context mContext;

    public static NotifyHelper getInstance(Context context) {
        if(instance==null){
            instance=new NotifyHelper(context);
        }
        return instance;
    }

    private NotifyHelper(Context context){
        mContext=context;
    }

    public void CreateChannel(String channel_id,CharSequence channel_name,String description){
        //8.0以上版本通知适配
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel=new NotificationChannel(channel_id,channel_name, NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setDescription(description);
            NotificationManager notificationManager=mContext.getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(notificationChannel);
        }
    }

    /**
     * 返回一个前台通知
     * @param channel_id  通知渠道id,注意8.0创建通知的时候渠道id与此要匹配
     * @param musicPicture 数据对象
     * @param remoteViews 自定义通知样式的对象,但是与View不同,不提供findViewById方法,详细建议看看源码和官方文档
     * @return
     */

    public Notification createForeNotification(String channel_id, MusicPicture musicPicture,RemoteViews remoteViews){
        Intent intent=new Intent(mContext, MainActivity.class);
        PendingIntent mainIntent=PendingIntent.getActivity(mContext,0,intent,0);
        NotificationCompat.Builder builder=new NotificationCompat.Builder(mContext,channel_id)
                .setSmallIcon(R.mipmap.qqyinyue)
                .setStyle(new NotificationCompat.DecoratedCustomViewStyle()) 
                .setCustomBigContentView(remoteViews)
                .setContentIntent(mainIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT);
        return builder.build();
    }
}

细心的朋友肯定发现了,我构建通知的时候picture对象没有使用到,我也不想改了,就放着吧~
需要注意的是,RemoteViews不支持部分布局和控件,ConstraintLayout,用于显示音乐进度的SeekBar,还有各种第三方库,比如CircleImageView,这些都不被支持,反正我刚开始写出来的布局,通知界面便没有正确显示。
<2>布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cl_music_player"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/myActionBarColor"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iv_music_icon"
        android:layout_width="@dimen/myBigMusicIconSize"
        android:layout_height="@dimen/myBigMusicIconSize"
        android:layout_gravity="center"
        android:scaleType="fitXY"
        android:layout_marginLeft="16dp"
        android:src="@mipmap/qqyinyue" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            >
            <ImageView
                android:id="@+id/iv_music_last_song"
                android:layout_width="@dimen/myMusicIconSize"
                android:layout_height="@dimen/myMusicIconSize"
                android:layout_margin="16dp"
                android:layout_gravity="left|center_vertical"
                android:src="@mipmap/music_last" />

            <ImageView
                android:id="@+id/iv_music_play"
                android:layout_width="@dimen/myIconSize"
                android:layout_height="@dimen/myIconSize"
                android:layout_margin="16dp"
                android:src="@mipmap/play_music"
                android:layout_gravity="center"/>

            <ImageView
                android:id="@+id/iv_music_next_song"
                android:layout_width="@dimen/myMusicIconSize"
                android:layout_height="@dimen/myMusicIconSize"
                android:layout_margin="16dp"
                android:src="@mipmap/music_next"
                android:layout_gravity="right|center_vertical"/>
        </FrameLayout>
    </LinearLayout>
</LinearLayout>

<3>创建主服务,用于显示播放器和控制整个音乐播放的服务,并且这里面实现了音乐播放帮助类的外放接口:

package com.example.jackandrose.services;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import android.widget.RemoteViews;

import com.example.jackandrose.R;

import com.example.jackandrose.broadcasts.MyBroadCastReceiver;
import com.example.jackandrose.entities.MediaHelper;
import com.example.jackandrose.entities.MusicPicture;
import com.example.jackandrose.entities.NotifyHelper;

public class MyService extends Service {

    /**
     * 此服务,用于展示音乐播放器,并实现音乐播放
     * 在此说明一下我们音乐播放器的实现
     * 1.  整体界面使用通知实现前台服务
     * 2. 上面的图片按钮操作选择了另外的Service和BroadCast
     * 3. 操作方面其实后台的话,服务和广播都是不错的选择
     * 4. 切换歌曲我选择了广播,暂停和继续播放我选择了另外的服务
     */

    private static final String CHANNEL_ID = "music_channel_id";
    private static final String CHANNEL_NAME = "music_channel_name";
    private static final String CHANNEL_DESCRIPTION = "music_channel_description";
    private static final int NOTIFY_ID = 0x1;
    private NotifyHelper mNotifyHelper;
    private MediaHelper mMediaHelper;
    private RemoteViews notifyLayout;
    private MyBroadCastReceiver mMyBroadCastReceiver;
    private static final boolean MODE_PLAY = true;
    private static final boolean MODE_PAUSE = false;
    private boolean flag;

    @Override
    public void onCreate() {
        super.onCreate();
        mMediaHelper = MediaHelper.getInstance(this);
        mNotifyHelper = NotifyHelper.getInstance(this);
        notifyLayout = new RemoteViews(MyService.this.getPackageName(), R.layout.player_layout);
        mMyBroadCastReceiver=MyBroadCastReceiver.getInstance();
        flag=MODE_PAUSE;
        mRegistBroadCast();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mMyBroadCastReceiver);
    }

    /**
     * 动态注册广播,因为8.0版本以后静态注册可能不起作用
     */

    private void mRegistBroadCast() {
        IntentFilter intentFilter=new IntentFilter();
        intentFilter.addAction("com.example.jackandrose.broadcasts.PLAY_LAST");
        intentFilter.addAction("com.example.jackandrose.broadcasts.PLAY_NEXT");
        registerReceiver(mMyBroadCastReceiver,intentFilter);
    }

    public MyService() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return new MyBinder();
    }

    public class MyBinder extends Binder {

        MyBinder() {
            super();
        }

        public MyService getService() {
            return MyService.this;
        }

        public void setMusic(final MusicPicture picture) {
            mMediaHelper.setmOnMediaHelperListener(new MediaHelper.OnMediaHelperListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    flag=MODE_PAUSE;
                    playMusicState(picture);
                    mMediaHelper.start();
                }

                @Override
                public void onPauseState() {
                    pauseMusicState(picture);
                }

                @Override
                public void onPlayingState() {
                    playMusicState(picture);
                }

            });
            changeMusic();
            mMediaHelper.setRawFile(picture.getPic_resource());
        }

        private void changeMusic() {
            /**
             * 发送广播,进行切歌到上一首操作
             */
            Intent lastIntent=new Intent("com.example.jackandrose.broadcasts.PLAY_LAST");
            PendingIntent lastPendingIntent=PendingIntent.getBroadcast(MyService.this,0,lastIntent,0);
            notifyLayout.setOnClickPendingIntent(R.id.iv_music_last_song,lastPendingIntent);
            /**
             * 发送广播,进行切歌到下一首操作
             */
            Intent nextIntent=new Intent("com.example.jackandrose.broadcasts.PLAY_NEXT");
            PendingIntent nextPendingIntent=PendingIntent.getBroadcast(MyService.this,0,nextIntent,0);
            notifyLayout.setOnClickPendingIntent(R.id.iv_music_next_song,nextPendingIntent);

        }

        /**
         * 更改了相关设置,比如notifyLayout布局显示之后,需要重新发送前台通知来更新UI
         * 太坑了,居然是这样的
         *  此外,由于我们是显示成一个播放器,因此通知id,使用固定id,就可以保证每次更新之后是同一个通知。
         * @param picture 设置播放器图标为指定音乐图标,此对象属于MVC开发模式的model层数据
         */
        private void mstartForeground(MusicPicture picture) {
            mNotifyHelper.CreateChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_DESCRIPTION);
            final Notification notification = mNotifyHelper.createForeNotification(CHANNEL_ID, picture, notifyLayout);
            startForeground(NOTIFY_ID, notification);
        }

        /**
         * 处于播放状态下的音乐应该具有的一些配置
         * @param picture
         */
        private void playMusicState(MusicPicture picture) {
            if(flag==MODE_PLAY) return;
            flag=MODE_PLAY;
            Intent pauseIntent=new Intent(MyService.this,PauseService.class);
            PendingIntent pausePendingIntent=PendingIntent.getService(MyService.this,0,pauseIntent,0);
            notifyLayout.setOnClickPendingIntent(R.id.iv_music_play,pausePendingIntent);
            notifyLayout.setImageViewResource(R.id.iv_music_icon, picture.getPic_id());
            notifyLayout.setImageViewResource(R.id.iv_music_play, R.mipmap.pause_music);
            mstartForeground(picture);
        }

        /**
         * 处于暂停状态下的音乐应该具有的一些配置
         * @param picture
         */

        private void pauseMusicState(MusicPicture picture) {
            if(flag==MODE_PAUSE) return;
            flag=MODE_PAUSE;
            Intent playIntent=new Intent(MyService.this,PlayService.class);
            PendingIntent playPendingIntent=PendingIntent.getService(MyService.this,0,playIntent,0);
            notifyLayout.setOnClickPendingIntent(R.id.iv_music_play,playPendingIntent);
            notifyLayout.setImageViewResource(R.id.iv_music_icon, picture.getPic_id());
            notifyLayout.setImageViewResource(R.id.iv_music_play, R.mipmap.play_music);
            mstartForeground(picture);
        }

    }
}

注意看代码注释,我觉得蛮清晰的,该说明的基本都说了哦~
特别注意一下我说坑的地方,就是更新通知栏播放器上面控件设置,必须在通知发送前实现,故我采用更新界面之后,重新发送相同id的一条通知!!!
3.好了,接下来,我们来实现两个服务,和一个广播接收器:
<1>播放与暂停服务:

package com.example.jackandrose.services;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;

import com.example.jackandrose.entities.MediaHelper;

public class PlayService extends Service {
    /**
     * 用于使音乐继续播放的服务
     */

    private MediaHelper mMediaHelper;

    public PlayService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        mMediaHelper=MediaHelper.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mMediaHelper.start();
        stopSelf();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
package com.example.jackandrose.services;

import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;


import com.example.jackandrose.entities.MediaHelper;

public class PauseService extends Service {
    /**
     * 用于使音乐暂停的服务
     */
    private MediaHelper mMediaHelper;

    public PauseService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        mMediaHelper=MediaHelper.getInstance(this);

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mMediaHelper.pause();
        stopSelf();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

这个时候,你就知道我们将音乐播放帮助类接口外放的原因了。我们在主服务里面实现了接口,即实现了播放和暂停状态下的UI界面,所以我们在服务里面只需要放心大胆的使用音乐帮助类的暂停和播放方法就可以了,音乐它们会调用外放的接口方法。
<2>切换歌曲的广播,其实播放和暂停完全也可以用广播实现,但是我为了看起来更加多元化,哈哈哈:

package com.example.jackandrose.broadcasts;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

public class MyBroadCastReceiver extends BroadcastReceiver {
    /**
     * 切换歌曲的广播,这里仍采用单例模式。
     * 原因是我们要将切换歌曲的接口外放到MusAdapter
     */

    private static MyBroadCastReceiver instance;
    private static final String PLAY_LAST="com.example.jackandrose.broadcasts.PLAY_LAST";
    private static final String PLAY_NEXT="com.example.jackandrose.broadcasts.PLAY_NEXT";
    private MyBroadListner mMyBroadListner;

    public static MyBroadCastReceiver getInstance() {
        if(instance==null){
            instance=new MyBroadCastReceiver();
        }
        return instance;
    }

    public void setmMyBroadListner(MyBroadListner mMyBroadListner) {
        this.mMyBroadListner = mMyBroadListner;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction()==null){
            Log.w("MyBroadCastReceiver","yes");
        }else{
            Log.w("MyBroadCastReceiver","no");
        }

        if(PLAY_LAST.equals(intent.getAction())){
            Toast.makeText(context, "即将切换至上一首,若切换失败,请回到界面点击音乐实现切换", Toast.LENGTH_LONG).show();
            if(mMyBroadListner!=null){
                mMyBroadListner.playLast();
            }

        }else if(PLAY_NEXT.equals(intent.getAction())){
            Toast.makeText(context, "即将切换至下一首,若切换失败,请回到界面点击音乐实现切换", Toast.LENGTH_LONG).show();
            if(mMyBroadListner!=null){
                mMyBroadListner.playNext();
            }
        }
    }

    /**
     * 定义外放接口,在MusAdapter类中我进行了实现
     */
    public interface MyBroadListner{
        void playLast();
        void playNext();
    }
}

注意一下,广播这个东西,8.0以上也有变化,静态注册可能不起作用,因此我在MyService的代码里进行了动态注册,为了保险起见,我们静态也加上:

<receiver android:name=".broadcasts.MyBroadCastReceiver">
            <intent-filter>
                <action android:name="com.example.jackandrose.broadcasts.PLAY_LAST" />
                <action android:name="com.example.jackandrose.broadcasts.PLAY_NEXT" />
            </intent-filter>
        </receiver>

4.广播接收器的代码注释提到了MusAdapter,没错了,这里便是音乐播放的启动器,我们是单击RecyclerView单个条目实现播放的,并设置选中条目,单击选中条目则不会播放。
实现广播接收器的外放接口!!!
代码还是给出来吧:

package com.example.jackandrose.adapters;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.jackandrose.R;
import com.example.jackandrose.broadcasts.MyBroadCastReceiver;
import com.example.jackandrose.entities.MusicPicture;
import com.example.jackandrose.services.MyService;

import java.util.ArrayList;

public class MusAdapter extends RecyclerView.Adapter<MusAdapter.ViewHolder> {

    private static final String TAG = "MusAdapter";
    private ArrayList<MusicPicture> myPictures;
    private Context mContext;
    private int selectedPosition = -5;
    private int mResID=-5;
    private MyService.MyBinder mMyBinder;
    private MyService mMyService;
    private boolean isBind;
    private Intent mServiceIntent;
    private MusicPicture mMusicPicture;
    private MyBroadCastReceiver mMyBroadCastReceiver;

    class ViewHolder extends RecyclerView.ViewHolder {
        ImageView picImg;
        TextView picText;
        LinearLayout box;
        int d_id; //音乐图片id
        int d_resource;  //音乐资源id
        View view;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            picImg = (ImageView) itemView.findViewById(R.id.pic_image);
            picText = (TextView) itemView.findViewById(R.id.pic_text);
            box = itemView.findViewById(R.id.item_box);
            view = itemView;
        }
    }

    public MusAdapter(ArrayList<MusicPicture> pictures,Context context) {
        myPictures = pictures;
        mContext=context;
        mMyBroadCastReceiver=MyBroadCastReceiver.getInstance();
        isBind=false;
    }

    private void setMyBroadCastListener() {
        /**
         * 此处仍然来实现单例对象的接口对象
         * 即实现歌曲的切换,实现广播接收器的外放接口
         */
        mMyBroadCastReceiver.setmMyBroadListner(new MyBroadCastReceiver.MyBroadListner() {
            @Override
            public void playLast() {
                MusAdapter.this.playLast();
            }

            @Override
            public void playNext() {
                MusAdapter.this.playNext();
            }
        });
    }

    private void playLast() {
        if (selectedPosition > 0) {
            setSelectedIndex(selectedPosition - 1);
        }
    }

    private void playNext() {
        if (selectedPosition < getItemCount() - 1) {
            setSelectedIndex(selectedPosition + 1);
        }
    }

    private void stateChange(ViewHolder holder, int position,MusicPicture picture) {
        if (selectedPosition == position) {
            setMyBroadCastListener();
            //不管怎么样,先把状态改变效果展示出来
            int color = holder.view.getContext().getResources().getColor(R.color.myActionBarColor);
            holder.box.setBackgroundColor(color);
            holder.picText.setTextColor(Color.WHITE);

            mMusicPicture =picture;

            if(picture.getPic_resource()==mResID&&mResID!=-5){
                //相同音乐id或者且不是第一次播放,就直接返回
                return;
            }

            mResID=picture.getPic_resource();

            //启动服务来播放
            if(mServiceIntent==null){
                mServiceIntent=new Intent(mContext,MyService.class);
                mContext.startService(mServiceIntent);
            }

            //每次切歌需要重新绑定服务
            destroy();
            if(!isBind){
                mContext.bindService(mServiceIntent,connection,Context.BIND_AUTO_CREATE);
                isBind=true;
            }
        } else {
            holder.box.setBackgroundColor(Color.WHITE);
            holder.picText.setTextColor(Color.BLACK);
            //因为我们从始至终只有一个显示图片的imageview,所以不应该写下面这行代码
        }
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        MusicPicture musicPicture = myPictures.get(position);
        holder.picImg.setImageResource(musicPicture.getPic_id());
        holder.picText.setText(musicPicture.getPic_name());
        holder.d_id = musicPicture.getPic_id();
        holder.d_resource = musicPicture.getPic_resource();
        setClick(holder, position);
        stateChange(holder, position,musicPicture);
    }

    @Override
    public int getItemCount() {
        return myPictures.size();
    }

    private void setClick(ViewHolder holder, final int position) {
        holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setSelectedIndex(position);
            }
        });
    }

    private void setSelectedIndex(int position) {
        selectedPosition = position;
        notifyItemChanged(position);
        notifyDataSetChanged();
    }

    private void destroy(){
        if(isBind){
            mContext.unbindService(connection);
            isBind=false;
        }
    }

    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMyBinder= (MyService.MyBinder) service;
            mMyService=mMyBinder.getService();
            mMyBinder.setMusic(mMusicPicture);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
}

MusicPicture类就不再展示了,就是标准的数据类,包含get和set方法~

写的有些乱,不好的地方提提意见,不要喷我,毕竟我还是个新手,谢谢。

5.效果图展示:

<1>音乐条目界面:

android 播放器 通知栏 通知栏音乐播放器_服务


<2>音乐播放界面:

android 播放器 通知栏 通知栏音乐播放器_android 播放器 通知栏_02


6.好了,到这里就结束了,不过其实bug就是退出界面后,切歌功能实效,其实不难理解为什么,但是实在不知道如何解决,或者说直接换一种实现,避免这个问题。