PAYATHON 2026

.NET MAUI iOS 点击通知为何不触发 NotificationOpened?

数据阿明

继续使用 NotificationOpened 处理普通通知点击,并检查插件初始化、iOS delegate 和冷启动事件队列。只把事件订阅移到 App.xaml.cs,不能保证整个回调链路正常。

目前还缺少插件版本、完整启动代码和其他通知组件配置,无法确定唯一根因。如果后台点击和冷启动点击都失败,建议先检查 delegate;如果只有冷启动失败,再重点检查订阅时机和事件队列。

先确认通知点击由哪个对象接收

在 iOS 上,用户点击通知后,系统通过 UNUserNotificationCenter 的 delegate 调用 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:。Apple 要求在应用完成启动前设置该 delegate。只在 AppDelegate 中添加带 [Export] 的方法,不会自动让它成为通知中心的 delegate。参见 Apple 通知响应说明。

所查阅的插件 develop 分支会安装自己的 delegate,并将普通点击转为 NotificationOpened。如果初始化时已经存在其他 delegate,插件只会记录警告,不会替换已有对象。因此,其他通知插件或手动设置 delegate 的代码可能截断这条路径,但现有信息不足以判断你的项目是否存在冲突。参见插件 iOS 实现。

可以分别在插件初始化后和其他组件初始化后记录 delegate:

#if IOS
System.Diagnostics.Debug.WriteLine(
    UserNotifications.UNUserNotificationCenter.Current.Delegate?
        .GetType().FullName ?? "Notification delegate is null");
#endif

如果项目同时使用多个通知组件,需要统一管理 delegate 并处理回调转发。直接把 delegate 改成 AppDelegate,可能导致插件收不到点击回调。

FinishedLaunching() 中的 LaunchOptionsRemoteNotificationKey 也不能作为所有点击场景的入口:应用从后台恢复时,不会重新执行启动流程。

检查初始化,并为冷启动配置事件队列

先确认 MauiProgram.cs 已调用 UseFirebasePushNotifications(),并按所安装版本的文档完成 iOS 的 AppDelegate 回调转发。注册回调和收到远程消息的回调需要保留,但它们不能代替通知点击响应。参见插件初始化文档。

对于支持 QueueFactory 的版本,可以在现有初始化调用中启用事件队列:

using Plugin.FirebasePushNotifications;
using Plugin.FirebasePushNotifications.Model.Queues;

var builder = MauiApp.CreateBuilder()
    .UseMauiApp<App>()
    .UseFirebasePushNotifications(options =>
    {
        options.QueueFactory = new PersistentQueueFactory();
    });

该配置用于拦截和排队通知事件,可用于排查冷启动时原生点击响应早于托管事件订阅到达的情况。所查阅的配置源码中,QueueFactory 默认为 null,即不启用排队。请先确认你的 NuGet 版本提供这些类型和属性,再使用示例;某个分支的默认行为不一定适用于所有版本。参见插件队列配置定义。

事件队列只能处理插件已经收到的事件。如果 delegate 回调没有进入插件,启用队列也无法补救。

先验证事件,再接入页面导航

在 App 构造函数中,先赋值再订阅,并在处理函数第一行记录日志。下面是放入现有 App 类的诊断片段:

private readonly IFirebasePushNotification _firebasePushNotification;

public App(IFirebasePushNotification firebasePushNotification)
{
    InitializeComponent();

    _firebasePushNotification = firebasePushNotification;
    _firebasePushNotification.NotificationOpened += onNotificationOpened;
}

private void onNotificationOpened(
    object sender,
    FirebasePushNotificationDataEventArgs e)
{
    System.Diagnostics.Debug.WriteLine("NotificationOpened fired");
}

保留项目原有的其他构造参数和初始化逻辑。原问题的代码片段省略了字段赋值,不能据此判断实际项目一定漏写了赋值。

看到日志后,再接入 _navigationService.GoToNotificationsView()。冷启动时,事件到达并不意味着窗口、根页面、Shell 或登录流程已经就绪。建议先保存待执行的导航请求,等应用自身的页面就绪流程完成后,再通过主线程执行:

await MainThread.InvokeOnMainThreadAsync(async () =>
{
    await _navigationService.GoToNotificationsView();
});

这段代码的前提是导航环境已经可用。切到主线程不会自动等待页面初始化,也不建议用固定时长的 Task.Delay() 猜测启动完成时间。异步事件处理函数还应捕获并记录异常,以免把导航失败误判为事件没有触发。

按场景确认故障位置

使用同一类可见的 notification + data 消息,分别验证:

  • 应用在后台时,点击通知中心里的通知。
  • 应用已被终止时,点击通知启动应用。
  • 点击通知主体和自定义操作按钮。

在所查阅的插件 iOS 实现中,普通点击交给 NotificationOpened,自定义按钮操作交给 NotificationAction。测试时要区分两者,参见对应处理逻辑。

如果后台点击也没有事件,先确认原生响应是否进入插件,以及 delegate 是否被占用或覆盖;如果后台正常而冷启动失败,重点检查事件队列和订阅时机;如果事件日志存在但页面不跳转,则继续排查导航就绪条件和异常。

备注:内容仅供参考。