无法在Android Oreo上通过编程方式更新通知渠道的声音

3
我在更新 Android Oreo 中一个通道的通知声音时遇到了一些问题。我知道用户可以通过打开应用程序通知屏幕手动设置声音,但我想通过在默认设置活动中使用 RingtonePreference 来以编程方式进行此操作(用户能够从我的应用程序内部的活动中选择通知声音)。
问题是,第一个发送到应用程序的通知会从 PreferenceManager.getDefaultSharedPreferences() 中获取默认的声音值,并且在手动更改为其他媒体(使用 RingtonePreference 屏幕)之后,它仍将播放最初在该通道上创建的声音,而不是用户选择的新声音。
我不明白为什么 NotificationChannel 声音没有根据新的声音值进行更新,因为我正在做这样的事情:
NotificationChannel mChannel = new NotificationChannel(id, title, importance); mChannel.setSound(ringtoneUri, audioAttributes);
以下是完整代码:
 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);

}

我唯一能够更新声音的方法是每次通知被触发时,给频道ID赋一个随机值UUID.randomUUID().toString(),但这会在用户手动检查应用程序通知屏幕时产生大量垃圾。

对此的提示将不胜感激。

非常感谢!


1
用户创建频道后,您无法更改它们 - 更改频道是用户的责任。 - ianhanniballake
2个回答

4
有一种方法可以“欺骗”Android以允许此操作。如果您使用WhatsApp应用程序,您会发现他们允许您在应用程序内更改消息通知的声音。如果您稍微调查一下,您会注意到他们实际上删除并创建频道。
但是,如果您删除并重新创建相同的频道(换句话说,“新”频道具有与“旧”频道相同的频道ID),则您的新更改将不适用。 Android显然跟踪所有已删除的频道,并在重新创建时防止对这些频道进行更新。所以假设您有一个频道(并且可以在应用程序中访问其字段)。如果您想要用户例如更新频道的声音,则需要执行以下操作:
  • 删除旧频道
  • 创建一个具有不同频道ID但具有新更新的声音和所有其他用户可见字段与刚刚删除的频道完全相同的新频道。
因此,假设您有两个带有定义的“基本ID”的频道,我们将基于它们的所有频道ID。然后,每当您的应用程序启动时,您可以检查频道是否已经存在,如果存在,则检索它们,否则创建它们。添加更多频道时需要一些手动操作,也许有更好的方法来完成这项工作。但是,如果您这样做,您可以为每个更改创建一个新的频道,并为其提供一个新的递增ID。下面是一个完整的工作示例类,负责处理所有这些内容。在此示例中,我们具有两个频道及其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;
    }
}

我认为唯一的缺点是:
  1. 你必须添加额外的逻辑来维护你的通道(而不只是创建它们,让用户在操作系统中进行更改)。
  2. 只有在版本>= OREO时才能运行。因此,在旧设备上运行此示例将无法完成任何操作。所以我们需要添加处理功能。在这种情况下,我们需要将我们“更新”的通知类型存储在prefs或数据库中,以便通知操作系统与我们添加的默认内容不同的内容。
  3. Android将在应用通知设置中显示计数器,以告知用户已删除多少次通道。我认为大多数用户可能永远不会关心或看到它,但可能会有一些人对此感到恼怒。

1
非常感谢您的详细解释!非常感激。 - Lucian Radu

1
我不理解为什么NotificationChannel声音值更新后没有变化。
大多数情况下,NotificationChannel是一个只写API。创建后,您无法修改它的大多数特性。
但我想通过在默认设置活动中使用RingtonePreference来以编程方式执行此操作。
我建议从您的应用程序中删除此功能或仅在Android 7.1及更早版本设备上提供它。

感谢澄清,我会尝试仅允许API >= 26的此功能。 - Lucian Radu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接