目录
- 一,先看效果图
- 二,实现方式
做音乐播放器,必然要用到通知栏,由于通知栏很多版本都有改动,一些厂商也做了调整,适配起来比较麻烦,能用系统自带的就用。
这里分享一下系统媒体通知栏的适配。
需要考虑的问题如下:
1,通知栏适配,音乐播放需要常驻,所以要维护一个通知栏。
2,音控处理,在安卓7.0及以下,通过MediaSessionCompat可控制锁屏页音乐播放。
3,对于耳机的处理,不管是线耳机还是蓝牙耳机,耳机控制播放暂停,下一曲上一曲等操作。
4,打电话处理,在听音乐的同时如果电话进来后挂断,希望可以自动播放。
5,音频播放焦点处理,如果有别的应用抢占焦点可进行暂停播放。还有就是进入APP时想拥有音频焦点,都可以通过AudioManager进行处理。
一,先看效果图
华为MatePad11 系统鸿蒙3.0
华为HONOR Pad 6 系统鸿蒙2.0
小米 NOTE PRO 系统7.0
华为Mate 8 系统8.0
魅族6T 系统7.0
锤子 系统11
OPPO 系统12
在系统7.0锁屏页效果
二,实现方式
创建通知管理类NotifyBuilderManager代码如下:
package com.idujing.myapplication.manager; | |
import android.annotation.SuppressLint; | |
import android.app.Notification; | |
import android.app.NotificationChannel; | |
import android.app.NotificationManager; | |
import android.app.PendingIntent; | |
import android.app.Service; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.os.Build; | |
import androidx.core.app.NotificationCompat; | |
import com.idujing.myapplication.R; | |
/** | |
* 音频播放通知栏管理 | |
*/ | |
public class NotifyBuilderManager { | |
private final String TAG = getClass().getSimpleName(); | |
public static final String ACTION_NEXT = "com.idujing.play.notify.next";// 下一首 | |
public static final String ACTION_PREV = "com.idujing.play.notify.prev";// 上一首 | |
public static final String ACTION_PLAY_PAUSE = "com.idujing.play.notify.play_state";// 播放暂停广播 | |
private static final int NOTIFICATION_ID = 0x123; | |
private Service mContext; | |
private Notification mNotification; | |
private NotificationManager mNotificationManager; | |
private NotificationCompat.Builder mNotificationBuilder; | |
private MediaSessionManager mSessionManager; | |
private PendingIntent mPendingPlay; | |
private PendingIntent mPendingPre; | |
private PendingIntent mPendingNext; | |
private boolean isRunningForeground = false; | |
public boolean isRunningForeground() { | |
return isRunningForeground; | |
} | |
public NotifyBuilderManager(Service context) { | |
this.mContext = context; | |
mSessionManager = new MediaSessionManager(context, null); | |
} | |
/** | |
* 初始化通知栏 | |
*/ | |
private void initNotify() { | |
mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); | |
Class<?> clazz = null; | |
try { | |
clazz = Class.forName("具体的播放器类名"); | |
} catch (ClassNotFoundException e) { | |
e.printStackTrace(); | |
} | |
// 适配12.0及以上 | |
int flag; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | |
flag = PendingIntent.FLAG_IMMUTABLE; | |
} else { | |
flag = PendingIntent.FLAG_UPDATE_CURRENT; | |
} | |
//绑定事件通过创建的具体广播去接收即可。 | |
Intent infoIntent = new Intent(mContext, clazz); | |
PendingIntent pendingInfo = PendingIntent.getActivity(mContext, 0, infoIntent, flag); | |
Intent preIntent = new Intent(); | |
preIntent.setAction(ACTION_PREV); | |
mPendingPre = PendingIntent.getBroadcast(mContext, 1, preIntent, flag); | |
Intent playIntent = new Intent(); | |
playIntent.setAction(ACTION_PLAY_PAUSE); | |
mPendingPlay = PendingIntent.getBroadcast(mContext, 2, playIntent, flag); | |
Intent nextIntent = new Intent(); | |
nextIntent.setAction(ACTION_NEXT); | |
mPendingNext = PendingIntent.getBroadcast(mContext, 3, nextIntent, PendingIntent.FLAG_IMMUTABLE); | |
androidx.media.app.NotificationCompat.MediaStyle style = new androidx.media.app.NotificationCompat.MediaStyle() | |
.setShowActionsInCompactView(0, 1, 2) | |
.setMediaSession(mSessionManager.getMediaSession()); | |
mNotificationBuilder = new NotificationCompat.Builder(mContext, initChannelId()) | |
.setSmallIcon(R.mipmap.ic_launcher) | |
.setPriority(NotificationCompat.PRIORITY_MAX) | |
.setContentIntent(pendingInfo) | |
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | |
.setStyle(style); | |
isRunningForeground = true; | |
} | |
/** | |
* 创建Notification ChannelID | |
* | |
* @return 频道id | |
*/ | |
private String initChannelId() { | |
// 通知渠道的id | |
String id = "music_01"; | |
// 用户可以看到的通知渠道的名字. | |
CharSequence name = mContext.getString(R.string.app_name); | |
// 用户可以看到的通知渠道的描述 | |
String description = "通知栏播放控制"; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
int importance = NotificationManager.IMPORTANCE_LOW; | |
NotificationChannel channel = new NotificationChannel(id, name, importance); | |
channel.setDescription(description); | |
channel.enableLights(false); | |
channel.enableVibration(false); | |
mNotificationManager.createNotificationChannel(channel); | |
} | |
return id; | |
} | |
/** | |
* 取消通知 | |
*/ | |
public void cancelNotification() { | |
if (mNotificationManager != null) { | |
mContext.stopForeground(true); | |
mNotificationManager.cancel(NOTIFICATION_ID); | |
isRunningForeground = false; | |
} | |
} | |
/** | |
* 设置通知栏大图片 | |
*/ | |
private void updateCoverSmall() { | |
Glide.with(mContext).asBitmap() | |
.load(url) | |
.into(new CustomTarget<Bitmap>() { | |
public void onResourceReady(super Bitmap> transition) { Bitmap resource, Transition<? | |
mNotificationBuilder.setLargeIcon(resource); | |
mNotification = mNotificationBuilder.build(); | |
mNotificationManager.notify(NOTIFICATION_ID, mNotification); | |
} | |
public void onLoadCleared( { Drawable placeholder) | |
} | |
public void onLoadFailed( { Drawable errorDrawable) | |
super.onLoadFailed(errorDrawable); | |
Log.e(TAG, "onLoadFailed: "); | |
} | |
}); | |
} | |
/** | |
* 更新状态栏通知 | |
*/ | |
public void updateNotification(boolean isMusicPlaying) { | |
if (mNotification == null) { | |
initNotify(); | |
} | |
mSessionManager.updateMetaData(); | |
if (mNotificationBuilder != null) { | |
int playButtonResId = isMusicPlaying | |
? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play; | |
if (!mNotificationBuilder.mActions.isEmpty()) { | |
mNotificationBuilder.mActions.clear(); | |
} | |
mNotificationBuilder | |
.addAction(android.R.drawable.ic_media_previous, "Previous", mPendingPre) // #0 | |
.addAction(playButtonResId, "Pause", mPendingPlay) // #1 | |
.addAction(android.R.drawable.ic_media_next, "Next", mPendingNext); | |
mNotificationBuilder.setContentTitle("主标题"); | |
mNotificationBuilder.setContentText("副标题"); | |
updateCoverSmall(); | |
mNotification = mNotificationBuilder.build(); | |
mContext.startForeground(NOTIFICATION_ID, mNotification); | |
mNotificationManager.notify(NOTIFICATION_ID, mNotification); | |
} | |
} | |
} |
创建音控管理类MediaSessionManager代码如下:
package com.idujing.myapplication.manager; | |
import android.content.Context; | |
import android.os.Handler; | |
import android.support.v4.media.MediaMetadataCompat; | |
import android.support.v4.media.session.MediaSessionCompat; | |
import android.support.v4.media.session.PlaybackStateCompat; | |
/** | |
* 主要管理Android 5.0以后线控和蓝牙远程控制播放 | |
*/ | |
public class MediaSessionManager { | |
private static final String TAG = "MediaSessionManager"; | |
//指定可以接收的来自锁屏页面的按键信息 | |
private static final long MEDIA_SESSION_ACTIONS = | |
PlaybackStateCompat.ACTION_PLAY | |
| PlaybackStateCompat.ACTION_PAUSE | |
| PlaybackStateCompat.ACTION_PLAY_PAUSE | |
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT | |
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | |
| PlaybackStateCompat.ACTION_STOP | |
| PlaybackStateCompat.ACTION_SEEK_TO; | |
private final Context mContext; | |
private MediaSessionCompat mMediaSession; | |
private Handler mHandler; | |
public MediaSessionManager(Context context, Handler handler) { | |
this.mContext = context; | |
this.mHandler = handler; | |
setupMediaSession(); | |
} | |
/** | |
* 是否在播放 | |
* | |
* @return | |
*/ | |
protected boolean isPlaying() { | |
//具体去实现 | |
return false; | |
} | |
/** | |
* 初始化并激活 MediaSession | |
*/ | |
private void setupMediaSession() { | |
mMediaSession = new MediaSessionCompat(mContext, TAG); | |
//指明支持的按键信息类型 | |
mMediaSession.setFlags( | |
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | | |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS | |
); | |
mMediaSession.setCallback(callback, mHandler); | |
mMediaSession.setActive(true); | |
} | |
/** | |
* 更新正在播放的音乐信息,切换歌曲时调用 | |
*/ | |
public void updateMetaData() { | |
MediaMetadataCompat.Builder metaDta = new MediaMetadataCompat.Builder() | |
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") | |
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist") | |
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Album") | |
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, "Artist") | |
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 100); | |
mMediaSession.setMetadata(metaDta.build()); | |
int state = isPlaying() ? PlaybackStateCompat.STATE_PLAYING : | |
PlaybackStateCompat.STATE_PAUSED; | |
mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder() | |
.setActions(MEDIA_SESSION_ACTIONS) | |
.setState(state, 0, 1) | |
.build()); | |
//锁屏页封面设置,高本版没有效果,因为通知栏权限调整。 | |
Glide.with(mContext).asBitmap(). | |
load(url) | |
.into(new CustomTarget<Bitmap>() { | |
public void onResourceReady(super Bitmap> transition) { Bitmap resource, Transition<? | |
metaDta.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, resource); | |
mMediaSession.setMetadata(metaDta.build()); | |
} | |
public void onLoadCleared() { Drawable placeholder | |
} | |
}); | |
} | |
public MediaSessionCompat.Token getMediaSession() { | |
return mMediaSession.getSessionToken(); | |
} | |
/** | |
* 释放MediaSession,退出播放器时调用 | |
*/ | |
public void release() { | |
mMediaSession.setCallback(null); | |
mMediaSession.setActive(false); | |
mMediaSession.release(); | |
} | |
/** | |
* API 21 以上 耳机多媒体按钮监听 MediaSessionCompat.Callback | |
*/ | |
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() { | |
public void onPlay() { | |
//具体自己实现 | |
} | |
public void onPause() { | |
} | |
public void onSkipToNext() { | |
} | |
public void onSkipToPrevious() { | |
} | |
public void onStop() { | |
} | |
public void onSeekTo(long pos) { | |
} | |
}; | |
} |
创建音频焦点控制类AudioAndFocusManager
通过音频焦点控制,不管是别的应用抢占焦点,还是打电话都可以接收到状态。
package com.idujing.myapplication.manager; | |
import android.content.Context; | |
import android.media.AudioFocusRequest; | |
import android.media.AudioManager; | |
import android.os.Build; | |
import android.util.Log; | |
import androidx.annotation.RequiresApi; | |
/** | |
* Description: 主要用来管理音频焦点 | |
*/ | |
public class AudioAndFocusManager { | |
private static final String TAG = "AudioAndFocusManager"; | |
private AudioManager mAudioManager; | |
public AudioAndFocusManager(Context mContext) { | |
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); | |
} | |
/** | |
* 请求音频焦点 | |
*/ | |
public void requestAudioFocus() { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
AudioFocusRequest mAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) | |
.setOnAudioFocusChangeListener(audioFocusChangeListener) | |
.build(); | |
int res = mAudioManager.requestAudioFocus(mAudioFocusRequest); | |
if (res == 1) { | |
Log.e(TAG, "res=" + true); | |
} | |
} else { | |
if (audioFocusChangeListener != null) { | |
boolean result = AudioManager.AUDIOFOCUS_REQUEST_GRANTED == | |
mAudioManager.requestAudioFocus(audioFocusChangeListener, | |
AudioManager.STREAM_MUSIC, | |
AudioManager.AUDIOFOCUS_GAIN); | |
Log.e(TAG, "requestAudioFocus result=" + result); | |
} | |
} | |
} | |
/** | |
* 关闭音频焦点 | |
*/ | |
public void abandonAudioFocus() { | |
if (audioFocusChangeListener != null) { | |
boolean result = AudioManager.AUDIOFOCUS_REQUEST_GRANTED == | |
mAudioManager.abandonAudioFocus(audioFocusChangeListener); | |
Log.e(TAG, "abandonAudioFocus result=" + result); | |
} | |
} | |
/** | |
* 音频焦点改变监听器 | |
*/ | |
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = focusChange -> { | |
switch (focusChange) { | |
case AudioManager.AUDIOFOCUS_LOSS://失去音频焦点 | |
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://暂时失去焦点 | |
break; | |
case AudioManager.AUDIOFOCUS_GAIN://获取焦点 | |
break; | |
default: | |
} | |
}; | |
} |