基于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会收到回调,处理重新调度逻辑。
额外注意事项
- 权限处理:Android 13+需要申请
POST_NOTIFICATIONS权限,iOS必须提前请求通知权限,建议在应用启动时就发起请求。 - 后台可靠性:Android的Doze模式会限制AlarmManager,所以要用
SetExactAndAllowWhileIdle;iOS的后台调度受系统限制,尽量保证触发时间的合理性。 - 医疗类合规:如果涉及敏感医疗数据,要确保通知内容不泄露敏感信息,只显示必要的提醒内容,详情页再加载完整数据。
内容的提问来源于stack exchange,提问作者AugustM
相关产品推荐
相关产品推荐

