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

Xamarin:Notification Service Extension创建UNNotificationCategory无效问题

问题描述

我的应用不需要固定的操作按钮集合,而是要通过通知负载动态传递按钮信息,所以需要在每次收到带按钮的通知时,动态创建UNNotificationCategory。

为实现这个需求,我创建了Notification Service Extension来拦截通知、解析负载、创建类别并更新推送通知的类别ID。理论上这个方案可行,但实际测试发现,在Notification Service Extension中创建UNNotificationCategory后,推送通知并没有显示对应的操作按钮。

测试情况

  • 在主项目的FinishedLaunching方法中添加了打印所有UNNotificationCategory的代码,结果始终为空。
  • 不确定FinishedLaunching是否在Notification Extension之前执行。
  • 曾怀疑UNUserNotificationCenter.Current实例在主项目和扩展中不一致,但测试发现库项目中创建的类别可以在主项目中访问。

其他说明

如果在FinishedLaunching中预先创建测试用的UNNotificationCategory,仅在Notification Extension中修改推送通知的categoryID,功能可以正常工作,但这无法实现动态创建的需求。

请问:为什么在Notification Service Extension中创建的类别无法被推送通知识别?


代码示例

AppDelegate.cs(主项目)

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    Rg.Plugins.Popup.Popup.Init();
    global::Xamarin.Forms.Forms.Init();
    IOSMigrationHelper migrationHelper = new IOSMigrationHelper();
    // must be done before Firebase.Core.App.Configure() to migrate the saved fcm token
    // and before LoadApplication() so we don't run the initial setup process
    // and after Xamarin.Forms.Forms.Init() so we can use DependencyService
    migrationHelper.Migrate();
    Forms9Patch.iOS.Settings.Initialize(this);
    LoadApplication(new App());
    bool success = base.FinishedLaunching(app, options);

    if (success)
    {
        AllGesturesRecognizer allGesturesRecognizer = new AllGesturesRecognizer(delegate
        {
            SessionManager.Instance.ExtendSession();
        });

        Window.AddGestureRecognizer(allGesturesRecognizer);
    }

    if (string.IsNullOrEmpty(AppPreferences.DeviceIdentifier))
    {
        // this will change if the user removes and reinstalls the app but there isn't anything else to use 
        AppPreferences.DeviceIdentifier = UIDevice.CurrentDevice.IdentifierForVendor.ToString();
    }

    // Test Categories. Creating them here and then just updating the categoryID in the notification extension works
    var actions1 = new List<UNNotificationAction>();
    var action11 = UNNotificationAction.FromIdentifier($"C1 B1", "C1 B1", UNNotificationActionOptions.None); // TODO: change to foregound if urlaction != post
    var action12 = UNNotificationAction.FromIdentifier($"C1 B2", "C1 B2", UNNotificationActionOptions.None);

    var actions2 = new List<UNNotificationAction>();
    var action21 = UNNotificationAction.FromIdentifier($"C2 B1", "C2 B1", UNNotificationActionOptions.None);
    var action22 = UNNotificationAction.FromIdentifier($"C2 B2", "C2 B2", UNNotificationActionOptions.None);

    actions1.Add(action11);
    actions1.Add(action12);

    actions2.Add(action21);
    actions2.Add(action22);

    var intentIDs = new string[] { };
    var category1 = UNNotificationCategory.FromIdentifier("cat1", actions1.ToArray(), intentIDs, UNNotificationCategoryOptions.AllowInCarPlay | UNNotificationCategoryOptions.AllowAnnouncement);
    var category2 = UNNotificationCategory.FromIdentifier("cat2", actions2.ToArray(), intentIDs, UNNotificationCategoryOptions.AllowInCarPlay | UNNotificationCategoryOptions.AllowAnnouncement);

    // Register category
    var categories = new UNNotificationCategory[] { category1, category2 };
    UserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(categories));
    PrintCategories();

    SetupPushNotifications(app);
    return success;
}

async void PrintCategories()
{
    try
    {
        NSSet<UNNotificationCategory> categories = await iOSNotificationCenter.Current.GetNotificationCategoriesAsync();

        // Categories is null when the categories created in the AppDelegate are commented out and moved to the Notification Extension
        if (categories == null)
        {
            App.Current.MainPage.DisplayAlert($"No categories", "", "Ok");
            return;
        }

        foreach (UNNotificationCategory c in categories)
        {
            App.Current.MainPage.DisplayAlert($"Category: {c.Identifier}", "", "Ok");
        }
    }
    catch (Exception e)
    {
        App.Current.MainPage.DisplayAlert(e.ToString(), "", "Ok");
    }
} 

NotificationService.cs(Notification Service Extension)

public override void DidReceiveNotificationRequest(UNNotificationRequest request, Action<UNNotificationContent> contentHandler)
{
    ContentHandler = contentHandler;
    BestAttemptContent = (UNMutableNotificationContent)request.Content.MutableCopy();

    var categoryID = "but1";

    // creating the categories in the notification service extension doesn't work. The notification doesn't have any buttons 
    var actions = new List<UNNotificationAction>();
    var action1 = UNNotificationAction.FromIdentifier($"Button 1", "Button 1", UNNotificationActionOptions.None);
    var action2 = UNNotificationAction.FromIdentifier($"Button 2", "Button 2", UNNotificationActionOptions.None);

    actions.Add(action1);
    actions.Add(action2);

    var intentIDs = new string[] { };
    var category = UNNotificationCategory.FromIdentifier(categoryID, actions.ToArray(), intentIDs, UNNotificationCategoryOptions.AllowInCarPlay | UNNotificationCategoryOptions.AllowAnnouncement);

    //// Register category
    var categories = new UNNotificationCategory[] { category };
    UNUserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(categories));

    BestAttemptContent.CategoryIdentifier = categoryID;
    ContentHandler(BestAttemptContent);
}

问题解答

核心原因

iOS系统在处理推送通知并展示操作按钮的流程中,会在触发Notification Service Extension之前就检查UNNotificationCategory的注册状态。也就是说,当你的Extension开始创建类别时,系统已经完成了对通知类别有效性的校验,此时新注册的类别无法被当前通知复用。

另外,Notification Service Extension和主App是两个独立的进程,虽然它们共享同一个UNUserNotificationCenter的底层存储,但系统在处理当前通知时,不会等待Extension中的类别注册完成再去加载按钮配置。

解决方案

  1. 提前注册所有可能的类别模板:如果按钮的类型、数量有固定的几种组合,可以在主App启动时(FinishedLaunching)提前注册所有对应的UNNotificationCategory,通知负载中只传递类别ID即可。这也是你测试中能正常工作的方案。

  2. 使用通知服务扩展配合本地通知转发:

    • 在Notification Service Extension中拦截远程通知,解析出按钮信息后,先取消原远程通知的展示。
    • 动态创建UNNotificationCategory并注册,然后创建一个本地通知,设置对应的类别ID,最后展示这个本地通知。
      注意:这种方式需要处理好通知的展示时机和权限,同时要避免重复通知。
  3. 使用iOS 14+的交互式通知新API:如果你的应用只需要支持iOS 14及以上版本,可以考虑利用通知内容扩展(Notification Content Extension)来自定义通知界面,直接在扩展中渲染按钮并处理点击事件,无需依赖UNNotificationCategory。

额外说明

你在FinishedLaunching中打印不到Extension注册的类别,是因为主App进程和Extension进程是分开启动的。当Extension运行时,主App可能处于未启动状态,此时Extension注册的类别会被存储,但主App启动时如果没有重新注册这些类别,调用GetNotificationCategoriesAsync可能无法获取到(因为主App进程的UNUserNotificationCenter实例需要重新加载存储的类别)。


内容的提问来源于stack exchange,提问作者Tyler Wanta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 07:12:06