Unity跨Android/iOS端如何实现Firebase FCM推送用户退订
问题根因
现有退订逻辑完全没触达FCM底层推送控制链路,三个核心错误直接导致开关失效:
- 移除
TokenReceived/MessageReceived的C#层事件委托,只会断开Unity业务层和SDK原生层的前台消息回调,完全不会阻止FCM系统服务接收推送、弹出系统通知。这两个事件仅在应用前台运行时生效,后台、杀进程状态下的通知展示完全由系统服务接管,和C#层是否注册监听没有任何关系。 - 调用Topic订阅/退订接口时没有等待异步任务执行完成,属于典型的“发完就不管”的写法,在SDK未完成初始化、网络波动时接口会静默失败,FCM服务端根本收不到退订请求,自然会继续向对应主题推送消息。
- 初始化逻辑存在状态丢失漏洞:仅在应用首次安装启动时执行一次通知初始化,后续冷启动时完全不读取本地存储的通知开关配置,而FCM SDK默认是开启消息接收的,用户关了开关之后只要重启应用,推送就会自动恢复。
- 没有调用FCM提供的全局推送控制接口,也没做系统级通知权限适配,仅靠业务层逻辑根本拦不住底层推送。
修复方案
按以下步骤替换原有逻辑即可实现稳定的通知开关控制,适配Firebase SDK 9.0.0版本、双端生效。
1. 重写通知开关方法
彻底抛弃“加减事件监听当开关”的错误写法,用FCM官方推荐的token控制+自动初始化开关+系统权限适配的方案实现:
using System; using System.Collections.Generic; using Firebase.Messaging; #if UNITY_ANDROID using Unity.Notifications.Android; #endif // 替换你原有的EnableNotifications/DisableNotification方法 public static async void EnableNotifications() { // 开启FCM自动初始化 FirebaseMessaging.AutoInitEnabled = true; // 重新注册事件监听,先减后加避免重复绑定 FirebaseMessaging.TokenReceived -= OnTokenReceived; FirebaseMessaging.MessageReceived -= OnMessageReceived; FirebaseMessaging.TokenReceived += OnTokenReceived; FirebaseMessaging.MessageReceived += OnMessageReceived; // 强制刷新获取新的FCM注册token,同步给FCM服务端 try { await FirebaseMessaging.GetTokenAsync(); } catch (Exception e) { Debug.LogError($"获取FCM Token失败,下次启动将重试:{e.Message}"); } // 安卓端恢复通知渠道权限 #if UNITY_ANDROID AndroidNotificationCenter.CancelAllNotifications(); // 替换成你项目实际使用的通知渠道ID、名称、描述 var defaultChannel = new AndroidNotificationChannel( "fcm_default_channel", "推送通知", "接收活动、奖励相关提醒", Importance.Default ); AndroidNotificationCenter.RegisterNotificationChannel(defaultChannel); #endif PlayerPrefs.SetInt("Notifications", 1); PlayerPrefs.Save(); } public static async void DisableNotification() { // 第一步:移除C#层事件回调,避免前台状态下触发业务逻辑 FirebaseMessaging.TokenReceived -= OnTokenReceived; FirebaseMessaging.MessageReceived -= OnMessageReceived; // 第二步:删除当前设备的FCM注册token,服务端将停止向该设备发送单播推送 try { await FirebaseMessaging.DeleteTokenAsync(); } catch (Exception e) { Debug.LogError($"删除FCM Token失败,缓存到下次启动重试:{e.Message}"); // 可将失败状态写入PlayerPrefs,下次启动后自动重试 } // 第三步:关闭FCM自动初始化,防止SDK后台自动重新注册token FirebaseMessaging.AutoInitEnabled = false; // 第四步:退订所有已订阅主题,必须等待异步任务完成 // 替换成你项目本地维护的已订阅主题列表 List<string> subscribedTopics = GetLocalSubscribedTopics(); foreach (var topic in subscribedTopics) { try { await FirebaseMessaging.UnsubscribeAsync(topic); } catch (Exception e) { Debug.LogError($"退订主题{topic}失败,缓存到下次启动重试:{e.Message}"); // 退订失败的主题写入本地缓存,下次启动后重新执行退订 AddFailedUnsubTopic(topic); } } // 第五步:系统级拦截通知,避免SDK异常状态下弹出消息 #if UNITY_ANDROID AndroidNotificationCenter.CancelAllNotifications(); // 将对应通知渠道的重要性设为None,系统会直接拦截该渠道所有通知 var disabledChannel = new AndroidNotificationChannel( "fcm_default_channel", "推送通知", "接收活动、奖励相关提醒", Importance.None ); AndroidNotificationCenter.RegisterNotificationChannel(disabledChannel); #endif PlayerPrefs.SetInt("Notifications", 0); PlayerPrefs.Save(); }
注意:所有FCM异步接口(
GetTokenAsync/DeleteTokenAsync/SubscribeAsync/UnsubscribeAsync)必须等待任务执行完成,不能调用后立刻返回,否则在SDK未就绪、网络差时会直接静默失败,没有任何报错提示。
2. 修复启动初始化逻辑
替换原有仅首次启动初始化通知的逻辑,每次冷启动都同步本地开关状态给SDK,避免重启后开关失效:
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(async task => { // 原有Analytics、Auth、实时数据库初始化逻辑保持不变 FirebaseAnalytics.SetAnalyticsCollectionEnabled(true); FirebaseAuth auth = FirebaseAuth.DefaultInstance; auth.SignInAnonymouslyAsync().ContinueWithOnMainThread(authTask => { if (authTask.IsCanceled || authTask.IsFaulted) { return; } DatabaseReference = FirebaseDatabase.DefaultInstance.RootReference; }); // 替换原有通知初始化逻辑 if (PlayerPrefs.GetInt("NotificationsFirstLaunch") == 0) { // 首次启动:iOS请求通知权限,默认开启通知 #if UNITY_IOS await FirebaseMessaging.RequestPermissionAsync(); #endif EnableNotifications(); PlayerPrefs.SetInt("NotificationsFirstLaunch", 1); PlayerPrefs.Save(); } else { // 非首次启动:读取本地开关状态,同步给SDK if (PlayerPrefs.GetInt("Notifications", 1) == 1) { EnableNotifications(); } else { DisableNotification(); // 此处可取出之前退订失败的主题,重新执行退订逻辑 } } });
3. 配置校验
- 检查AndroidManifest配置,不要手动给FCM相关的Service、Activity加
android:enabled="false"属性,保持Firebase SDK默认生成的配置即可,推送开关完全由上述代码逻辑控制。 - 如果通过Firebase控制台向全量用户推送通知,删除token后服务端状态同步最长需要15分钟,这段时间可能收到少量缓存消息,属于正常延迟,不是逻辑失效。
- 测试时不要在应用刚安装启动、还没拿到FCM token的时候就操作开关,等Logcat输出
Received Registration Token日志之后再操作,避免SDK未就绪导致接口调用失败。
常见踩坑说明
- 不要试图在
OnMessageReceived回调里拦截消息不弹通知:应用在后台、杀进程状态时,这个回调根本不会触发,通知是由FCM系统服务直接弹出的,C#层代码完全无法拦截。 - 不要使用已过时的
FirebaseMessaging.StopListening()接口,9.0.0版本下该接口行为不稳定,在部分安卓机型上完全不生效,删除token+关闭自动初始化是官方推荐的彻底停止推送的方案。 - iOS端不要重复调用
RequestPermissionAsync,系统会记住用户的授权选择,重复调用不会弹框也不会修改权限状态,关通知时不需要撤销权限,删除token之后服务端就不会再发消息。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

