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

基于Xamarin Forms实现本地定时事件通知的技术咨询

Xamarin Forms 实现医疗类本地调度通知全方案

作为刚上手Xamarin Forms开发医疗类应用的开发者,你的需求完全是这类应用的核心场景之一,我来给你梳理最优实现路径+可直接复用的示例代码,同时解答你的关联问题:

一、最优实现路径:依赖服务(Dependency Service)+ 平台原生API

你猜的没错,本地通知属于平台专属功能,Xamarin Forms没有统一的跨平台API,所以**依赖服务(Dependency Service)**是标准最优方案——在共享项目定义抽象接口,然后在Android/iOS项目分别实现原生逻辑,这样既保证跨平台一致性,又能利用原生平台的通知能力。

二、核心代码示例

1. 共享项目:定义通知服务接口

先在共享项目里创建ILocalNotificationService,把你需要的能力抽象出来:

public interface ILocalNotificationService
{
    // 请求通知权限(iOS必须,Android 13+也需要)
    Task<bool> RequestNotificationPermission();
    
    // 调度本地通知:参数包含事件ID、标题、内容、触发时间、延后操作所需的配置
    Task ScheduleNotification(int eventId, string title, string message, DateTime triggerTime);
    
    // 取消指定事件ID的通知
    Task CancelNotification(int eventId);
    
    // 处理通知点击/延后操作的回调(可根据平台实现)
    void HandleNotificationResponse(int eventId, string actionType);
}

2. Android平台实现

Android这边需要处理通知渠道(Android 8.0+强制要求)、AlarmManager调度触发、BroadcastReceiver接收触发事件并显示通知,还要支持锁屏操作:

[assembly: Dependency(typeof(LocalNotificationService_Android))]
namespace YourApp.Droid.Services
{
    public class LocalNotificationService_Android : ILocalNotificationService
    {
        private const string ChannelId = "MedicalReminderChannel";
        private const string ChannelName = "医疗提醒";

        public async Task<bool> RequestNotificationPermission()
        {
            if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
            {
                var status = await Android.App.Application.Context.CheckSelfPermissionAsync(Manifest.Permission.PostNotifications);
                if (status != Permission.Granted)
                {
                    // 这里需要在Activity中请求权限,可配合MessagingCenter通知主页面发起请求
                    return false;
                }
            }
            CreateNotificationChannel();
            return true;
        }

        private void CreateNotificationChannel()
        {
            if (Build.VERSION.SdkInt < BuildVersionCodes.O) return;
            
            var channel = new NotificationChannel(ChannelId, ChannelName, NotificationImportance.High)
            {
                Description = "用于医疗预约、检查的提醒通知"
            };
            var notificationManager = (NotificationManager)Android.App.Application.Context.GetSystemService(Context.NotificationService);
            notificationManager.CreateNotificationChannel(channel);
        }

        public Task ScheduleNotification(int eventId, string title, string message, DateTime triggerTime)
        {
            var intent = new Intent(Android.App.Application.Context, typeof(NotificationReceiver));
            intent.PutExtra("EventId", eventId);
            intent.PutExtra("Title", title);
            intent.PutExtra("Message", message);
            
            // 创建延后操作的PendingIntent
            var delayIntent = new Intent(Android.App.Application.Context, typeof(NotificationActionReceiver));
            delayIntent.PutExtra("EventId", eventId);
            delayIntent.PutExtra("Action", "Delay");
            var delayPendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, eventId + 1000, delayIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

            var pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, eventId, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
            
            var alarmManager = (AlarmManager)Android.App.Application.Context.GetSystemService(Context.AlarmService);
            var triggerTimeMillis = (long)(triggerTime - DateTime.UtcNow).TotalMilliseconds + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            
            // 针对Doze模式使用精确调度
            if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, triggerTimeMillis, pendingIntent);
            }
            else
            {
                alarmManager.SetExact(AlarmType.RtcWakeup, triggerTimeMillis, pendingIntent);
            }

            return Task.CompletedTask;
        }

        public Task CancelNotification(int eventId)
        {
            var intent = new Intent(Android.App.Application.Context, typeof(NotificationReceiver));
            var pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, eventId, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
            var alarmManager = (AlarmManager)Android.App.Application.Context.GetSystemService(Context.AlarmService);
            alarmManager.Cancel(pendingIntent);
            return Task.CompletedTask;
        }

        public void HandleNotificationResponse(int eventId, string actionType)
        {
            // 根据事件ID请求在线数据,处理对应操作
            if (actionType == "Delay")
            {
                // 示例:延后30分钟重新调度
                var newTriggerTime = DateTime.Now.AddMinutes(30);
                ScheduleNotification(eventId, "延后的医疗提醒", "你已延后本次提醒,新时间:" + newTriggerTime, newTriggerTime);
            }
            else
            {
                // 处理正常点击,打开对应事件详情页
                MessagingCenter.Send(this, "OpenEventDetail", eventId);
            }
        }
    }

    // 接收通知触发事件,显示通知
    [BroadcastReceiver(Enabled = true)]
    public class NotificationReceiver : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            var eventId = intent.GetIntExtra("EventId", -1);
            var title = intent.GetStringExtra("Title");
            var message = intent.GetStringExtra("Message");

            // 创建通知的延后操作按钮
            var delayAction = new Notification.Action.Builder(
                Icon.CreateWithResource(context, Resource.Drawable.ic_delay),
                "延后处理",
                PendingIntent.GetBroadcast(context, eventId + 1000, new Intent(context, typeof(NotificationActionReceiver))
                    .PutExtra("EventId", eventId)
                    .PutExtra("Action", "Delay"), PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable))
                .Build();

            var notificationBuilder = new Notification.Builder(context, "MedicalReminderChannel")
                .SetContentTitle(title)
                .SetContentText(message)
                .SetSmallIcon(Resource.Drawable.ic_notification)
                .SetPriority((int)NotificationPriority.High)
                .SetAutoCancel(true)
                .AddAction(delayAction);

            var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);
            notificationManager.Notify(eventId, notificationBuilder.Build());
        }
    }

    // 接收通知操作(比如延后)
    [BroadcastReceiver(Enabled = true)]
    public class NotificationActionReceiver : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            var eventId = intent.GetIntExtra("EventId", -1);
            var action = intent.GetStringExtra("Action");
            var service = DependencyService.Get<ILocalNotificationService>();
            service.HandleNotificationResponse(eventId, action);
        }
    }
}

3. iOS平台实现

iOS使用UNUserNotificationCenter来处理通知调度,同样支持锁屏操作:

[assembly: Dependency(typeof(LocalNotificationService_iOS))]
namespace YourApp.iOS.Services
{
    public class LocalNotificationService_iOS : ILocalNotificationService
    {
        public async Task<bool> RequestNotificationPermission()
        {
            var center = UNUserNotificationCenter.Current;
            var options = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge;
            var status = await center.RequestAuthorizationAsync(options);
            return status.Granted;
        }

        public Task ScheduleNotification(int eventId, string title, string message, DateTime triggerTime)
        {
            var center = UNUserNotificationCenter.Current;
            
            // 创建延后操作的Action
            var delayAction = UNNotificationAction.FromIdentifier("DelayAction", "延后处理", UNNotificationActionOptions.None);
            var category = UNNotificationCategory.FromIdentifier("MedicalReminderCategory", new[] { delayAction }, new string[] { }, UNNotificationCategoryOptions.None);
            center.SetNotificationCategories(new NSSet<UNNotificationCategory>(category));

            var content = new UNMutableNotificationContent
            {
                Title = title,
                Body = message,
                Sound = UNNotificationSound.Default,
                CategoryIdentifier = "MedicalReminderCategory",
                UserInfo = new NSDictionary("EventId", eventId.ToString())
            };

            var triggerDate = DateTimeOffset.FromUnixTimeSeconds((long)(triggerTime - new DateTime(1970, 1, 1)).TotalSeconds);
            var trigger = UNCalendarNotificationTrigger.CreateTrigger(
                new NSDateComponents
                {
                    Year = triggerDate.Year,
                    Month = triggerDate.Month,
                    Day = triggerDate.Day,
                    Hour = triggerDate.Hour,
                    Minute = triggerDate.Minute
                },
                repeats: false);

            var request = UNNotificationRequest.FromIdentifier(eventId.ToString(), content, trigger);
            center.AddNotificationRequest(request, (error) =>
            {
                if (error != null)
                {
                    // 处理调度错误
                }
            });

            // 设置通知响应处理
            center.Delegate = new NotificationDelegate();

            return Task.CompletedTask;
        }

        public Task CancelNotification(int eventId)
        {
            UNUserNotificationCenter.Current.RemovePendingNotificationRequests(new[] { eventId.ToString() });
            UNUserNotificationCenter.Current.RemoveDeliveredNotifications(new[] { eventId.ToString() });
            return Task.CompletedTask;
        }

        public void HandleNotificationResponse(int eventId, string actionType)
        {
            if (actionType == "DelayAction")
            {
                // 延后30分钟重新调度
                var newTriggerTime = DateTime.Now.AddMinutes(30);
                ScheduleNotification(eventId, "延后的医疗提醒", "你已延后本次提醒,新时间:" + newTriggerTime, newTriggerTime);
            }
            else
            {
                // 打开事件详情页
                MessagingCenter.Send(this, "OpenEventDetail", eventId);
            }
        }
    }

    public class NotificationDelegate : UNUserNotificationCenterDelegate
    {
        public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
        {
            var eventId = int.Parse(response.Notification.Request.Content.UserInfo["EventId"].ToString());
            var actionType = response.ActionIdentifier;
            
            var service = DependencyService.Get<ILocalNotificationService>();
            service.HandleNotificationResponse(eventId, actionType);
            
            completionHandler();
        }
    }
}

三、事件ID关联在线数据的可行性

完全可以!而且这是标准做法:

  • 调度通知时,把事件ID作为通知的唯一标识(Android的PendingIntent请求码、iOS的requestIdentifier),同时放到通知的附加信息里(Android的Intent Extra、iOS的UserInfo)。
  • 当用户点击通知或者执行延后操作时,取出这个事件ID,直接调用你的在线API获取该事件的完整数据,再进行后续处理(比如打开详情页、更新事件状态)。

四、锁屏端延后操作实现

上面的示例代码已经包含了这部分逻辑:

  • Android:通过给Notification添加Action,绑定一个PendingIntent到自定义的BroadcastReceiver,接收延后操作的触发,然后在接收器里调用服务重新调度通知。
  • iOS:通过创建UNNotificationAction并关联到通知类别,用户在锁屏点击“延后处理”时,UNUserNotificationCenterDelegate会收到回调,处理重新调度逻辑。

额外注意事项

  1. 权限处理:Android 13+需要申请POST_NOTIFICATIONS权限,iOS必须提前请求通知权限,建议在应用启动时就发起请求。
  2. 后台可靠性:Android的Doze模式会限制AlarmManager,所以要用SetExactAndAllowWhileIdle;iOS的后台调度受系统限制,尽量保证触发时间的合理性。
  3. 医疗类合规:如果涉及敏感医疗数据,要确保通知内容不泄露敏感信息,只显示必要的提醒内容,详情页再加载完整数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:25:39