Xamarin使用LocalNotifications插件实现页面加载后弹出通知
问题核心原因
你遇到的通知不弹出问题不是单一原因导致的,核心有4个问题:
- 生命周期调用顺序错误:重写
OnAppearing时你把base.OnAppearing()放在了方法末尾,Xamarin.Forms要求重写生命周期方法时必须首先调用基类实现,否则页面未完成可视化树挂载,本地通知插件拿不到平台端的Activity/UIViewController上下文,调用Show方法时会被静默丢弃。 - 调用时机过早:OnAppearing触发时页面还处于渲染过渡阶段,此时同步调用系统通知API会和页面渲染抢占UI线程资源,多数平台下插件会直接跳过执行。
- 异步异常被吞噬:
async void标记的生命周期方法不会向外抛出异常,Show方法内部抛出的上下文无效、权限不足等异常会被直接忽略,表现为代码执行到该行就直接跳过、无任何报错。 - 异步初始化时序不可控:你在ViewModel构造函数里用
Task.Run以fire-and-forget模式跑通知入库逻辑,属于非阻塞调用,虽然测试时能查到数据,但存在概率性的入库未完成就触发查库的问题,会导致偶发漏弹通知。
另外你代码里foreach (Event event in eventList)用了C#保留关键字event作为变量名,也存在隐性解析风险。
分步修复方案
1. 修正OnAppearing方法逻辑
调整基类调用顺序,增加页面渲染等待时间,加上主线程调度和异常捕获,代码如下:
protected override async void OnAppearing() { // 必须第一行调用基类方法,确保页面生命周期正常挂载 base.OnAppearing(); try { // 等待300ms让页面完成渲染、导航栈稳定,避免抢占UI线程 await Task.Delay(300); // 先等待ViewModel完成通知入库逻辑,避免时序竞争 if (BindingContext is MainPageViewModel vm) { await vm.InitializeAsync(); } var notifications = await DatabaseService.db.Table<Notifications>().ToListAsync(); if (notifications.Count > 0) { foreach (Notifications notification in notifications) { // 强制在UI主线程调用通知API,满足平台端线程要求 Device.BeginInvokeOnMainThread(() => { CrossLocalNotifications.Current.Show( "Reminder", $"Event {notification.Name} is {notification.Occurrence} Today on {notification.NotifyDate}" ); }); } } } catch (Exception ex) { // 调试时在这里打断点,可以捕获之前被吞噬的异常 Debug.WriteLine($"Notification show failed: {ex}"); } }
2. 重构ViewModel的异步初始化逻辑
移除构造函数里的fire-and-forget异步调用,改为公开可等待的初始化方法,避免时序不可控问题:
public class MainPageViewModel { private bool _isInitialized; public MainPageViewModel() { // 构造函数里不要直接执行异步逻辑 } /// <summary> /// 页面加载时主动调用的异步初始化方法 /// </summary> public async Task InitializeAsync() { // 加锁避免重复执行初始化 if (_isInitialized) return; _isInitialized = true; await CheckNotifications(); } private async Task CheckNotifications() { await DatabaseService.Init(); var eventList = await DatabaseService.db.Table<Event>().ToListAsync(); if (eventList.Count > 0) { // 注意不要用关键字event作为变量名,改为eventItem foreach (Event eventItem in eventList) { if (eventItem.NotifyStartDate && eventItem.StartDate.Date == DateTime.Today) { Notifications notification = new Notifications { Name = $"{eventItem.Name}", NotifyDate = eventItem.StartDate, Occurrence = "Starting" }; await DatabaseService.AddNotification(notification); } if (eventItem.NotifyEndDate && eventItem.EndDate.Date == DateTime.Today) { Notifications notification = new Notifications { Name = $"{eventItem.Name}", NotifyDate = eventItem.EndDate, Occurrence = "Ending" }; await DatabaseService.AddNotification(notification); } } } } }
3. 确认插件和平台配置
- Android端:在MainActivity的OnCreate方法中确保LocalNotifications插件已完成初始化,Android 13及以上版本必须动态申请
PostNotifications运行时权限,否则通知会被系统拦截。 - iOS端:在AppDelegate的
FinishedLaunching方法中完成插件初始化,首次启动时主动请求用户通知授权,用户同意后才能弹出通知。 - 不要在App类构造函数中执行异步导航,此时应用平台端上下文未完成初始化,异步逻辑会被同步上下文阻塞。
额外注意
async void仅可用于重写生命周期方法,其他场景一律使用async Task作为异步方法返回值,避免异常被吞噬无法排查。- 所有调用系统API、UI相关的逻辑,尽量用
Device.BeginInvokeOnMainThread包裹,确保运行在主线程,避免跨线程调用异常。
内容的提问来源于stack exchange,提问作者user17195990
相关产品推荐
相关产品推荐

