带用户认证的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
相关产品推荐
相关产品推荐

