求助:如何每日18:30执行后台任务?Xamarin本地通知后台不生效
解决Xamarin中应用未运行时每日本地通知不生效的问题
首先,咱们得明确核心问题:Android 8.0+对后台任务有严格的限制,普通的AlarmManager或者仅在应用运行时触发的逻辑,在应用被杀或未启动时会失效。要实现无论应用是否运行都能每日18:30触发通知,推荐使用Google官方的WorkManager(最可靠),或者调整AlarmManager的使用方式来适配后台限制。
方案一:使用WorkManager(推荐)
WorkManager是专门为需要保证执行的后台任务设计的,即使应用未运行、设备重启,它也会在合适的时机触发任务,完美适配Android的后台限制。
步骤1:创建Worker类
这个类负责执行发送通知的逻辑:
using Android.App; using Android.Content; using Android.OS; using AndroidX.Core.App; using AndroidX.Work; public class DailyNotificationWorker : Worker { public DailyNotificationWorker(Context context, WorkerParameters workerParams) : base(context, workerParams) { } public override Result DoWork() { SendDailyNotification(); return Result.Success; } private void SendDailyNotification() { const string channelId = "daily_reminder_channel"; var notificationManager = NotificationManagerCompat.From(ApplicationContext); // Android 8.0+必须创建通知渠道 if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { var channel = new NotificationChannel( channelId, "每日提醒", NotificationImportance.Default) { Description = "每日18:30自动发送的通知" }; notificationManager.CreateNotificationChannel(channel); } // 点击通知打开主页面 var launchIntent = ApplicationContext.PackageManager.GetLaunchIntentForPackage(ApplicationContext.PackageName); var pendingIntent = PendingIntent.GetActivity( ApplicationContext, 0, launchIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable); // 构建通知 var notification = new NotificationCompat.Builder(ApplicationContext, channelId) .SetSmallIcon(Resource.Drawable.ic_notification) // 替换成你的图标 .SetContentTitle("每日提醒") .SetContentText("这是你设置的每日18:30通知") .SetPriority(NotificationCompat.PriorityDefault) .SetContentIntent(pendingIntent) .SetAutoCancel(true) .Build(); notificationManager.Notify(1001, notification); } }
步骤2:调度每日18:30的任务
在你的主Activity或者启动页中,添加调度逻辑,计算首次执行的延迟时间,然后设置周期性任务:
using AndroidX.Work; private void ScheduleDailyNotificationTask() { var now = DateTime.Now; var targetTime = new DateTime(now.Year, now.Month, now.Day, 18, 30, 0); // 如果当前时间已过18:30,就把目标时间设为明天的18:30 if (now > targetTime) { targetTime = targetTime.AddDays(1); } // 计算从现在到目标时间的延迟 var initialDelay = targetTime - now; // 创建周期性工作请求:每隔24小时重复一次,首次延迟initialDelay var workRequest = new PeriodicWorkRequest.Builder( typeof(DailyNotificationWorker), TimeSpan.FromHours(24)) .SetInitialDelay(initialDelay) .Build(); // 唯一任务:如果已有相同任务,替换掉旧的 WorkManager.Instance.EnqueueUniquePeriodicWork( "DailyNotificationTask", ExistingPeriodicWorkPolicy.Replace, workRequest); }
步骤3:处理通知权限(Android 13+)
Android 13及以上需要主动请求POST_NOTIFICATIONS权限,在主Activity的OnCreate中添加:
using Android.Content.PM; using AndroidX.Core.App; using AndroidX.Core.Content; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // 请求通知权限 if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu) { if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.PostNotifications) != Permission.Granted) { ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.PostNotifications }, 1002); } } // 调度任务 ScheduleDailyNotificationTask(); }
方案二:使用AlarmManager(兼容旧版本)
如果你坚持用AlarmManager,需要解决两个核心问题:静态注册广播接收器和使用精确闹钟适配后台限制。
步骤1:静态注册广播接收器
在AndroidManifest.xml中注册接收器(应用未运行时只有静态注册的接收器能被唤醒):
<receiver android:name=".NotificationAlarmReceiver" android:exported="false"> <intent-filter> <action android:name="com.yourpackage.action.DAILY_NOTIFICATION" /> </intent-filter> </receiver>
步骤2:实现广播接收器
在接收器中发送通知,并重新设置下一次的闹钟(因为Android 7.0+不支持精确重复闹钟):
using Android.App; using Android.Content; using AndroidX.Core.App; [BroadcastReceiver(Enabled = true)] public class NotificationAlarmReceiver : BroadcastReceiver { public override void OnReceive(Context context, Intent intent) { // 发送通知 SendNotification(context); // 重新设置下一次的闹钟 SetNextDailyAlarm(context); } private void SendNotification(Context context) { // 这里的逻辑和WorkManager中的SendDailyNotification一致,直接复用即可 const string channelId = "daily_reminder_channel"; var notificationManager = NotificationManagerCompat.From(context); if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { var channel = new NotificationChannel(channelId, "每日提醒", NotificationImportance.Default) { Description = "每日18:30自动发送的通知" }; notificationManager.CreateNotificationChannel(channel); } var launchIntent = context.PackageManager.GetLaunchIntentForPackage(context.PackageName); var pendingIntent = PendingIntent.GetActivity(context, 0, launchIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable); var notification = new NotificationCompat.Builder(context, channelId) .SetSmallIcon(Resource.Drawable.ic_notification) .SetContentTitle("每日提醒") .SetContentText("这是你设置的每日18:30通知") .SetPriority(NotificationCompat.PriorityDefault) .SetContentIntent(pendingIntent) .SetAutoCancel(true) .Build(); notificationManager.Notify(1001, notification); } private void SetNextDailyAlarm(Context context) { var alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService); var intent = new Intent(context, typeof(NotificationAlarmReceiver)); intent.SetAction("com.yourpackage.action.DAILY_NOTIFICATION"); var pendingIntent = PendingIntent.GetBroadcast(context, 0, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable); var now = DateTime.Now; var targetTime = new DateTime(now.Year, now.Month, now.Day, 18, 30, 0).AddDays(1); var targetMillis = (long)(targetTime - new DateTime(1970, 1, 1)).TotalMilliseconds; // Android 6.0+使用setExactAndAllowWhileIdle,保证在Doze模式下也能触发 if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, targetMillis, pendingIntent); } else { alarmManager.SetExact(AlarmType.RtcWakeup, targetMillis, pendingIntent); } } }
步骤3:初始化首次闹钟
在主Activity的OnCreate中调用初始化方法:
private void InitializeDailyAlarm() { var alarmManager = (AlarmManager)GetSystemService(AlarmService); var intent = new Intent(this, typeof(NotificationAlarmReceiver)); intent.SetAction("com.yourpackage.action.DAILY_NOTIFICATION"); var pendingIntent = PendingIntent.GetBroadcast(this, 0, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable); var now = DateTime.Now; var targetTime = new DateTime(now.Year, now.Month, now.Day, 18, 30, 0); if (now > targetTime) { targetTime = targetTime.AddDays(1); } var targetMillis = (long)(targetTime - new DateTime(1970, 1, 1)).TotalMilliseconds; if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, targetMillis, pendingIntent); } else { alarmManager.SetExact(AlarmType.RtcWakeup, targetMillis, pendingIntent); } }
关键注意事项
- 应用被强制停止:无论是WorkManager还是AlarmManager,当用户在系统设置中强制停止应用后,所有任务都会被取消,这是Android的系统限制,无法绕过,只能提示用户不要强制停止应用。
- Doze模式适配:WorkManager会自动在系统维护窗口执行任务;AlarmManager的
setExactAndAllowWhileIdle会在设备空闲时唤醒,但每9分钟只能触发一次,不过每日一次的场景完全不受影响。 - 权限声明:确保在
AndroidManifest.xml中声明必要的权限(如RECEIVE_BOOT_COMPLETED如果需要开机自启,不过WorkManager已经支持开机自启)。
内容的提问来源于stack exchange,提问作者Aswathi PS
相关产品推荐
相关产品推荐

