Flutter Alarm包周重复闹钟失效及后台不响问题排查
Flutter Alarm Package 周重复闹钟问题解决方案(Android)
一、确保应用被杀后每周选中日期闹钟正常触发
申请精确闹钟权限
Android 12及以上系统要求SCHEDULE_EXACT_ALARM权限才能触发无延迟的精确闹钟,先在AndroidManifest.xml中添加权限声明:<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />再在Flutter代码中动态申请权限:
import 'package:permission_handler/permission_handler.dart'; Future<void> requestExactAlarmPermission() async { final status = await Permission.scheduleExactAlarm.request(); if (!status.isGranted) { openAppSettings(); } }注册开机与应用更新广播接收器
系统重启或应用更新后,所有已设置的闹钟会被清除,需在开机后重新加载重复规则并设置闹钟。在AndroidManifest.xml中注册广播接收器:<receiver android:name=".AlarmBootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> </intent-filter> </receiver>创建
AlarmBootReceiver类,在onReceive方法中读取SharedPreferences保存的重复日期,重新计算并设置闹钟:class AlarmBootReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == Intent.ACTION_BOOT_COMPLETED || intent?.action == Intent.ACTION_MY_PACKAGE_REPLACED) { val prefs = context?.getSharedPreferences("alarm_prefs", Context.MODE_PRIVATE) val selectedDays = prefs?.getStringSet("selected_days", emptySet()) ?: emptySet() // 通过MethodChannel调用Flutter端的闹钟设置逻辑 } } }启用前台服务模式
将响铃服务设为前台服务,避免系统在后台杀掉进程。设置闹钟时开启相关配置:await Alarm.set( alarmSettings: AlarmSettings( id: alarmId, dateTime: nextTriggerTime, notificationTitle: "闹钟提醒", notificationBody: "该起床了!", enableNotificationOnKill: true, foregroundMode: true, ), );引导用户关闭电池优化
国产ROM的电池优化会强制清理后台进程,需引导用户将应用加入白名单:Future<void> requestIgnoreBatteryOptimizations() async { final status = await Permission.ignoreBatteryOptimizations.request(); if (!status.isGranted) { openAppSettings(); } }
二、避免停止闹钟后删除重复规则,确保后续日期正常触发
核心逻辑:不依赖alarm package的重复机制,手动维护重复规则,每次响铃结束后自动设置下一次闹钟
分离重复规则与闹钟实例
将用户选中的重复日期(如["FRIDAY", "SATURDAY"])持久化存储在SharedPreferences中,不要与单个闹钟实例绑定。修改响铃页面的停止逻辑
用户点击停止响铃时,先计算下一个触发日期并设置新闹钟,再停止当前响铃:// 计算下一次触发日期 DateTime calculateNextTriggerTime(Set<String> selectedDays, DateTime currentAlarmTime) { DateTime now = DateTime.now(); DateTime nextTime = DateTime(now.year, now.month, now.day); bool found = false; // 遍历一周找最近的符合条件的日期 for (int i = 0; i < 7; i++) { DateTime candidate = nextTime.add(Duration(days: i)); String dayName = DateFormat('EEEE').format(candidate).toUpperCase(); if (selectedDays.contains(dayName) && candidate.isAfter(now)) { nextTime = DateTime( candidate.year, candidate.month, candidate.day, currentAlarmTime.hour, currentAlarmTime.minute ); found = true; break; } } // 本周无符合日期则取下周第一个目标日期 if (!found) { for (String day in selectedDays) { int targetWeekday = _getWeekdayNumber(day); int daysToAdd = (targetWeekday - now.weekday + 7) % 7; if (daysToAdd == 0) daysToAdd = 7; nextTime = now.add(Duration(days: daysToAdd)); nextTime = DateTime( nextTime.year, nextTime.month, nextTime.day, currentAlarmTime.hour, currentAlarmTime.minute ); break; } } return nextTime; } // 星期名称转数字(1=周一,7=周日) int _getWeekdayNumber(String dayName) { switch (dayName) { case "MONDAY": return 1; case "TUESDAY": return 2; case "WEDNESDAY": return 3; case "THURSDAY": return 4; case "FRIDAY": return 5; case "SATURDAY": return 6; case "SUNDAY": return 7; default: return 1; } } // 停止闹钟并设置下一次 Future<void> stopAlarmAndSetNext(int currentAlarmId, DateTime currentAlarmTime) async { SharedPreferences prefs = await SharedPreferences.getInstance(); Set<String> selectedDays = prefs.getStringSet("selected_days") ?? {}; if (selectedDays.isNotEmpty) { DateTime nextTime = calculateNextTriggerTime(selectedDays, currentAlarmTime); await Alarm.set( alarmSettings: AlarmSettings( id: currentAlarmId, dateTime: nextTime, notificationTitle: "闹钟提醒", notificationBody: "该起床了!", enableNotificationOnKill: true, foregroundMode: true, ), ); } await Alarm.stop(currentAlarmId); }禁止主动删除闹钟实例
仅在用户主动取消整个重复规则时调用Alarm.delete(),停止响铃仅使用Alarm.stop(),避免删除重复逻辑的载体。
内容的提问来源于stack exchange,提问作者Nafis Ligari
相关产品推荐
相关产品推荐

