最近接到一个需求,如题,给友盟推送添加定制化声音。
描述很简洁,由上图可知,本次需求的重点工作是8.0以上版本的兼容问题。友盟给出的示例代码如下:
由以上代码可知,其实就是设置一个自定义Notification,但是以上代码用在8.0以上系统中并不可行。原因大家应该都清楚了,8.0以上通知栏新增了一个NotificationChannel的特性,如果没有设置channel通知渠道的话,就会导致通知无法展示。
设置本地声音
现在再来回到我们本次需求的重点:定制声音。通过以上分析我们已经知道了友盟自定义声音其实就是自定义通知,那么定制声音也就是自定义通知的一部分,该怎么做呢?通过查找NotificationChannel的API可以找到setSound(Uri sound, AudioAttributes audioAttributes)
方法。那么赶快上代码吧,我们先来设置一个本地声音吧:
//Android 8.0 以上需包添加渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(newChannel, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
Uri sound = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.ding);
//只能在create一个渠道之前修改铃声,在创建之后不支持修改
notificationChannel.setSound(sound, Notification.AUDIO_ATTRIBUTES_DEFAULT);
manager.createNotificationChannel(notificationChannel);
}
注意:
这里要特别注意上述代码中的注释,只能在create一个渠道之前修改铃声,在创建之后不支持修改。
具体原因setSound
方法的注释中已经给出了描述:
声音可配置
看到这里有人可能有疑问了,那我如果要想更改声音,让定制声音可配置,这怎么办呢???
既然一个NotificationChannel只能绑定一个声音,那我们可不可以再新建一个Channel来绑定新的声音呢?答案是可以的。由于之前的声音不再使用,所以我们需要删掉(deleteNotificationChannel(String channelId))之前的NotificationChannel,这里有个坑,你什么时候去删除呢?第一次测试我是在修改铃声或者振动的时候创建一个新的渠道,把之前所有旧的渠道都删除,但是这样会有一个bug,之前渠道上还在状态栏显示的Notification都会删除掉,所有要做一个判断,如果当前渠道在状态栏没有notification显示则删除,否则继续保存,代码如下:
注:
这里补充下,deleteNotificationChannel方法并不是真的删除了该渠道,只是设置了一个删除标记。具体的分析过程可以看下源码PreferencesHelper.deleteNotificationChannel()。
private static void deleteNoNumberNotification(NotificationManager nm, String newChannelId) {
List<NotificationChannel> notificationChannels = nm.getNotificationChannels();
if (Utils.isEmpty(notificationChannels) || notificationChannels.size() < 5) {
return;
}
for (NotificationChannel channel : notificationChannels) {
if (channel.getId() == null || channel.getId().equals(newChannelId)) {
continue;
}
int notificationNumbers = getNotificationNumbers(nm, channel.getId());
Logger.i(TAG, "notificationNumbers: " + notificationNumbers + " channelId:" + channel.getId());
if (notificationNumbers == 0) {
Log.i(TAG, "deleteNoNumberNotification: " + channel.getId());
nm.deleteNotificationChannel(channel.getId());
}
}
}
/**
* 获取某个渠道下状态栏上通知显示个数
*
* @param mNotificationManager NotificationManager
* @param channelId String
* @return int
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static int getNotificationNumbers(NotificationManager mNotificationManager, String channelId) {
if (mNotificationManager == null || TextUtils.isEmpty(channelId)) {
return -1;
}
int numbers = 0;
StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
for (StatusBarNotification item : activeNotifications) {
Notification notification = item.getNotification();
if (notification != null) {
if (channelId.equals(notification.getChannelId())) {
numbers++;
}
}
}
return numbers;
}
以上就是定制本地声音的所有问题了,那么有人又有问题了,我如果想要配置一个网络音频,该如何设置呢???
设置网络音频
参考友盟SDK里面UmengMessageHandler的getSound方法:
如果是网络音频的话,友盟先将音频文件下载到本地缓存目录,在进行设置的。
这里我一开始有个偷懒的想法,既然友盟已经为我们考虑的如此周全了,那我可不可以直接用这个getSound方法呢?通过试验,很遗憾,直接用友盟的方法声音并没有播放出来,这里留个大大的问号给大家,如果有哪位伙伴知道原因可以评论分享出来一起探讨。
既然直接用友盟的消息不可靠那就只有直接动手了,按照友盟提供的思路,将文件下载到本地,再进行设置吧。这里我下载的目录是getExternalCacheDir(),奇怪竟然可以播放( •̀ ω •́ )y。
通知音频播放的一点点分析
这里还要插播一条:在找不到友盟下载的文件为什么不能播放的时候,我还有个偷懒的想法直接将http音频地址设置给Uri,测试发现在小米手机上Https的地址竟然可以播放,Http的地址不可以播放。这里稍微说下吧,其实这并不是一个解决方案,甚至本来就是一个错误的做法。为什么呢?
通知声音的播放是在NotificationPlayer类中实现,看代码:
最终声音的播放还是由MediaPlayer来执行的。之前我们给Notification设置的Sound就是MediaPlayer的DataSource,如果是网络音频,然后被Uri包裹,那么播放器就认为这是一个本地文件,在解析的时候就会报找不到文件错误,很神奇,这里明明报错了,有些手机上你还是可以听到声音的播放。所以并不建议大家直接将Http地址塞给Sound。
由于上面提到的源码部分只是某个点,需要了解更多的同学,请打开源码详细对照理解哈。
本文源码如下:
public static String UMENG_INTERNET_SOUND_DOWNPATH = MyApplication.getInstance().getExternalCacheDir() + "/sound_cache";
//8.0版本以上自定义通知兼容
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mPushAgent.setMessageHandler(messageHandler);
}
//自定义消息进行处理
private static UmengMessageHandler messageHandler = new UmengMessageHandler() {
@Override
public void dealWithNotificationMessage(Context context, UMessage uMessage) {
if (uMessage.hasResourceFromInternet() && !MessageSharedPrefs.getInstance(context).hasMessageResourceDownloaded(uMessage.msg_id)) {
downInternetSound(context, uMessage);
return;
}
super.dealWithNotificationMessage(context, uMessage);
}
@Override
public Uri getSound(Context context, UMessage uMessage) {
return getCustomSound(context, uMessage);
}
@Override
public Notification getNotification(Context context, UMessage uMessage) {
if (uMessage.builder_id == 1) {
long curTime = System.currentTimeMillis();
String CHANNEL_ID = AppUtils.getAppName();//应用频道Id唯一值, 长度若太长可能会被截断,
String newChannel = CHANNEL_ID + curTime;
String CHANNEL_NAME = AppUtils.getAppName();//最长40个字符,太长会被截断
Uri sound = getSound(context, uMessage);
Intent hangIntent = new Intent(context, MainActivity.class);
PendingIntent hangPendingIntent = PendingIntent.getActivity(context, 1001, hangIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(context, newChannel)
.setContentTitle(uMessage.title)
.setContentText(uMessage.text)
.setSmallIcon(R.mipmap.app_logo)
.setContentIntent(hangPendingIntent)
//.setFullScreenIntent(hangPendingIntent,true)
.setSound(sound)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.app_logo))
.setAutoCancel(true)
.build();
//Android 5.0 以上锁屏通知
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.visibility = Notification.VISIBILITY_PUBLIC;
}
//Android 8.0 以上需包添加渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//只能在create一个渠道之前修改铃声,在创建之后不支持修改
//只能去重新创建一个渠道设置铃声振动
//对于之前创建的渠道,通过deleteNotificationChannel(String channelId)去删除
List<NotificationChannel> channelList = manager.getNotificationChannels();
if (channelList != null && channelList.size() > 0) {
for (NotificationChannel channel : channelList) {
if (!TextUtils.isEmpty(channel.getId()) && channel.getId().startsWith(CHANNEL_ID)) {
manager.deleteNotificationChannel(channel.getId());
}
}
}
NotificationChannel notificationChannel = new NotificationChannel(newChannel, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
//只能在create一个渠道之前修改铃声,在创建之后不支持修改
notificationChannel.setSound(sound, Notification.AUDIO_ATTRIBUTES_DEFAULT);
manager.createNotificationChannel(notificationChannel);
}
Log.d("umeng", "notificationDefault: " + getNotificationDefaults(context, uMessage));
return notification;
}
//默认为0,若填写的builder_id并不存在,也使用默认。
return super.getNotification(context, uMessage);
}
};
/**
* 自定义通知声音
*
* @param context
* @param uMessage
* @return
*/
private static Uri getCustomSound(Context context, UMessage uMessage) {
String soundPath = uMessage.sound;
try {
if (soundPath == null) {
int assetsSound = com.umeng.message.common.d.a(context).j("umeng_push_notification_default_sound");
if (assetsSound > 0) {
soundPath = "android.resource://" + context.getPackageName() + "/" + assetsSound;
}
} else {
if (uMessage.isSoundFromInternet()) {
soundPath = UMENG_INTERNET_SOUND_DOWNPATH + "/" + uMessage.sound.hashCode();
} else {
int assetsSound = com.umeng.message.common.d.a(context).j(uMessage.sound);
if (assetsSound > 0) {
soundPath = "android.resource://" + context.getPackageName() + "/" + assetsSound;
}
}
}
if (soundPath != null) {
Uri soundUri = Uri.parse(soundPath);
return soundUri;
}
} catch (Throwable throwable) {
throwable.toString();
}
return null;
}
private static void downInternetSound(Context context, UMessage uMessage) {
String downPath = UMENG_INTERNET_SOUND_DOWNPATH;
String downFileName = uMessage.sound.hashCode() + "";
OkGoRequest.downLoad(context, uMessage.sound, new FileCallback(downPath, downFileName) {
@Override
public void onSuccess(Response<File> response) {
MessageSharedPrefs.getInstance(context).setMessageResourceDownloaded(uMessage.msg_id);
messageHandler.dealWithNotificationMessage(context, uMessage);
}
@Override
public void onError(Response<File> response) {
super.onError(response);
MessageSharedPrefs.getInstance(context).setMessageResourceDownloaded(uMessage.msg_id);
messageHandler.dealWithNotificationMessage(context, uMessage);
}
});
}
代码仅提供思路,具体实现自己写完整代码哈。
补充:
最近又遇到说Android11上面有手机自定义声音不发声的,打开系统配置里面铃声配置,发现通知铃声为无,就挺诡异的。有同事提出仿QQ,微信的自定义铃声方案,自己去用MediaPlayer来播放音乐,这也是一种解决方案吧,遇到此问题的同学可以一试。