能否按用户而非设备推送通知?Android应用Firebase函数问题
问题排查与解决方案:FCM通知无法接收 + 按User ID推送需求
我来帮你一步步解决这两个问题:首先排查现有代码收不到通知的原因,然后实现按user_id给对应设备推送的功能。
一、现有代码收不到通知的核心问题
先看你当前代码里的几个明显错误:
设备令牌路径完全错误
你写的/users/${receiver_user_id}/user_id获取的是用户ID,根本不是FCM设备令牌!用户登录设备时,必须把设备的FCM令牌存在用户节点下(比如device_tokens字段或数组),否则拿到的token_id是无效的,自然推不出通知。Promise处理不规范
sendToDevice的.then()是空的,而且catch块没有return语句,这会导致Cloud Functions无法正确跟踪异步操作的完成状态,函数可能提前终止,错误也没法被正常捕获。前台通知未处理(客户端问题)
如果你的Android app在前台运行,FCM默认不会弹出系统通知栏,必须在客户端写FirebaseMessagingService来手动处理通知显示。
二、按User ID推送的最佳实现方案
要给特定user_id的所有登录设备推送,最可靠的方式是给每个用户维护一个设备令牌列表:用户登录新设备时,把FCM令牌加到数据库的/users/{user_id}/device_tokens节点;登出或卸载时移除令牌。然后在云函数中获取这个列表,批量发送通知。
修改后的完整云函数代码
'use strict' const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); exports.sendNotification = functions.database.ref(`/notification/{receiver_user_id}/{notification_id}`) .onWrite(async (change, context) => { const receiverUserId = context.params.receiver_user_id; const notificationId = context.params.notification_id; console.log('准备给用户推送通知:', receiverUserId); // 如果通知被删除,直接结束流程 if (!change.after.exists()) { console.log('通知已被删除:', notificationId); return null; } try { // 获取该用户的所有设备令牌(假设存在/users/{user_id}/device_tokens节点) const tokenSnapshot = await admin.database().ref(`/users/${receiverUserId}/device_tokens`).once('value'); if (!tokenSnapshot.exists()) { console.log('该用户没有注册任何设备令牌'); return null; } // 将快照转为令牌数组(支持多设备) const deviceTokens = Object.values(tokenSnapshot.val()); console.log('待推送的设备令牌:', deviceTokens); // 构建推送负载 const payload = { notification: { title: "新消息提醒", body: `您收到了一条新消息,快来查看吧!`, icon: "ic_notification" // 确保Android项目里有这个图标资源 }, data: { // 可选:自定义数据,客户端可以用来跳转页面等 type: "new_message", notification_id: notificationId } }; console.log('推送内容:', payload); // 批量发送通知到所有设备 const response = await admin.messaging().sendToDevice(deviceTokens, payload); // 自动清理无效令牌(比如设备卸载了应用) const invalidTokenCleanup = []; response.results.forEach((result, index) => { const error = result.error; if (error) { console.error('推送失败到令牌:', deviceTokens[index], error); // 识别无效令牌并加入清理队列 if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { invalidTokenCleanup.push(tokenSnapshot.ref.child(deviceTokens[index]).remove()); } } }); // 等待所有清理操作完成 if (invalidTokenCleanup.length > 0) { await Promise.all(invalidTokenCleanup); console.log('已清理无效设备令牌'); } console.log('通知推送流程完成'); return null; } catch (error) { console.error('推送过程出错:', error); return null; } });
客户端必须做的配置
- 存储设备令牌到数据库
用户登录成功后,获取FCM令牌并保存到用户节点下(注意去重),示例Android代码:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (!task.isSuccessful) { Log.w("FCM", "获取令牌失败", task.exception) return@addOnCompleteListener } val token = task.result val currentUserId = FirebaseAuth.getInstance().currentUser?.uid currentUserId?.let { // 用令牌作为key存true,自动去重 FirebaseDatabase.getInstance().ref("users/$it/device_tokens").child(token).setValue(true) } }
- 处理前台通知显示
新建FirebaseMessagingService子类,重写onMessageReceived来手动显示通知:
class MyMessagingService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) // 构建通知 val notificationBuilder = NotificationCompat.Builder(this, "MESSAGE_CHANNEL") .setContentTitle(remoteMessage.notification?.title) .setContentText(remoteMessage.notification?.body) .setSmallIcon(R.drawable.ic_notification) .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_HIGH) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 适配Android O及以上的通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel("MESSAGE_CHANNEL", "消息通知", NotificationManager.IMPORTANCE_HIGH) notificationManager.createNotificationChannel(channel) } // 显示通知 notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build()) } }
别忘了在AndroidManifest.xml中注册这个服务:
<service android:name=".MyMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
三、调试小贴士
- 去Firebase控制台的Cloud Functions日志里看具体错误,比如令牌无效、数据库权限问题等。
- 先测试单个有效令牌,确认推送正常后再扩展到多设备。
- 确保Android app的
google-services.json配置正确,FCM依赖添加完整。
内容的提问来源于stack exchange,提问作者Divya Vyas
相关产品推荐
相关产品推荐

