Android | 音乐播放器(版本4)

一、功能

  •  将手机内的音乐展示到播放界面
  • 点击播放界面的任一歌曲,能够进行播放,同时展示到底部导航栏上
  • 将程序转入后台,仍然能够播放
  • 在通知栏上显示当前播放音乐信息
  • 能够对音乐进行暂停和继续
  • 在导航栏上能够显示当前播放进度,即进度条

二、源代码

1.布局文件

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

<!--音乐-->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/navigation"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <ListView
            android:id="@+id/lv_playlist"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:divider="@android:color/transparent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

<!--底部导航-->
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

  • list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<!--标号-->
    <TextView
        android:id="@+id/tv_order"
        android:text="1"
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginStart="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
<!--歌名-->
    <TextView
        android:id="@+id/tv_title"
        android:textColor="@color/purple_700"
        android:textSize="18sp"
        android:layout_marginTop="8dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="8dp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/tv_order"/>
<!--作者-->
    <TextView
        android:id="@+id/tv_artist"
        android:text="Artist name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@id/tv_title"
        app:layout_constraintStart_toStartOf="@id/tv_title"/>
<!--分隔栏-->
    <View
        android:id="@+id/divider"
        android:layout_width="340dp"
        android:layout_height="1dp"
        android:layout_marginTop="8dp"
        android:background="@color/colorDivider"
        app:layout_constraintStart_toStartOf="@id/tv_title"
        app:layout_constraintTop_toBottomOf="@id/tv_artist"/>

</androidx.constraintlayout.widget.ConstraintLayout>

  • bottom_media_toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--播放进度-->
    <ProgressBar
        android:id="@+id/progress"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="0dp"
        android:layout_height="3dp"
        android:progress="0"
        android:progressBackgroundTint="@android:color/transparent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="@id/tv_bottom_title"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!--专辑封面图-->
    <ImageView
        android:id="@+id/iv_thumbnail"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginStart="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <!--歌曲名-->
    <TextView
        android:id="@+id/tv_bottom_title"
        android:text="Title"
        android:textSize="14sp"
        android:textColor="@color/colorPrimary"
        android:layout_marginTop="4dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="56dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/iv_thumbnail" />

    <!--歌手名-->
    <TextView
        android:id="@+id/tv_bottom_artist"
        android:text="Artist name"
        android:textSize="12sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="4dp"
        app:layout_constraintTop_toBottomOf="@id/tv_bottom_title"
        app:layout_constraintStart_toStartOf="@id/tv_bottom_title"/>

    <!--播放控制图标-->
    <ImageView
        android:id="@+id/iv_play"
        android:src="@drawable/ic_baseline_pause_circle_outline_24"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:clickable="true"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout >

2.资源文件

  • colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
    <color name="colorDivider">#4DAAA9A9</color>
    <color name="colorPrimary">#FF000000</color>
</resources>

  • ic_baseline_pause_circle_outline_24.xml
<vector android:height="24dp" android:tint="#000000"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
</vector>

  • ic_baseline_play_circle_outline_24.xml
<vector android:height="24dp" android:tint="#000000"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

3.Java程序

  • MainActivity.java
package com.example.musiclist;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import java.io.IOException;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {


    /* 底部导航组件 */
    /**
     * 底部导航视图
     */
    private BottomNavigationView bottomNavigation;
    /**
     * 歌曲名
     */
    private TextView tvBottomTitle;
    /**
     * 歌手名
     */
    private TextView tvBottomArtist;
    /**
     * 专辑封面图
     */
    private ImageView ivAlbumThumbnail;
    /**
     * 播放按钮图标
     */
    private ImageView ivPlay;
    /**
     * 播放状态
     */
    private Boolean ivPlayStatus = true;
    /**
     * 播放进度条
     */
    private ProgressBar pbProgress;

    /* 媒体播放器 MediaPlayer */
    /**
     * 媒体播放器
     */
    private final MediaPlayer mMediaPlayer = null;

    /* 内容解析器 ContentResolver */
    /**
     * 内容解析器
     */
    private ContentResolver mContentResolver;
    /**
     * 选择语句where子句
     */
    private final String SELECTION = MediaStore.Audio.Media.IS_MUSIC + " = ? " + " AND " + MediaStore.Audio.Media.MIME_TYPE + " LIKE ? ";
    /**
     * where子句参数
     */
    private final String[] SELECTION_ARGS = {Integer.toString(1), "audio/mpeg"};
    /**
     * 请求外部存储
     */
    private final int REQUEST_EXTERNAL_STORAGE = 1;
    /**
     * 权限存储
     */
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,  // 读取外部存储
            Manifest.permission.WRITE_EXTERNAL_STORAGE  // 写入外部存储
    };

    /* 媒体光标适配器 MediaCursorAdapter */
    /**
     * 列表显示
     */
    private ListView mPlaylist;
    /**
     * 媒体光标适配器
     */
    private MediaCursorAdapter mCursorAdapter;

    /* 后台服务 相关常量 */
    /**
     * 日期 URI
     */
    public static final String DATA_URI = "com.glriverside.xgqin.musiclist.DATA_URI";
    /**
     * 歌名
     */
    public static final String TITLE = "com.glriverside.xgqin.musiclist.TITLE";
    /**
     * 歌手
     */
    public static final String ARTIST = "com.glriverside.xgqin.musiclist.ARTIST";

    /* 监听器 */
    /**
     * 歌曲界面事件监听器
     */
    private final ListView.OnItemClickListener mPlaylistClickListener = new ListView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> arg0, View arg1, int i, long l) {

            Cursor cursor = mCursorAdapter.getCursor();
            if (cursor != null && cursor.moveToPosition(i)) { // cursor.moveToPosition(i) 移动到指定行
                /* 获取索引 */
                int titleIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
                int artistIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
                int albumIdIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
                int dataIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);

                /* 获取资源 */
                String title = cursor.getString(titleIndex);
                String artist = cursor.getString(artistIndex);
                long albumId = cursor.getLong(albumIdIndex);
                String data = cursor.getString(dataIndex);

                /* 启动前台服务播放音乐 */
                Intent serviceIntent = new Intent(MainActivity.this, MusicService.class);
                serviceIntent.putExtra(MainActivity.DATA_URI, data);
                serviceIntent.putExtra(MainActivity.TITLE, title);
                serviceIntent.putExtra(MainActivity.ARTIST, artist);
                startService(serviceIntent);

                /* 设置底部导航栏开始 不可见 */
                bottomNavigation.setVisibility(View.VISIBLE);

                /* 将 歌名 绑定到控件上 */
                if (tvBottomTitle != null) tvBottomTitle.setText(title);
                /* 将 歌手 绑定到控件上 */
                if (tvBottomArtist != null) tvBottomArtist.setText(artist);
                /* 将 封面 绑定到控件上 */
                Uri albumUri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId); // 查询
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    try {
                        Bitmap album = mContentResolver.loadThumbnail(albumUri, new Size(640, 480), null);
                        ivAlbumThumbnail.setImageBitmap(album);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };
    /**
     * 播放按钮单击 监听器
     */
    private final View.OnClickListener ivPlayButtonClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (view.getId() == R.id.iv_play) {
                ivPlayStatus = !ivPlayStatus;
                if (ivPlayStatus) {
                    mService.play();
                    ivPlay.setImageResource(R.drawable.ic_baseline_pause_circle_outline_24);
                } else {
                    mService.pause();
                    ivPlay.setImageResource(R.drawable.ic_baseline_play_circle_outline_24);
                }
            }
        }
    };


    /* 绑定MusicService服务 */
    /**
     * 音乐服务类实例
     */
    private MusicService mService;
    /**
     * ServiceConnection接口对象
     */
    private final ServiceConnection mConn = new ServiceConnection() {
        /**
         * 对mService进行初始化
         * @param componentName 组件名称
         * @param iBinder MusicService的onBind()方法返回的IBinder对象
         */
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            MusicService.MusicServiceBinder binder = (MusicService.MusicServiceBinder) iBinder;
            mService = binder.getService();
        }

        /**
         * 对mService进行初始化
         * @param componentName 组件名称
         */
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
        }
    };


    /* 更新音乐播放进度状态 */
    /**
     * 用于区分Message的类别
     */
    public static final int UPDATE_PROGRESS = 1;
    /**
     * 子线程与主线程间的数据传递
     */
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        public void handleMessage(Message msg) {
            if (msg.what == UPDATE_PROGRESS) {
                int position = msg.arg1;
                pbProgress.setProgress(position);
            }
        }
    };
    /**
     * 实现Runnable接口,可在Thread线程类中进行运行.主要是简化项目功能实现工程
     */
    private class MusicProgressRunnable implements Runnable {
        public MusicProgressRunnable() {
        }

        @Override
        public void run() {
            boolean mThreadWorking = true;
            while (mThreadWorking) {
                try {
                    if (mService != null) {
                        int position = mService.getCurrentPosition();
                        Message message = new Message();
                        message.what = UPDATE_PROGRESS;
                        message.arg1 = position;
                        mHandler.sendMessage(message);
                    }
                    assert mService != null;
                    mThreadWorking = mService.isPlaying();
                    Thread.sleep(100);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }


    /* 自定义MusicReceiver广播 */
    /**
     * 音乐开始
     */
    public static final String ACTION_MUSIC_START = "com.glriverside.xgqin.ggmusic.ACTION_MUSIC_START";
    /**
     * 音乐停止
     */
    public static final String ACTION_MUSIC_STOP = "com.glriverside.xgqin.ggmusic.ACTION_MUSIC_STOP";
    /**
     * 内部类 MusicReceiver
     */
    public class MusicReceiver extends BroadcastReceiver {
        /**
         * 当音乐开始播放后,开启MusicProgressRunnable线程执行查询音乐播放状态
         * MusicService服务在MediaPlayer类完成prepare()、start()方法调用后发送该广播,
         * 从而实现MusicService所在线程与MusicProgressRunnable所在线程同步
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            if (mService != null) {
                pbProgress.setMax(mService.getDuration());
                new Thread(new MusicProgressRunnable()).start();
            }
        }
    }
    /**
     * 内部类 MusicReceiver 实例
     */
    private MusicReceiver musicReceiver;




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 实例化相关对象
        mContentResolver = getContentResolver();
        mCursorAdapter = new MediaCursorAdapter(MainActivity.this);

        // 绑定 ListView
        mPlaylist = findViewById(R.id.lv_playlist);

        // 设置适配器
        mPlaylist.setAdapter(mCursorAdapter);

        // 绑定 BottomNavigationView
        bottomNavigation = findViewById(R.id.navigation); // 拿到主布局的底部导航栏对象
        LayoutInflater.from(MainActivity.this).inflate(R.layout.bottom_media_toolbar, bottomNavigation, true); // 把 bottom_media_toolbar 放到主布局的底部导航栏中

        // 让底部导航栏一开始消失的
        bottomNavigation.setVisibility(View.GONE);

        // 绑定 BottomNavigationView 其他控件
        tvBottomTitle = bottomNavigation.findViewById(R.id.tv_bottom_title);
        tvBottomArtist = bottomNavigation.findViewById(R.id.tv_bottom_artist);
        ivAlbumThumbnail = bottomNavigation.findViewById(R.id.iv_thumbnail);
        pbProgress = bottomNavigation.findViewById(R.id.progress);
        ivPlay = bottomNavigation.findViewById(R.id.iv_play);

        // 查询权限,判断第一次打开APP时是否点了是
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE))
                requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); // 如果第一次点了是,下次不会弹出; 如果点了否,下次还会弹出
        } else initPlaylist();

        // 设置监听器
        if (ivPlay != null) ivPlay.setOnClickListener(ivPlayButtonClickListener);
        mPlaylist.setOnItemClickListener(mPlaylistClickListener);

        // 通过 registerReceiver 注册 MusicReceiver 广播
        musicReceiver = new MusicReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_MUSIC_START);
        intentFilter.addAction(ACTION_MUSIC_STOP);
        registerReceiver(musicReceiver, intentFilter);
    }

    /**
     * 权限管理
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_EXTERNAL_STORAGE) {// 判断授权结果
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                initPlaylist();
        }
    }

    /**
     * 播放、暂停按钮 点击事件触发器
     */
    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.iv_play) {
            ivPlayStatus = !ivPlayStatus;
            if (ivPlayStatus) {
                mService.play();
                ivPlay.setImageResource(R.drawable.ic_baseline_pause_circle_outline_24);
            } else {
                mService.pause();
                ivPlay.setImageResource(R.drawable.ic_baseline_play_circle_outline_24);
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(MainActivity.this, MusicService.class);
        // Intent - 表示绑定服务的Intent对象,与startService()方法中的Intent对象相同
        // ServiceConnection - 表示服务连接接口,在绑定与解绑服务时,接口的相应方法被回调
        // int - 表示绑定服务的动作行为,在本例中使用BIND_AUTO_CREATE,表示自动创建服务
        bindService(intent, mConn, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        unbindService(mConn);
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(musicReceiver);
        super.onDestroy();
    }

    /**
     * 查询MP3数据
     */
    private void initPlaylist() {
        /* 游标对象查询MP3数据 */
        Cursor mCursor = mContentResolver.query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, // 表名
                null,   // 所有列
                SELECTION,        // where子句
                SELECTION_ARGS,   // where参数
                MediaStore.Audio.Media.DEFAULT_SORT_ORDER // 默认排序方式
        );
        mCursorAdapter.swapCursor(mCursor);
        mCursorAdapter.notifyDataSetChanged();
    }
}

  • MediaCursorAdapter.java
package com.example.musiclist;

import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;

public class MediaCursorAdapter extends CursorAdapter {

    private final Context mContext;

    private final LayoutInflater mLayoutInflater;

    public MediaCursorAdapter(Context context) {
        super(context, null, 0);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(mContext);
    }

    public static class ViewHolder {
        TextView tvTitle;
        TextView tvArtist;
        TextView tvOrder;
        View divider;
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
        // 直接写死
        View view = mLayoutInflater.inflate(R.layout.list_item, viewGroup, false);
        if (view != null) {
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.tvTitle = view.findViewById(R.id.tv_title);
            viewHolder.tvArtist = view.findViewById(R.id.tv_artist);
            viewHolder.tvOrder = view.findViewById(R.id.tv_order);
            viewHolder.divider = view.findViewById(R.id.divider);
            view.setTag(viewHolder);
            return view;
        }
        return null;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ViewHolder viewHolder = (ViewHolder) view.getTag();

        int titleIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
        int artistIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);

        String title = cursor.getString(titleIndex);
        String artist = cursor.getString(artistIndex);

        int position = cursor.getPosition();

        if (viewHolder != null) {
            viewHolder.tvTitle.setText(title);
            viewHolder.tvArtist.setText(artist);
            viewHolder.tvOrder.setText(Integer.toString(position + 1));
        }
    }
}

  • MusicService.java
package com.example.musiclist;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

import java.io.IOException;


public class MusicService extends Service {
    /**
     * 媒体播放器
     */
    private MediaPlayer mMediaPlayer;

    /* 前台服务 相关属性 */
    /**
     * 通知 ID
     */
    private static final int ONGOING_NOTIFICATION_ID = 1001;
    /**
     * 频道编号
     */
    private static final String CHANNEL_ID = "Music channel";
    /**
     * 通知管理器
     */
    NotificationManager mNotificationManager;

    /* 内部类 MusicServiceBinder */
    /**
     * 内部类,提供访问 MusicService 实例的可能
     */
    public class MusicServiceBinder extends Binder {
        /**
         * 内部类可访问其外部类的实例对象及定义的成员及方法。因此在MusicServiceBinder类中,
         * 定义了名为getService的方法,该方法返回当前MusicService类实例,从而为其他组件提
         * 供了访问MusicService实例的可能
         */
        MusicService getService() {
            return MusicService.this;
        }
    }
    /**
     * 内部类实例
     */
    private final IBinder mBinder = new MusicServiceBinder();


    /**
     * 构造方法
     */
    public MusicService() {
    }

    /**
     * 响应startService()方法的回调方法
     *
     * @param intent  由startService()方法传递过来的Intent对象,可以使用它获取其他组件传递过来的数据
     * @param flags   与特定服务请求相关的额外数据,其值是0或是 STARTFLAG REDELIVERY或STAR_FLAG_RETRY的组合
     * @param startId 唯一的整型id,表示特定的一次startService()请求,可以在stopSelf(int)中使用,指定停止特定的一次服务
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    // 前台服务
        String title = intent.getStringExtra(MainActivity.TITLE);
        String artist = intent.getStringExtra(MainActivity.ARTIST);
        String data = intent.getStringExtra(MainActivity.DATA_URI);
        Uri dataUri = Uri.parse(data);

        // 判断版本是否在 Android Oreo 以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Music Channel", NotificationManager.IMPORTANCE_HIGH);
            if (mNotificationManager != null)
                mNotificationManager.createNotificationChannel(channel);
        }

        // 固定
        Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);

        // 通知栏样式配置
        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
        Notification notification = builder
                .setContentTitle(title) // 标题
                .setContentText(artist) // 正文文本
                .setSmallIcon(R.drawable.ic_launcher_foreground) // 图标
                .setContentIntent(pendingIntent).build(); // 点击通知时的延迟意图

        startForeground(ONGOING_NOTIFICATION_ID, notification);

    // 发送广播
        if (mMediaPlayer != null) {
            try {
                mMediaPlayer.reset();
                mMediaPlayer.setDataSource(getApplicationContext(), dataUri);
                mMediaPlayer.prepare();
                mMediaPlayer.start();
                // 广播发射器
                Intent musicStartIntent = new Intent(MainActivity.ACTION_MUSIC_START);
                sendBroadcast(musicStartIntent);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        return super.onStartCommand(intent, flags, startId);
    }


    /**
     * 服务被创建时执行的方法
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mMediaPlayer = new MediaPlayer(); // 初始化操作
    }

    /**
     * 服务结束时执行的方法
     */
    @Override
    public void onDestroy() {
        mMediaPlayer.stop();
        mMediaPlayer.release(); // 释放操作
        mMediaPlayer = null;
        super.onDestroy();
    }

    /**
     * 定义并返回IBinder接口
     *
     * @return IBinder接口
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

/* 定义公有方法并通过 IBinder 接口实现 */
    /**
     * 暂停MediaPlayer音乐播放
     */
    public void pause() {
        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) mMediaPlayer.pause();
    }

    /**
     * 继续MediaPlayer音乐播放
     */
    public void play() {
        if (mMediaPlayer != null) mMediaPlayer.start();
    }

    /**
     * 获取当前播放音乐总时长
     * @return int 总时长 duration
     */
    public int getDuration() {
        int duration = 0;
        if (mMediaPlayer != null) duration = mMediaPlayer.getDuration();
        return duration;
    }

    /**
     * 获取当前音乐播放进度
     * @return int 播放进度 position
     */
    public int getCurrentPosition() {
        int position = 0;
        if (mMediaPlayer != null) position = mMediaPlayer.getCurrentPosition();
        return position;
    }

    /**
     * 获取MediaPlayer音乐播放状态
     * @return boolean 播放状态
     */
    public boolean isPlaying() {
        if (mMediaPlayer != null) return mMediaPlayer.isPlaying();
        return false;
    }
}

4.配置文件

  • AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.musiclist">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"
        tools:targetApi="31">
        <service
            android:name=".MusicService"
            android:enabled="true"
            android:exported="false"></service>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

三、效果

  • 播放

  •  暂停

  • 状态栏

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值