安卓FCM通知分组

13

我正在尝试为聊天应用实现FCM通知,并希望实现“WhatsApp-like”通知,其中通知将按对话分组。

例如,当Alice回复与Bob进行的对话时,Bob将收到一个消息主体为“Alice:内容”的通知。但是,如果Alice快速回复该对话,Bob将再次收到另一个新通知,但我只想将新回复附加到上一个通知中。

我该如何实现?


已回答:https://dev59.com/DZ_ha4cB1Zd3GeqPvDVL#43913156 - Rishabh
3个回答

6

通过将通知进行分组,我认为你的意思是将通知堆叠或者捆绑通知

这更多地涉及到如何在客户端应用程序中处理通知。您只需要使用setGroup()将所有通知添加到单个组中,然后调用notify()让NotificationManager进行更改。

将每个通知添加到一个组文档总结了所有内容。


是的,我的意思是堆叠通知,我也认为这样更有意义,因为可以在客户端应用程序上处理它。 - user2796124
@StevenLee 这完全取决于你希望如何实现。看到你想要的行为,我猜你只需要每次收到新通知时调整通知摘要即可。请随意将答案标记为正确,以标记您的帖子已被回答。干杯 - AL.
AL,你能检查一下这个问题吗?https://stackoverflow.com/q/63271091/8822337 - BIS Tech

0
您可以使用以下代码对通知进行分组。但是,由于我正在使用Android Web视图应用程序,因此我没有实现回复按钮。 我根据Android版本使用了2种类型的通知分组,因为Android Nougat及更高版本会根据组ID自动分组通知,但Marshmallow及更早版本不会分组通知。
我的Firebase消息处理服务如下:
import static com.packageName.config.AppConstant.MY_NOTIFICATION;

public class MyFireBaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "MyFireBaseService";
    private static final int SUMMARY_ID = 999;

    @Override
    public void onNewToken(String refreshedToken) {
        super.onNewToken(refreshedToken);
        //Store fcm token to shared preferences
        SharedPrefManager.getInstance(getApplicationContext()).setFCMToken(refreshedToken);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /* Data messages should be in the form of
     * {
     *      type(Required)              : "NotificationDTO type"
     *      title(Required)             : "NotificationDTO title"
     *      message(Required)           : "Message to be displayed in the notification panel"
     *      notificationURL(Required)   : "Url to be loaded into the web view"
     *      groupId(Optional)           : "Based on this group id, system will group the notification"
     *      channelId(optional)         : "This channel id will be used to send notification"
     *      image(optional)             : "This image will be displayed on notification panel"
     *      label(optional)             : "NotificationDTO label"
     *      priority(optional)          : "NotificationDTO priority. If notification priority not mentioned,
     *                                     Then default priority will be assigned to the notification"
     * }
     */
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        boolean isForeGround = false;

        super.onMessageReceived(remoteMessage);
        // Fetching data part from the notification
        Map<String, String> data = remoteMessage.getData();
        String message = data.get("message");
        String id = data.get("notificationId");
        int notificationId;

        // If notification id is empty then no need to show a notification
        if (id == null || id.isEmpty()) {
            return;
        } else {
            notificationId = Integer.parseInt(id);
        }
        if (message == null || message.equals("")) {
            message = getString(R.string.default_notification_message);
        }
        String notificationURL = data.get("notificationURL");
        String title = data.get("title");

        // Group id should be a string
        String groupKey = AppConstant.GROUP_KEY_NOTIFICATION;
        if (data.get("groupKey") != null) {
            groupKey = data.get("groupKey");
        }

        // Current we have only one channel with id `general_notification_id`
        String channelId = data.get("channelId");
        String label = data.get("label");
        String image = data.get("image");

        /*
         *  Notification priority(String Value) should be one of the following
         *  PRIORITY_HIGH/PRIORITY_LOW/PRIORITY_MAX/PRIORITY_MIN
         *  If no priority mentioned, system will automatically assign the default priority
         */
        String priority = data.get("priority");
        int notificationPriority = 0;
        if (priority != null && !priority.isEmpty()) {
            priority = priority.toUpperCase();
            switch (priority) {
                case "PRIORITY_HIGH":
                    notificationPriority = NotificationCompat.PRIORITY_HIGH;
                    break;
                case "PRIORITY_LOW":
                    notificationPriority = NotificationCompat.PRIORITY_LOW;
                    break;
                case "PRIORITY_MAX":
                    notificationPriority = NotificationCompat.PRIORITY_MAX;
                    break;
                case "PRIORITY_MIN":
                    notificationPriority = NotificationCompat.PRIORITY_MIN;
                    break;
                default:
                    notificationPriority = NotificationCompat.PRIORITY_DEFAULT;
                    break;
            }
        }

        /*
         *  Category should be from the following list.
         *  Because system will sort the notification based on the category.
         *
         *  CATEGORY_ALARM,CATEGORY_CALL,CATEGORY_MESSAGE,CATEGORY_EMAIL,CATEGORY_EVENT,
         *  CATEGORY_PROMO,CATEGORY_ALARM,CATEGORY_PROGRESS,CATEGORY_SOCIAL,CATEGORY_ERROR,
         *  CATEGORY_TRANSPORT,CATEGORY_SYSTEM,CATEGORY_SERVICE,CATEGORY_REMINDER,
         *  CATEGORY_RECOMMENDATION,CATEGORY_STATUS
         */
        String category = data.get("category");
        String notificationCategory = "";
        if (category != null && !category.isEmpty()) {
            category = category.toUpperCase();
            switch (category) {
                case "CATEGORY_ALARM":
                    notificationCategory = NotificationCompat.CATEGORY_ALARM;
                    break;
                case "CATEGORY_CALL":
                    notificationCategory = NotificationCompat.CATEGORY_CALL;
                    break;
                case "CATEGORY_MESSAGE":
                    notificationCategory = NotificationCompat.CATEGORY_MESSAGE;
                    break;
                case "CATEGORY_EMAIL":
                    notificationCategory = NotificationCompat.CATEGORY_EMAIL;
                    break;
                case "CATEGORY_EVENT":
                    notificationCategory = NotificationCompat.CATEGORY_EVENT;
                    break;
                case "CATEGORY_PROMO":
                    notificationCategory = NotificationCompat.CATEGORY_PROMO;
                    break;
                case "CATEGORY_PROGRESS":
                    notificationCategory = NotificationCompat.CATEGORY_PROGRESS;
                    break;
                case "CATEGORY_SOCIAL":
                    notificationCategory = NotificationCompat.CATEGORY_SOCIAL;
                    break;
                case "CATEGORY_ERROR":
                    notificationCategory = NotificationCompat.CATEGORY_ERROR;
                    break;
                case "CATEGORY_TRANSPORT":
                    notificationCategory = NotificationCompat.CATEGORY_TRANSPORT;
                    break;
                case "CATEGORY_SYSTEM":
                    notificationCategory = NotificationCompat.CATEGORY_SYSTEM;
                    break;
                case "CATEGORY_SERVICE":
                    notificationCategory = NotificationCompat.CATEGORY_SERVICE;
                    break;
                case "CATEGORY_RECOMMENDATION":
                    notificationCategory = NotificationCompat.CATEGORY_RECOMMENDATION;
                    break;
                case "CATEGORY_REMINDER":
                    notificationCategory = NotificationCompat.CATEGORY_REMINDER;
                    break;
                case "CATEGORY_STATUS":
                    notificationCategory = NotificationCompat.CATEGORY_STATUS;
                    break;
            }
        }

        // Default notification visibility is private
        String visibility = data.get("visibility");
        int notificationVisibility = 0;
        if (visibility != null && !visibility.isEmpty()) {
            visibility = visibility.toUpperCase();
            switch (visibility) {
                case "VISIBILITY_PUBLIC":
                    notificationVisibility = NotificationCompat.VISIBILITY_PUBLIC;
                    break;
                case "VISIBILITY_SECRET":
                    notificationVisibility = NotificationCompat.VISIBILITY_SECRET;
                    break;
                default:
                    notificationVisibility = NotificationCompat.VISIBILITY_PRIVATE;
                    break;
            }
        }
        //creating default notification url for grouped notifications
        // if notification grouped, user cannot go the url corresponding to the each notification therefore assign a common url for the notification 
        String defaultNotificationURL = "https://something.com"

        // Creating notification object
        NotificationDTO notificationDTO = new NotificationDTO(
                notificationId,
                groupKey,
                message,
                notificationURL,
                channelId,
                image,
                label,
                notificationPriority,
                title,
                notificationCategory,
                notificationVisibility,
                defaultNotificationURL);

        // Checking app is in foreground or background
        // if the app in the foreground this message service send a broadcast message
        // else app will create a notification in notification panel
        try {
            isForeGround = new ForegroundCheckTask().execute(this).get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //Android implement new grouping and channel mechanisms after android API version 24,
        //So we need to implement different notification settings for both above 24 and below 24
        if (android.os.Build.VERSION.SDK_INT >= 24) {
            createNotificationForAPILevelAbove24(notificationDTO, isForeGround);
        } else {
            createNotificationForAPILevelBelow24(notificationDTO, isForeGround);
        }
    }

    /**
     * Creating notification for api level above 24
     *
     * @param notificationDTO NotificationDTO
     * @param isForeGround    Boolean
     */
    private void createNotificationForAPILevelAbove24(NotificationDTO notificationDTO, Boolean isForeGround) {

        Log.d(TAG, String.valueOf(isForeGround));
        if (isForeGround) {
            Intent intent = new Intent(MY_NOTIFICATION);
            intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        } else {
            int requestID = (int) System.currentTimeMillis();
            Intent intent = new Intent(this, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intent.putExtra("notificationURL", notificationDTO.getNotificationURL());

            PendingIntent resultIntent = PendingIntent.getActivity(this, requestID, intent,
                    PendingIntent.FLAG_ONE_SHOT);
            Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            String defaultChannel = getString(R.string.general_notification_id);
            NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this, defaultChannel);
            mNotificationBuilder.setSmallIcon(R.drawable.ic_stat_notification);
            mNotificationBuilder.setColor(getResources().getColor(R.color.colorPrimary));
            mNotificationBuilder.setContentTitle(notificationDTO.getTitle());
            mNotificationBuilder.setContentText(notificationDTO.getMessage());
            mNotificationBuilder.setGroup(notificationDTO.getGroupKey());
            mNotificationBuilder.setAutoCancel(true);
            mNotificationBuilder.setSound(notificationSoundURI);
            mNotificationBuilder.setPriority(notificationDTO.getPriority());
            if (notificationDTO.getImage() != null) {
                Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
                mNotificationBuilder.setStyle(new NotificationCompat.BigPictureStyle()
                        .bigPicture(bitmap));
            }
            mNotificationBuilder.setContentIntent(resultIntent);
            if (notificationDTO.getCategory() != null) {
                mNotificationBuilder.setCategory(notificationDTO.getCategory());
            }
            mNotificationBuilder.setVisibility(notificationDTO.getVisibility());
            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
            boolean areNotificationsEnabled = notificationManager.areNotificationsEnabled();
            String appPushEnabled = String.valueOf(areNotificationsEnabled);
            notificationManager.notify(notificationDTO.getId(), mNotificationBuilder.build());

            // Creating notification summary for grouping notifications
            Notification summaryNotification =
                    new NotificationCompat.Builder(this, defaultChannel)
                            .setContentTitle(getString(R.string.app_name))
                            .setSmallIcon(R.drawable.ic_stat_notification)
                            //specify which group this notification belongs to
                            .setGroup(notificationDTO.getGroupKey())
                            //set this notification as the summary for the group
                            .setGroupSummary(true)
                            //automatically remove the notifications from the notification tray
                            .setAutoCancel(true)
                            .build();
            notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summaryNotification);
        }
    }

    /**
     * Handling notification for api level below 24
     *
     * @param notificationDTO NotificationDTO
     * @param isForeGround    Boolean
     */
    private void createNotificationForAPILevelBelow24(NotificationDTO notificationDTO, Boolean isForeGround) {
        if (isForeGround) {
            Intent intent = new Intent(MY_NOTIFICATION);
            intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        } else {
            //Grouping notifications
            String storedNotifications = SharedPrefManager.getInstance(this).getNotifications();
            JSONArray notificationArray;
            try {
                boolean isDuplicateNotification = false;
                JSONObject notificationObject = new JSONObject();
                notificationObject.put("notificationId", notificationDTO.getId());
                notificationObject.put("description", notificationDTO.getMessage());
                notificationObject.put("title", notificationDTO.getTitle());
                if (storedNotifications != null && !storedNotifications.equals("")) {
                    Log.d(TAG, storedNotifications);
                    notificationArray = new JSONArray(storedNotifications);
                    for (int i = 0; i < notificationArray.length(); i++) {
                        JSONObject json = notificationArray.getJSONObject(i);
                        if (json.getInt("notificationId") == notificationDTO.getId()) {
                            isDuplicateNotification = true;
                            break;
                        }
                    }
                } else {
                    notificationArray = new JSONArray();
                }
                if (isDuplicateNotification) {
                    //Notification already added to the tray
                    return;
                }
                notificationArray.put(notificationObject);
                SharedPrefManager.getInstance(this).setNotificationDetails(notificationArray.toString());
                Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                NotificationCompat.Builder summary = new NotificationCompat.Builder(this);
                summary.setSmallIcon(R.drawable.ic_stat_notification);
                summary.setGroup(notificationDTO.getGroupKey());
                summary.setAutoCancel(true);
                summary.setPriority(notificationDTO.getPriority());
                summary.setColor(ContextCompat.getColor(this, R.color.colorPrimary));
                summary.setSound(notificationSoundURI);
                summary.setContentTitle(notificationDTO.getTitle());
                summary.setContentText(notificationDTO.getMessage());
                summary.setPriority(notificationDTO.getPriority());
                if (notificationDTO.getCategory() != null) {
                    summary.setCategory(notificationDTO.getCategory());
                }
                summary.setVisibility(notificationDTO.getVisibility());
                if (notificationDTO.getImage() != null) {
                    Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
                    summary.setStyle(new NotificationCompat.BigPictureStyle()
                            .bigPicture(bitmap));
                }
                /*
                 *  This is used to pass notification url to the main class of the application.
                 *  Based on this url MainActivity load the corresponding url into the web view
                 */
                Intent intent = new Intent(this, MainActivity.class);

                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

                /*
                 * checking more than 2 notifications received by the system,
                 * then this will create a summary of that notifications.
                 * else create a single notification
                 */
                if (notificationArray.length() > 1) {

                    summary.setGroupSummary(true);

                    NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();

                    inboxStyle.setBigContentTitle(getString(R.string.app_name));

                    summary.setStyle(inboxStyle);
                    int messageCount;
                    for (messageCount = 0; messageCount < notificationArray.length(); messageCount++) {
                        JSONObject json = notificationArray.getJSONObject(messageCount);
                        inboxStyle.addLine(json.getString("title") + " " + json.getString("description"));
                    }
                    inboxStyle.setSummaryText(String.valueOf
                            (messageCount) + " notifications");
                    summary.setNumber(messageCount);
                    summary.setContentText(String.valueOf(messageCount + " notifications"));
                    intent.putExtra("notificationURL", notificationDTO.getDefaultNotificationUrl());
                } else {
                    intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
                }

                PendingIntent resultIntent = PendingIntent.getActivity(this, 0, intent,
                        PendingIntent.FLAG_ONE_SHOT);

                summary.setContentIntent(resultIntent);

                /*
                 *  One cancel intent is used to clear the notifications stored in
                 *  the shared preferences when user delete the notifications.
                 */
                Intent onCancelIntent = new Intent(this, OnCancelBroadcastReceiver.class);

                PendingIntent onDismissPendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, onCancelIntent, 0);

                summary.setDeleteIntent(onDismissPendingIntent);

                notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summary.build());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Used to load image from notification
     *
     * @param imageUrl String
     * @return Bitmap
     */
    public Bitmap getBitmapFromUrl(String imageUrl) {
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.connect();
            InputStream input = connection.getInputStream();
            return BitmapFactory.decodeStream(input);

        } catch (Exception e) {
            return null;
        }
    }
}

通知对象类如下:

public class NotificationDTO {
    private String groupKey, message, notificationURL, channelId;
    private String image, label, title, category,defaultNotificationUrl;
    private int priority, id, visibility;

    public NotificationDTO(
            int id,
            String groupKey,
            String message,
            String notificationURL,
            String channelId,
            String image,
            String label,
            int priority,
            String title,
            String category,
            int visibility,
            String defaultNotificationUrl) {
        this.groupKey = groupKey;
        this.message = message;
        this.id = id;
        this.notificationURL = notificationURL;
        this.channelId = channelId;
        this.image = image;
        this.label = label;
        this.priority = priority;
        this.title = title;
        this.category = category;
        this.visibility = visibility;
        this.defaultNotificationUrl = defaultNotificationUrl;
    }

    public String getGroupKey() {
        return groupKey;
    }

    public String getMessage() {
        return message;
    }

    public String getNotificationURL() {
        return notificationURL;
    }

    public String getChannelId() {
        return channelId;
    }

    public String getLabel() {
        return label;
    }

    public String getImage() {
        return image;
    }

    public int getPriority() {
        return priority;
    }

    public String getTitle() {
        return title;
    }

    public String getCategory() {
        return category;
    }

    public int getId() {
        return id;
    }

    public int getVisibility() {
        return visibility;
    }

    public String getDefaultNotificationUrl() {
        return defaultNotificationUrl;
    }
}

以下是类似于SharedPreference管理器的代码:

public class SharedPrefManager {
    private static final String KEY_FCM_TOKEN = "keyFCMToken";
    private static final String KEY_NOTIFICATIONS = "keyNotifications";
    private static SharedPrefManager mInstance;
    private static Context mContext;

    private SharedPrefManager(Context context) {
        mContext = context;
    }

    public static synchronized SharedPrefManager getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new SharedPrefManager(context);
        }
        return mInstance;
    }

    public void setNotificationDetails(String descriptions) {
        SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(KEY_NOTIFICATIONS, descriptions);
        editor.apply();
    }
    public void setFCMToken(String fcmToken) {
        SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(KEY_FCM_TOKEN, fcmToken);
        editor.apply();
    }
    public String getNotifications() {
        SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
        return sharedPreferences.getString(KEY_NOTIFICATIONS, null);
    }

    public String getFCMToken() {
        SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
        return sharedPreferences.getString(KEY_FCM_TOKEN, null);
    }
}

而为了创建通知渠道,我使用了一个扩展应用程序类的应用程序控制器类。

public class AppController extends Application {
    public static final String TAG = AppController.class.getSimpleName();
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        /*
          Define notification channels here.
         */
        //NotificationDTO channel is necessary
        //create  a notification channel id in res/values/strings.xml
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create channel to show notifications.
            String defaultChannel = getString(R.string.general_notification_id);
            String channelName = getString(R.string.general_notification_name);
//            String miscellaneousChannel = getString(R.string.miscellaneous_notification_id);
//            String miscellaneousChannelName = getString(R.string.miscellaneous_notification_name);
            NotificationManager notificationManager =
                    getSystemService(NotificationManager.class);
            if (notificationManager != null) {
                notificationManager.createNotificationChannel(new NotificationChannel(defaultChannel,
                        channelName, NotificationManager.IMPORTANCE_DEFAULT));
            }
        }
    }

一个广播接收器实现了如下清除共享首选项的功能
public class OnCancelBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("ON_CANCEL","Cancelled");
        SharedPrefManager.getInstance(context).setNotificationDetails("");
    }
}

-1

14
标签不会将它们分组,而是替换它们。你无法看到具有相同标签的早期通知。这就像一个游戏得分卡,在游戏进行中更新。 - Hanu
@Amruta-Pani,你找到解决方案了吗? - SardorbekR

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