FCM主题推送引发Firestore通知重复问题求助
动物领养APP FCM通知重复写入Firestore问题
问题背景
我和学生开发一款动物领养APP,未修改FCM相关代码的情况下出现突发Bug:新增动物后,Firestore通知集合中出现重复文档。
复现步骤
- 发送FCM推送:向Firestore添加新Animal时,通过POST发送FCM主题推送(无自定义后端,教学场景),此步骤正常,通知可在
MyFirebaseMessagingService中接收。 - 处理通知并写入Firestore:重写
onMessageReceived处理通知,将通知数据存入Firestore集合(限制最多5条)以展示通知历史。
错误现象
- 预期:通知历史页面仅显示1条最新通知
- 实际:Firestore集合中出现两个timestamp完全相同的文档,一个
tipoAnimal为"Perro/Gato",另一个为空字符串;调试时onMessageReceived仅进入一次,仅收到一条推送通知。
代码片段
1. FCM推送发送代码
private fun sendNotificationToServer(animal: Animal, animalId: String) { val retrofit = Retrofit.Builder() .baseUrl("https://fcm.googleapis.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val service = retrofit.create(FCMService::class.java) val notification = FCMNotification( to = "/topics/animales", notification = null, data = FCMAnimalData( idAnimal = animalId, tipoAnimal = animal.tipoAnimal.animalString, base64Image = "", title = "Nuevo animal", body = "Se ha añadido un nuevo animal a la lista. Haz clic para conocerlo!", timestamp = Timestamp.now().seconds ) ) val call = service.sendNotification(notification) call.enqueue(object : Callback<ResponseBody> { override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { if (response.isSuccessful) { Log.d("NOTIFICATION POST SUCCESS", response.message()) } else { Log.d("NOTIFICATION POST FAILURE1", response.message()) } } override fun onFailure(call: Call<ResponseBody>, t: Throwable) { Log.d("NOTIFICATION POST FAILURE2", t.message.toString()) } }) }
2. FCM消息处理代码
val fcmData = remoteMessage.data sendNotification(fcmData["title"] as String, fcmData["body"] as String) firestore.collection(ALERTS_TABLE_NAME) .add(FCMAnimalData( base64Image = fcmData["base64Image"] as String, idAnimal = fcmData["idAnimal"] as String, tipoAnimal = fcmData["tipoAnimal"] as String, title = fcmData["title"] as String, body = fcmData["body"] as String, timestamp = fcmData["timestamp"]?.toLong() as Long )).addOnSuccessListener { Log.d("NOTIFICATION", "ADDED") firestore.collection(ALERTS_TABLE_NAME).get().addOnSuccessListener { notificationList -> if (notificationList.size() > 5) { firestore.collection(ALERTS_TABLE_NAME).document(notificationList.first().id).delete() } } }
3. Animal数据类与枚举定义
@Parcelize data class Animal( @DocumentId var id: String = "", val nombre: String = "", val edad: Int = 0, val imagenBase64: String = "", val raza: String = "", val descripcion: String = "", var favorito: Boolean = false, val tipoAnimal: TipoAnimal = TipoAnimal.PERRO, val procedencia: TipoUsuario = TipoUsuario.PARTICULAR, val idUsuario: String = "", val timestamp: Long = 0 ): Parcelable enum class TipoAnimal(val animalString: String) { PERRO("Perro"), GATO("Gato") }
可能原因分析与排查建议
1. 推送发送逻辑被重复触发
虽然调试时onMessageReceived仅执行一次,但sendNotificationToServer可能被调用了两次:
- 检查新增Animal到Firestore的代码逻辑,确认
sendNotificationToServer是否在addOnSuccessListener中被重复绑定或调用。 - 在
sendNotificationToServer函数开头添加日志,打印animal.tipoAnimal.animalString和调用次数,验证是否发送了两次不同的推送。
2. FCM数据字段解析的空值安全问题
代码中使用as String强制转型,若remoteMessage.data中字段为null可能引发异常,此处出现空字符串需排查:
- 在
onMessageReceived中打印remoteMessage.data的完整内容(Log.d("FCM_DATA", fcmData.toString())),确认是否存在重复字段或空值。 - 替换强制转型为安全取值,比如
fcmData["tipoAnimal"] ?: "",避免潜在空值问题,同时明确空值来源。
3. 枚举序列化/反序列化异常
若Animal对象的tipoAnimal字段未正确初始化,会导致推送时发送空字符串:
- 检查新增Animal时的逻辑,确认
tipoAnimal是否被正确赋值(比如表单提交时是否选择了动物类型)。 - 验证从Firestore读取Animal对象时,枚举
TipoAnimal的反序列化是否正常(是否存在字段名不匹配或Gson配置问题)。
4. FCM重复投递(低概率)
FCM存在极端情况下的重复投递机制,即使客户端仅收到一次回调,也需验证:
- 在
onMessageReceived中打印remoteMessage.messageId,确认两次写入是否对应同一条消息ID。
内容的提问来源于stack exchange,提问作者marcpetitvecino
相关产品推荐
相关产品推荐

