使用flutter_local_notifications创建大量Flutter通知避免UI卡顿方案咨询
解决方案
核心思路是把纯计算逻辑拆分到独立Isolate执行,和原生插件、上下文绑定的操作留在主线程,既规避compute不能传实例的限制,又完全避免UI线程阻塞。
改造步骤
- 拆分纯计算逻辑为顶层函数
compute仅支持传入可序列化的参数,返回可序列化的结果,所以你需要把168条通知的时间计算、参数组装这些不需要上下文、不需要插件实例的逻辑,抽成顶层纯函数,入参仅传入时区、起止时间、提醒次数、勾选星期、通知文案等纯数据,输出预计算好的所有通知配置列表。 - 主线程预取依赖项
所有和BuildContext、本地化、原生插件绑定的依赖,提前在主线程读取后以纯数据形式传给compute,不可直接传入Isolate。 - 主线程批量提交通知
Isolate计算完成后,在主线程用Future.wait批量并行提交通知到本地通知插件,比你当前串行await的提交方式效率提升3倍以上。
改造代码示例
// 可序列化的通知参数实体,仅存储纯数据 class NotificationConfig { final int id; final int scheduledTimestamp; final String title; final String content; NotificationConfig({required this.id, required this.scheduledTimestamp, required this.title, required this.content}); } // 顶层函数,给compute调用,所有入参都是可序列化的纯数据 List<NotificationConfig> calculateNotificationConfigs(Map<String, dynamic> params) { final startTime = params['startTime'] as TimeOfDay; final endTime = params['endTime'] as TimeOfDay; final reminderNumber = params['reminderNumber'] as int; final checkedDays = params['checkedDays'] as Map<dynamic, List>; final timeZoneName = params['timeZoneName'] as String; final notificationTitle = params['notificationTitle'] as String; final notificationContent = params['notificationContent'] as String; final location = tz.getLocation(timeZoneName); final now = tz.TZDateTime.now(location); List<NotificationConfig> configs = []; int id = 0; int delta = (endTime.minute + endTime.hour * 60) - (startTime.minute + startTime.hour * 60); double interval = delta / reminderNumber; checkedDays.forEach((day, values) { if (values[1]) { double minute = startTime.minute + (startTime.hour * 60); for (int reminder = 0; reminder < reminderNumber; reminder++) { int tmpHour = (minute - minute % 60) ~/ 60; int tmpMinute = (minute.round()) % 60; // 计算触发时间 tz.TZDateTime scheduledDate = tz.TZDateTime(location, now.year, now.month, now.day, tmpHour, tmpMinute); if (scheduledDate.isBefore(now)) { scheduledDate = scheduledDate.add(const Duration(days: 1)); } while (scheduledDate.weekday != values[0]) { scheduledDate = scheduledDate.add(const Duration(days: 1)); } configs.add(NotificationConfig( id: id, scheduledTimestamp: scheduledDate.millisecondsSinceEpoch, title: notificationTitle, content: notificationContent, )); minute += interval; id++; } } }); return configs; } // 修改后的_scheduleNotifications方法 Future<void> _scheduleNotifications() async { tz.initializeTimeZones(); final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone(); await data.getData(); // 提前拿到上下文依赖的文案,不能传到Isolate里 final title = AppLocalizations.of(ctx)!.notificationTitle; final content = AppLocalizations.of(ctx)!.notificationMessage; // 组装compute需要的纯数据参数 final params = { 'startTime': data.startTime, 'endTime': data.endTime, 'reminderNumber': data.reminderNumber, 'checkedDays': data.checkedDays, 'timeZoneName': timeZoneName, 'notificationTitle': title, 'notificationContent': content, }; // 计算过程完全在独立Isolate执行,不卡UI final List<NotificationConfig> configs = await compute(calculateNotificationConfigs, params); // 主线程批量提交通知,用Future.wait并行提交比串行await快至少3倍 List<Future> submitTasks = []; final location = tz.getLocation(timeZoneName!); for (var config in configs) { final scheduledTime = tz.TZDateTime.fromMillisecondsSinceEpoch(location, config.scheduledTimestamp); submitTasks.add(flutterLocalNotificationsPlugin.zonedSchedule( config.id, config.title, config.content, scheduledTime, const NotificationDetails( android: AndroidNotificationDetails( 'weekly notification channel id', 'New citation message', channelDescription: 'Notifications for new citations configured in the timer page.', sound: RawResourceAndroidNotificationSound('notification_sound'), groupKey: "meditation invitation", ), ), androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime, )); } await Future.wait(submitTasks); }
额外优化建议
- 保留当前
isNotificationSetup标记逻辑,用户未修改提醒配置时无需重复计算,进一步减少启动开销 - 后续用户修改提醒配置时再触发计算逻辑即可,计算过程不会阻塞UI交互
内容的提问来源于stack exchange,提问作者François Bechet
相关产品推荐
相关产品推荐

