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

带用户认证的Firebase应用:正确捕获FCM设备注册令牌的方法

正确的FCM设备注册令牌捕获与用户关联方案

你的问题核心在于令牌获取时机早于用户认证,导致无法将令牌与具体用户绑定,而且你用的FirebaseInstanceIdService其实已经被Firebase官方弃用了,咱们一步步来解决这个问题:

第一步:改用最新的FCM令牌回调API

Firebase在新版本中已经用FirebaseMessagingService替代了FirebaseInstanceIdService,你需要重写onNewToken方法来捕获新生成或刷新的令牌:

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "MyFirebaseMsgService";
    private SharedPreferences sharedPrefs;

    @Override
    public void onCreate() {
        super.onCreate();
        sharedPrefs = getSharedPreferences("FCM_PREFS", MODE_PRIVATE);
    }

    @Override
    public void onNewToken(String token) {
        Log.d(TAG, "Refreshed FCM token: " + token);
        // 先检查用户是否已经登录
        FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
        if (currentUser != null) {
            // 用户已登录,直接将令牌绑定到用户节点
            saveTokenToUserNode(currentUser.getUid(), token);
        } else {
            // 用户未登录,先把令牌存在本地SharedPreferences
            sharedPrefs.edit().putString("UNASSOCIATED_TOKEN", token).apply();
            Log.d(TAG, "Token stored locally, will associate after login");
        }
    }

    private void saveTokenToUserNode(String userId, String token) {
        // 数据库结构建议:users/{userId}/fcmTokens/{tokenKey} = token
        // 这样一个用户可以绑定多个设备令牌
        DatabaseReference userFcmTokensRef = FirebaseDatabase.getInstance()
                .getReference("users")
                .child(userId)
                .child("fcmTokens");
        
        // 用令牌本身作为key,避免重复存储
        userFcmTokensRef.child(token).setValue(true)
                .addOnSuccessListener(aVoid -> Log.d(TAG, "Token saved to user node"))
                .addOnFailureListener(e -> Log.e(TAG, "Failed to save token", e));
    }
}

第二步:登录成功后关联本地存储的令牌

在你的SignInActivity中,当用户登录成功的回调里,要检查本地是否有未关联的令牌,如果有就上传到当前用户的节点:

// 假设这是你的登录成功回调
FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(this, task -> {
            if (task.isSuccessful()) {
                FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
                if (user != null) {
                    // 检查本地是否有未关联的令牌
                    SharedPreferences sharedPrefs = getSharedPreferences("FCM_PREFS", MODE_PRIVATE);
                    String unassociatedToken = sharedPrefs.getString("UNASSOCIATED_TOKEN", null);
                    if (unassociatedToken != null) {
                        // 上传令牌到用户节点
                        DatabaseReference userFcmTokensRef = FirebaseDatabase.getInstance()
                                .getReference("users")
                                .child(user.getUid())
                                .child("fcmTokens");
                        userFcmTokensRef.child(unassociatedToken).setValue(true)
                                .addOnSuccessListener(aVoid -> {
                                    Log.d("SignIn", "Unassociated token linked to user");
                                    // 关联成功后清除本地存储
                                    sharedPrefs.edit().remove("UNASSOCIATED_TOKEN").apply();
                                });
                    }
                    // 同时,主动获取一次当前令牌,确保没有遗漏
                    FirebaseMessaging.getInstance().getToken()
                            .addOnCompleteListener(tokenTask -> {
                                if (!tokenTask.isSuccessful()) {
                                    Log.w("SignIn", "Fetching FCM token failed", tokenTask.getException());
                                    return;
                                }
                                String token = tokenTask.getResult();
                                // 保存到用户节点
                                saveTokenToUserNode(user.getUid(), token);
                            });
                }
            }
        });

// 把saveTokenToUserNode方法也复制到SignInActivity里,或者封装成工具类
private void saveTokenToUserNode(String userId, String token) {
    DatabaseReference userFcmTokensRef = FirebaseDatabase.getInstance()
            .getReference("users")
            .child(userId)
            .child("fcmTokens");
    userFcmTokensRef.child(token).setValue(true);
}

第三步:配置数据库安全规则(关键!)

为了确保只有用户自己能读写自己的令牌,需要在Firebase控制台的Realtime Database中设置安全规则:

{
  "rules": {
    "users": {
      "$uid": {
        "fcmTokens": {
          ".read": "$uid === auth.uid",
          ".write": "$uid === auth.uid"
        }
      }
    }
  }
}

关键要点总结

  • 令牌与用户强绑定:不要像之前那样把所有令牌存在一个公共节点,而是每个用户节点下维护自己的令牌列表,这样后续Cloud Functions发通知时,能精准找到目标用户的所有设备令牌。
  • 处理两种时机:登录前获取的令牌先存本地,登录后再关联;登录后获取的令牌直接绑定用户。
  • 避免重复存储:用令牌本身作为数据库节点的key,这样同一设备的令牌更新时会自动覆盖旧记录。
  • 主动获取令牌:在登录成功后主动调用FirebaseMessaging.getInstance().getToken(),确保不会因为令牌刷新时机的问题遗漏绑定。

内容的提问来源于stack exchange,提问作者mike.kamau

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:12:46