Android——Youmeng custom notification sound

Recently, I received a request, such as the title, to add a customized voice to Youmeng Push.
insert image description here
The description is very concise. As can be seen from the figure above, the focus of this requirement is the compatibility of versions above 8.0. The sample code given by Youmeng is as follows:
insert image description here
From the above code, it is actually setting a custom Notification, but the above code is not feasible for systems above 8.0. The reason should be clear to everyone. A NotificationChannel feature has been added to the notification bar above 8.0. If the channel notification channel is not set, the notification will not be displayed.

set local sound

Now let's go back to the focus of our requirement this time: customizing the sound. Through the above analysis, we already know that Umeng's custom sound is actually a custom notification, so the custom sound is also a part of the custom notification, how to do it? The method can be found by looking up the NotificationChannel's API setSound(Uri sound, AudioAttributes audioAttributes). So hurry up and upload the code, let's set up a local sound first:

//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);
}

Note:
Pay special attention to the comments in the above code here. You can only modify the ringtone before creating a channel, and it does not support modification after creation. The description has been given in the comments of
the specific reason method:setSound
insert image description here

sound configurable

Seeing that some people may have questions here, what should I do if I want to change the sound and make the custom sound configurable? ? ?

Since a NotificationChannel can only bind one sound, can we create a new Channel to bind a new sound? The answer is yes. Since the previous sound is no longer used, we need to delete (deleteNotificationChannel(String channelId)) the previous NotificationChannel. There is a pit here. When will you delete it? For the first test, I created a new channel when modifying the ringtone or vibration, and deleted all the old channels before, but there will be a bug in this way, the Notification that is still displayed on the status bar on the previous channel will be deleted, all Make a judgment, if the current channel has no notification displayed in the status bar, delete it, otherwise continue to save, the code is as follows:

Note:
To add here, the deleteNotificationChannel method does not really delete the channel, but just sets a deletion flag. The specific analysis process can be seen in the source code 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;
}

The above are all the problems of customizing local sound, so someone has another problem. If I want to configure a network audio, how should I set it up? ? ?

Set up network audio

Refer to the getSound method of UmengMessageHandler in Umeng SDK:
insert image description hereinsert image description here
If it is network audio, Umeng first downloads the audio file to the local cache directory, and then sets it.

Here I had a lazy idea at the beginning, since Youmeng has considered so well for us, can I use this getSound method directly? Through the experiment, unfortunately, the sound was not played directly by the method of Youmeng. Here is a big question mark for everyone. If any partner knows the reason, please comment and share it to discuss together.

Since it is unreliable to directly use the news from Youmeng, you can only do it directly. According to the ideas provided by Youmeng, download the file to the local, and then set it up. The directory I downloaded here is getExternalCacheDir(), and it is strange that it can be played ( •̀ ω •́ )y.

A little analysis of notification audio playback

Here is another one to add: When I couldn’t find why the downloaded file of Youmeng couldn’t be played, I had a lazy idea to directly set the http audio address to Uri. The test found that the Https address can be played on the Xiaomi mobile phone. Http addresses cannot be played. Let’s talk about it here, in fact, this is not a solution, or even a wrong approach. why?

The playback of the notification sound is implemented in the NotificationPlayer class, see the code:
insert image description here
the final sound playback is still performed by MediaPlayer. The Sound we set for Notification before is the DataSource of MediaPlayer. If it is network audio and then wrapped by Uri, the player will think it is a local file, and it will report a file not found error when parsing. It is amazing. Here It is clearly an error, but you can still hear the sound playing on some mobile phones. So it is not recommended that you directly plug the Http address to Sound.

Since the source code part mentioned above is only a certain point, if you need to know more students, please open the source code for detailed comparison and understanding.

The source code of this article is as follows:

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);
            }
        });
    }

The code only provides ideas, and you can write the complete code yourself for the specific implementation.

Replenish:

Recently, I encountered that there is a mobile phone custom sound on Android 11 that does not make a sound. Open the ringtone configuration in the system configuration and find that there is no notification ringtone, which is quite strange. A colleague proposed a custom ringtone scheme that imitates QQ and WeChat, and uses MediaPlayer to play music by himself. This is also a solution. Students who encounter this problem can try it.

Guess you like

Origin blog.csdn.net/u012230055/article/details/117032246