You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:06:02