一、功能
- 将手机内的音乐展示到播放界面
- 点击播放界面的任一歌曲,能够进行播放,同时展示到底部导航栏上
- 将程序转入后台,仍然能够播放
- 在通知栏上显示当前播放音乐信息
- 能够对音乐进行暂停和继续
- 在导航栏上能够显示当前播放进度,即进度条
二、源代码
1.布局文件
<?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>
<?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>
<?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.资源文件
<?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>
![](https://img-blog.csdnimg.cn/eb66b9208d0247a297e45abbf888975e.png)
3.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();
}
}
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));
}
}
}
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.配置文件
<?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>
三、效果
![](https://img-blog.csdnimg.cn/ce30fa3cc2b841448edc82e046000bbe.png)
![](https://img-blog.csdnimg.cn/5c526feb67c74fc9bd425c3a12bfa44e.png)
![](https://img-blog.csdnimg.cn/2f6e975090794474a84ec3169f969ca7.png)