简体   繁体   English

无法以编程方式为 Android Oreo 上的通知通道更新声音

[英]Can't update sound programmatically for Notification Channel on Android Oreo

I am having some troubles updating notification sound for a channel into Android Oreo.我在将频道的通知声音更新到 Android Oreo 时遇到了一些麻烦。 I know that the sound can be set by the user manually by opening App notifications screen, but I want to do this programmatically by using RingtonePreference into a default Settings activity (user to be able to pick up notification sound from an activity inside my app).我知道用户可以通过打开应用程序通知屏幕手动设置声音,但我想通过在默认设置活动中使用 RingtonePreference 以编程方式执行此操作(用户能够从我的应用程序内的活动中获取通知声音) .

Problem is that the first notification fired into application picks up the default sound value from the PreferenceManager.getDefaultSharedPreferences() and after manually changing it to other media (using RingtonePreference screen) it will still play the sound which was created initially on that channel and not the new one selected by the user.问题是应用程序发出的第一个通知从 PreferenceManager.getDefaultSharedPreferences() 中获取默认声音值,并且在手动将其更改为其他媒体(使用 RingtonePreference 屏幕)后,它仍会播放最初在该频道上创建的声音,而不是用户选择的新的。

I don't understand why the NotificationChannel sound is not updated according with the new sound value as I am doing something like this NotificationChannel mChannel = new NotificationChannel(id, title, importance); mChannel.setSound(ringtoneUri, audioAttributes);我不明白为什么 NotificationChannel 声音没有根据新的声音值更新,因为我正在做这样的事情NotificationChannel mChannel = new NotificationChannel(id, title, importance); mChannel.setSound(ringtoneUri, audioAttributes); NotificationChannel mChannel = new NotificationChannel(id, title, importance); mChannel.setSound(ringtoneUri, audioAttributes);

Below is the full code:以下是完整代码:

 public static void sendNotification(Context context, NotificationType notificationType) {
        String id = "channel_1"; // default_channel_id
        String title = "Doppler Channel"; // Default Channel
        Intent intent;
        PendingIntent pendingIntent;
        NotificationCompat.Builder builder;

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        Uri ringtoneUri = Uri.parse(preferences.getString("notifications_new_message_ringtone", "DEFAULT_RINGTONE_URI"));
        boolean isNotificationSticky = !Boolean.parseBoolean(preferences.getAll().get("stickyNotification").toString());

        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                .build();

        NotificationManager notifManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            int importance = NotificationManager.IMPORTANCE_DEFAULT;

            NotificationChannel mChannel = notifManager.getNotificationChannel(id);

                mChannel = new NotificationChannel(id, title, importance);
                mChannel.setSound(ringtoneUri, audioAttributes);
                notifManager.createNotificationChannel(mChannel);

            builder = new NotificationCompat.Builder(context, id);
            intent = new Intent(context, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
            builder .setSmallIcon(notificationType.getNotificationIcon())
                    .setContentTitle(notificationType.getNotificationContent())
                    .setSubText(notificationType.getNotificationTitle())
                    .setOnlyAlertOnce(true)
                    .setOngoing(isNotificationSticky)
                    .setAutoCancel(true)
                    .setSound(ringtoneUri)
                    .setColor(notificationType.getNotificationColor())
                    .setContentIntent(pendingIntent);
        }
Notification notification = builder.build();
notifManager.notify(1, notification);

}

The only way I was able to update the sound was to give the channel id a random value UUID.randomUUID().toString() each time a notification is fired but this is causing a lot of garbage when user manually checks the App notification screen.我能够更新声音的唯一方法是在每次触发通知时为频道 ID 提供一个随机值UUID.randomUUID().toString()但这会在用户手动检查 App 通知屏幕时造成大量垃圾.

A hint on this would be much appreciated.对此的提示将不胜感激。

Thanks a lot!非常感谢!

There is a way you can "trick" Android into allowing this.有一种方法可以“欺骗”Android 允许这样做。 If you use the WhatsApp app you'll see that they allow you to alter the sound of message notifications from within the app.如果您使用 WhatsApp 应用程序,您会发现它们允许您从应用程序内更改消息通知的声音。 And if you investigate a little you'll notice that they actually delete and create channels.如果您稍微调查一下,您会发现他们实际上删除并创建了频道。

However, if you delete and recreate the same channel (in other words, the "new" channel has the same channel ID as the "old" one) then your new changes will not apply.但是,如果您删除并重新创建相同的频道(换句话说,“新”频道与“旧”频道具有相同的频道 ID),那么您的新更改将不适用。 Android apparently keeps track of all the deleted ones and prevent updates to those when recreated. Android 显然会跟踪所有已删除的内容,并在重新创建时阻止对这些内容进行更新。 So lets say you have your channel (and access to its fields in your app).因此,假设您拥有自己的频道(并可以访问您应用中的各个字段)。 What you need to do if you want a user to, for example, update the sound of a channel is:例如,如果您希望用户更新频道的声音,您需要做的是:

  • Delete the old channel删除旧频道
  • Create a new one with a different channel ID but with the new updated sound and all other user visible fields identical to the just deleted channel.使用不同的频道 ID 创建一个新频道,但使用新的更新声音和与刚刚删除的频道相同的所有其他用户可见字段。

So if we assume you have two channels with defined "Base ID":s which we'll base all their channel ID's on.因此,如果我们假设您有两个定义了“基本 ID”的频道:我们将基于它们的所有频道 ID。 Then, whenever your app launches you can check if the channels already exist, and if so, retreive them, otherwise create them.然后,每当您的应用程序启动时,您都可以检查通道是否已经存在,如果存在,则检索它们,否则创建它们。 It requires some manual labor when you add more channels, perhaps there are better ways of doing this.当您添加更多频道时,它需要一些人工劳动,也许有更好的方法来做到这一点。 But if you do this you can create a new channel for each change and give it a new incremented ID.但是,如果您这样做,您可以为每个更改创建一个新频道并为其提供一个新的递增 ID。 Below is a full working example of a class that takes care of all this.下面是一个处理所有这些的类的完整工作示例。 And in this example we have the two channels with their baseId's and they are initialized with sound #1 and sound #2 but each time we instantiate the class we update the second channel and give it sound #3.在这个例子中,我们有两个通道及其 baseId,它们用声音 #1 和声音 #2 初始化,但每次我们实例化类时,我们都会更新第二个通道并给它声音 #3。

public class TestNotificationHandler {

    private static TestNotificationHandler instance;
    private NotificationManager mNotificationManager;
    private Context mContext;

    private Uri NOTIFICATION_SOUND_1, NOTIFICATION_SOUND_2, NOTIFICATION_SOUND_3, NOTIFICATION_SOUND_4;

    private static final String TAG = TestNotificationHandler.class.getSimpleName();

    public static TestNotificationHandler getInstance(Context context) {
        if (instance == null) {
            instance = new TestNotificationHandler(context);
        }
        return instance;
    }

    private TestNotificationHandler(Context context) {
        mContext = context;
        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        NOTIFICATION_SOUND_1 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_1);
        NOTIFICATION_SOUND_2 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_2);
        NOTIFICATION_SOUND_3 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_3);
        NOTIFICATION_SOUND_4 = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.sound_4);

        getOrCreateChannels();

        // Only added here for testing, this should of course be done when user actually performs a change from within the app
        setNewChannelSound(testChannel2, NOTIFICATION_SOUND_3);

       // Remember that we now effectively have deleted testChannel2, so running this will not work, b/c no channel with the id that the testChannel2 object has currently exists so we cannot delete it
        setNewChannelSound(testChannel2, NOTIFICATION_SOUND_4);

      // The easy and ugly way would be to simply do this which will update our objects
       getOrCreateChannels();

      // And this will now work
      setNewChannelSound(testChannel2, NOTIFICATION_SOUND_4);


      // If we changed so that setNewChannelSound and updateChannel actually returned the updated 
      // channel (or the already existing one if could not update), then we could do something like: 
      // testChannel2 = setNewChannelSound(testChannel2, NOTIFICATION_SOUND_3);
    }


    AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setUsage(AudioAttributes.USAGE_ALARM)
            .build();

    private NotificationChannel testChannel;
    private String baseTestChannelId = "TEST_CHANNEL_ID-";

    private NotificationChannel testChannel2;
    private String baseTest2ChannelId = "TEST_CHANNEL_2_ID-";

    private void getOrCreateChannels() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            for (NotificationChannel channel : mNotificationManager.getNotificationChannels()) {
                // Since we'll incrementally update the ID we'll look for the correct channel with startsWith
                if (channel.getId().startsWith(baseTestChannelId)) {
                    testChannel = channel;
                } else if (channel.getId().startsWith(baseTest2ChannelId)) {
                    testChannel2 = channel;
                }
            }
            if (testChannel == null) {
                // This should only happen the first time the app is launched or if you just added this channel to your app
                // We'll update the ID incrementally so if it doesn't exist will simply create it as number 0

                testChannel = new NotificationChannel(baseTestChannelId + "0", "TEST CHANNEL", NotificationManager.IMPORTANCE_HIGH);
                testChannel.setDescription("First test channel");
                testChannel.setSound(NOTIFICATION_SOUND_1, mAudioAttributes);
                mNotificationManager.createNotificationChannel(testChannel);
            }
            if (testChannel2 == null) {
                // This should only happen the first time the app is launched or if you just added this channel to your app
                // We'll update the ID incrementally so if it doesn't exist will simply create it as number 0
                testChannel2 = new NotificationChannel(baseTest2ChannelId + "0", "TEST CHANNEL 2", NotificationManager.IMPORTANCE_HIGH);
                testChannel2.setDescription("Second test channel");
                testChannel2.setSound(NOTIFICATION_SOUND_2, mAudioAttributes);
                mNotificationManager.createNotificationChannel(testChannel2);
            } 
        }
    }

    private boolean setNewChannelSound(NotificationChannel notificationChannel, Uri newSound) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationChannel.setSound(newSound, mAudioAttributes);
            return updateChannel(notificationChannel);
        }
        return false;
    }


    private boolean updateChannel(NotificationChannel notificationChannel) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String baseChannelId = getBaseChannelId(notificationChannel);
            if (baseChannelId == null) {
                Log.e(TAG, "Could not find baseChannelId from id: " + notificationChannel.getId());
                return false;
            }
            int oldIndex = getChannelIncrementedIndex(notificationChannel, baseChannelId);
            if (oldIndex == -1) {
                Log.e(TAG, String.format("Could not get old channel index from id: %s and baseChannelId: %d", notificationChannel.getId(), baseChannelId));
                return false;
            }
            NotificationChannel updatedChannel = new NotificationChannel(baseChannelId+(oldIndex+1), notificationChannel.getName(), NotificationManager.IMPORTANCE_HIGH);
            updatedChannel.setDescription(notificationChannel.getDescription());
            updatedChannel.setVibrationPattern(notificationChannel.getVibrationPattern());
            updatedChannel.setSound(notificationChannel.getSound(), mAudioAttributes);
            mNotificationManager.deleteNotificationChannel(notificationChannel.getId());
            mNotificationManager.createNotificationChannel(updatedChannel);
            return true;
        }
        return false;
    }

    /**********************************************************
     Some helper methods
     **********************************************************/
    private String getBaseChannelId(NotificationChannel notificationChannel) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (notificationChannel.getId().startsWith(baseTestChannelId)) {
                return baseTestChannelId;
            } else if (notificationChannel.getId().startsWith(baseTest2ChannelId)) {
                return baseTest2ChannelId;
            }
        }
        return null;
    }

    private int getChannelIncrementedIndex(NotificationChannel channel, String baseChannelId) {
        int index = -1;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelIncIdx = channel.getId().substring(baseChannelId.length(), channel.getId().length());

            try {
                index = Integer.parseInt(channelIncIdx);
            } catch (Exception e) {
                Log.e(TAG, String.format("Could not parse channel index %s", channelIncIdx));
            }
        }
        return index;
    }
}

I would say that the only drawbacks are:我会说唯一的缺点是:

  • That you actually have to add this extra logic to maintain your channels (instead of simply creating them and letting the user do the changes out in the OS).你实际上必须添加这个额外的逻辑来维护你的频道(而不是简单地创建它们并让用户在操作系统中进行更改)。
  • Only works on version >= OREO.仅适用于 >= OREO 的版本。 So running this example on an older device will not accomplish anything.所以在旧设备上运行这个例子不会完成任何事情。 So we need to add functionality to handle that.所以我们需要添加功能来处理这个问题。 In that case we need to store our "updated" notification types in the prefs or database in order to notify the OS with something different then what we've added as default.在那种情况下,我们需要将“更新的”通知类型存储在首选项或数据库中,以便用与我们默认添加的内容不同的内容通知操作系统。
  • Android will show a counter in the App notification settings which informs the user of how many times a channel has been deleted. Android 会在 App 通知设置中显示一个计数器,通知用户某个频道被删除了多少次。 I'd say most users would never care about this or even see it but potentially a few could be annoyed by this.我会说大多数用户永远不会关心这个,甚至不会看到它,但可能会有一些人对此感到恼火。

I don't understand why the NotificationChannel sound is not updated according with the new sound value我不明白为什么 NotificationChannel 声音没有根据新的声音值更新

For the most part, NotificationChannel is a write-once API.在大多数情况下, NotificationChannel是一次编写的 API。 You cannot modify most of its characteristics after you create it.创建后,您无法修改其大部分特征。

but I want to do this programmatically by using RingtonePreference into a default Settings activity但我想通过在默认设置活动中使用 RingtonePreference 以编程方式执行此操作

I recommend removing this feature from your app, or only offering it on Android 7.1 and older devices.我建议从您的应用中删除此功能,或仅在 Android 7.1 及更早版本的设备上提供此功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM