最近、Youmeng Pushにタイトルなどのカスタマイズボイスを追加してほしいというご要望をいただきました。
説明は非常に簡潔で、上の図からわかるように、この要件の焦点は 8.0 以降のバージョンとの互換性です。Youmeng が提供したサンプル コードは次のとおりです。
上記のコードから、実際にはカスタム通知を設定していますが、上記のコードは 8.0 以降のシステムでは実行できません。理由は誰にとっても明らかですが、8.0 以降では通知バーに通知チャネル機能が追加されており、チャネル通知チャネルが設定されていない場合、通知は表示されません。
ローカルサウンドを設定する
さて、今回の要件の焦点であるサウンドのカスタマイズに戻りましょう。上記の分析を通じて、Umeng のカスタム サウンドが実際にはカスタム通知であることがすでにわかっています。そのため、カスタム サウンドもカスタム通知の一部です。どうすればよいでしょうか? このメソッドは、 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);
}
注:
ここでは、上記のコードのコメントに特に注意してください。着信音はチャンネルを作成する前にのみ変更でき、作成後の変更はサポートされていません。
説明は、特定の理由メソッドsetSound
のコメントに記載されています。
サウンド設定可能
ここで質問する人もいるかもしれませんが、サウンドを変更してカスタム サウンドを構成可能にしたい場合はどうすればよいですか? ? ?
NoticeChannel は 1 つのサウンドのみをバインドできるため、新しいチャネルを作成して新しいサウンドをバインドできますか? 答えは「はい」です。以前のサウンドはもう使用されないので、以前のNotificationChannelを削除(deleteNotificationChannel(String channelId))する必要があります。ここに穴があります。いつ削除しますか? 最初のテストでは、着信音やバイブレーションを変更するときに新しいチャンネルを作成し、古いチャンネルをすべて削除しましたが、この方法ではバグが発生し、前のチャンネルのステータスバーに表示された通知が表示されなくなります。現在のチャンネルにステータスバーに通知が表示されていない場合は削除し、そうでない場合は保存を続行します。コードは次のとおりです。
注:
ここに追加すると、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;
}
上記はすべてローカル サウンドのカスタマイズの問題ですが、ネットワーク オーディオを設定したい場合、どのように設定すればよいですか? ? ?
ネットワークオーディオを設定する
ネットワーク オーディオの
場合、Umeng は最初にオーディオ ファイルをローカル キャッシュ ディレクトリにダウンロードし、次にそれを設定します。
Youmeng が非常によく考えてくれたので、最初は怠惰なアイデアを思いつきましたが、この getSound メソッドを直接使用できるでしょうか? 実験の結果、残念ながら、Youmeng の方法で直接音を再生することはできませんでした。ここに皆さんにとって大きな疑問符が付きました。理由を知っているパートナーがいたら、コメントして共有して一緒に話し合ってください。
Youmeng からのニュースを直接使用するのは信頼性が低いため、直接行うしかありませんが、Youmeng が提供するアイデアに従って、ファイルをローカルにダウンロードしてセットアップします。ここでダウンロードしたディレクトリは getExternalCacheDir() なのですが、再生できるのが不思議です( •̀ ω •́ )y。
通知オーディオ再生のちょっとした分析
ダウンロードした Youmeng ファイルが再生できない理由が分からなかったとき、http オーディオ アドレスを直接 Uri に設定するという怠惰なアイデアを思いつきました。テストの結果、HTTPS アドレスは再生できることがわかりました。 Xiaomi 携帯電話では、HTTP アドレスを再生できません。ここでそれについて話しましょう。実際、これは解決策ではなく、間違ったアプローチですらありません。なぜ?
通知サウンドの再生は、NotificationPlayer クラスに実装されています。コードを参照してください。
最終的なサウンドの再生は、引き続き MediaPlayer によって実行されます。先ほど通知用に設定したサウンドは 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);
}
});
}
このコードはアイデアを提供するだけであり、特定の実装のために完全なコードを自分で書くことができます。
補充:
最近、Android 11 で携帯電話のカスタムサウンドがあり、音が出ないことに遭遇しましたが、システム構成の着信音設定を開くと、通知着信音が存在しないことがわかり、非常に奇妙です。同僚は、QQ と WeChat を模倣し、MediaPlayer を使用して自分で音楽を再生するカスタム着信音スキームを提案しました。これも解決策です。この問題に遭遇した学生は、それを試すことができます。