Android会话通知更新:标准实现方式及最佳实践咨询
问题
我们在Android应用中使用NotificationCompat.MessagingStyle实现聊天消息推送通知,通过OneSignal SDK接收通知,并实现INotificationServiceExtension对通知进行修改。需求是收到新消息通知时,将新消息追加到同一会话的已有通知中(无则新建)。
当前实现方式是通过locusId查找旧通知、复制其样式、取消旧通知后再创建包含新消息的新通知,但不确定该方式是否正确。此外,通知ID由OneSignal自动生成,未进行追踪。简化实现代码如下:
class PushNotificationService : INotificationServiceExtension { override fun onNotificationReceived(event: INotificationReceivedEvent) { when (val type = event.notification.additionalData?.getString("type")) { "new_message" -> { // Can't be null if type exists val data: NewMessagePushData = event.notification.additionalData!!.let { Json.decodeFromString(it.toString()) } event.applyConversationStyle(data) } // ... } } private fun INotificationReceivedEvent.applyConversationStyle( data: NewMessagePushData ) { val notificationManager = context.getSystemService(FirebaseMessagingService.NOTIFICATION_SERVICE) as NotificationManager val previousNotification = notificationManager .activeNotifications .find { it.notification.locusId?.id == data.chatId.toString() } val usersDao = getService(context).usersDao() notification.setExtender { notification -> val (user, icon) = runBlocking { val user = /* Fetch user from local DB*/ val icon = user?.let {/*Fetch user's avatar and process it for display*/} user to icon } if (user == null) return@setExtender notification val person = /*Build an androidx.core.app.Person from user data*/ val style = previousNotification?.let { NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(it.notification) } ?: NotificationCompat.MessagingStyle( /*Build an androidx.core.app.Person for current user*/ ) // This is questionable! previousNotification?.id?.let { OneSignal.Notifications.removeNotification(it) } notification .setContentTitle(user.fullName.asString(context)) .setStyle( style .addMessage( NotificationCompat.MessagingStyle.Message( data.body, System.currentTimeMillis(), person ) ) ) .addPerson(person) .setLargeIcon(icon?.toIcon(context)) .setCategory(Notification.CATEGORY_MESSAGE) .setLocusId(LocusIdCompat(data.chatId.toString())) .setShortcutInfo(context.createConversationShortcutIfNeeded( data = data, user = user, person = person, icon = icon )) } } }
核心疑问:
- 正确的标准实现方式是什么?
- 是否需要引入消息DAO获取消息?
- 是否应让后端为同一会话固定通知ID,以便系统自动更新通知?还是需要自行追踪这些通知ID?
回答
一、标准实现方式
你的核心聚合思路是对的,但当前先删除旧通知再发送新通知的做法存在问题——会导致通知闪烁,不符合系统推荐的更新逻辑。正确的流程应该是:
- 通过
locusId或会话ID找到同一会话的旧通知 - 从旧通知中提取
MessagingStyle,追加新消息 - 使用相同的通知ID直接更新旧通知,系统会自动替换并保留会话聚合效果
另外,在INotificationServiceExtension中修改通知时,不需要手动调用OneSignal.Notifications.removeNotification(),只要确保通知ID一致,修改后的通知会自动覆盖旧通知,OneSignal会负责完成后续发送逻辑。
二、是否需要引入消息DAO?
分两种场景判断:
- 若推送仅包含最新一条消息,且不需要展示完整历史消息链:不需要额外引入DAO,直接从旧通知的
MessagingStyle中提取历史消息并追加新消息即可。 - 若需要在通知中展示最近N条完整消息(比如用户未清除旧通知时,新通知要显示完整上下文):建议引入消息DAO,从本地数据库拉取最近的消息列表重新构建
MessagingStyle——这样即使旧通知被清除,新通知也能完整展示历史消息,体验更一致。
三、通知ID的处理方案
这是实现会话聚合的核心,推荐优先级如下:
后端为同一会话固定通知ID(最优)
让后端在推送时,给同一会话的所有消息分配同一个通知ID(比如直接用chatId作为通知ID)。系统会自动用新通知替换同ID的旧通知,无需手动查找旧通知,MessagingStyle的消息聚合会自动生效,代码更简洁。- 注意:需要在OneSignal推送配置中指定固定的通知ID(具体可参考OneSignal推送API的
notificationId或android_group参数),覆盖其自动生成逻辑。
- 注意:需要在OneSignal推送配置中指定固定的通知ID(具体可参考OneSignal推送API的
本地自行追踪通知ID
如果后端无法修改,可在本地维护chatId与通知ID的映射表(用SharedPreferences或本地数据库存储)。收到新消息时:- 先查映射表拿到对应会话的通知ID,用该ID更新通知
- 若没有映射记录,则使用OneSignal自动生成的ID,并将
chatId与该ID的映射关系保存下来 - 需要额外处理用户手动清除通知的情况(此时要删除对应映射记录),复杂度稍高。
内容的提问来源于stack exchange,提问作者Calamity

