You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否按用户而非设备推送通知?Android应用Firebase函数问题

问题排查与解决方案:FCM通知无法接收 + 按User ID推送需求

我来帮你一步步解决这两个问题:首先排查现有代码收不到通知的原因,然后实现按user_id给对应设备推送的功能。

一、现有代码收不到通知的核心问题

先看你当前代码里的几个明显错误:

  1. 设备令牌路径完全错误
    你写的/users/${receiver_user_id}/user_id获取的是用户ID,根本不是FCM设备令牌!用户登录设备时,必须把设备的FCM令牌存在用户节点下(比如device_tokens字段或数组),否则拿到的token_id是无效的,自然推不出通知。

  2. Promise处理不规范
    sendToDevice的.then()是空的,而且catch块没有return语句,这会导致Cloud Functions无法正确跟踪异步操作的完成状态,函数可能提前终止,错误也没法被正常捕获。

  3. 前台通知未处理(客户端问题)
    如果你的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;
  }
 });

客户端必须做的配置

  1. 存储设备令牌到数据库
    用户登录成功后,获取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)
    }
}
  1. 处理前台通知显示
    新建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 22:38:09