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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:30:39