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

如何在Flutter(Android/iOS)实现无Firebase/后端的实时倒计时本地通知

本地通知实时倒计时实现方案(Flutter)

可行性说明

仅通过Flutter本地插件完全可以实现通知内的实时倒计时,核心依赖flutter_local_notifications配合后台定时任务插件(如flutter_workmanager)即可完成,无需Firebase或外部后端服务。但Android和iOS的通知机制差异较大,需要针对性处理。

实现步骤

1. 依赖配置

在pubspec.yaml中添加所需插件:

dependencies:
  flutter_local_notifications: ^16.1.0  # 本地通知核心插件
  flutter_workmanager: ^0.5.1            # 后台定时任务调度
  intl: ^0.18.1                          # 时间格式化工具

2. 初始化设置

通知插件初始化

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_workmanager/flutter_workmanager.dart';
import 'package:intl/intl.dart';

final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();

Future<void> initNotifications() async {
  // Android初始化:指定通知图标
  const AndroidInitializationSettings androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
  // iOS初始化:申请必要权限
  const DarwinInitializationSettings iosInit = DarwinInitializationSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );
  
  const InitializationSettings initSettings = InitializationSettings(android: androidInit, iOS: iosInit);
  await _notificationsPlugin.initialize(initSettings);
}

后台任务初始化

void initWorkManager() {
  FlutterWorkmanager().initialize(
    _countdownUpdateCallback,
    isInDebugMode: false,
  );
}

// 后台任务回调:负责更新通知内容
void _countdownUpdateCallback() {
  FlutterWorkmanager().executeTask((taskName, inputData) async {
    if (taskName != 'update_countdown') return Future.value(false);
    
    final int endTimeMs = inputData!['endTimeMs'];
    final int notificationId = inputData['notificationId'];
    final DateTime endTime = DateTime.fromMillisecondsSinceEpoch(endTimeMs);
    
    // 更新当前倒计时通知
    await _updateCountdownNotification(notificationId, endTime);
    
    // 倒计时未结束则继续调度下一次更新
    if (DateTime.now().isBefore(endTime)) {
      await FlutterWorkmanager().registerOneOffTask(
        'update_countdown_$notificationId${DateTime.now().millisecondsSinceEpoch}',
        'update_countdown',
        inputData: {
          'endTimeMs': endTimeMs,
          'notificationId': notificationId,
        },
        initialDelay: const Duration(seconds: 1),
        constraints: const Constraints(networkType: NetworkType.not_required),
      );
    } else {
      // 倒计时结束,推送完成通知
      await _notificationsPlugin.show(
        notificationId,
        '倒计时结束',
        '任务已完成',
        const NotificationDetails(
          android: AndroidNotificationDetails('countdown_channel', '倒计时通知'),
        ),
      );
    }
    return Future.value(true);
  });
}

3. 核心倒计时更新逻辑

// 更新通知内容的方法
Future<void> _updateCountdownNotification(int notificationId, DateTime endTime) async {
  final DateTime now = DateTime.now();
  if (now.isAfter(endTime)) return;
  
  final Duration remaining = endTime.difference(now);
  final String formattedTime = DateFormat('mm:ss').format(DateTime(0).add(remaining));
  
  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    'countdown_channel',
    '倒计时通知',
    channelDescription: '实时更新的倒计时提醒',
    priority: Priority.high,
    importance: Importance.high,
    ongoing: true, // 设为持续通知,避免被用户滑动清除
    showWhen: false,
  );
  
  const DarwinNotificationDetails iosDetails = DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: false,
    presentSound: false,
  );
  
  await _notificationsPlugin.show(
    notificationId,
    '倒计时进行中',
    '剩余时间:$formattedTime',
    const NotificationDetails(android: androidDetails, iOS: iosDetails),
  );
}

// 启动倒计时通知的入口
Future<void> startCountdown(int notificationId, Duration duration) async {
  final DateTime endTime = DateTime.now().add(duration);
  // 先显示初始通知
  await _updateCountdownNotification(notificationId, endTime);
  // 调度第一次后台更新任务
  await FlutterWorkmanager().registerOneOffTask(
    'update_countdown_$notificationId',
    'update_countdown',
    inputData: {
      'endTimeMs': endTime.millisecondsSinceEpoch,
      'notificationId': notificationId,
    },
    initialDelay: const Duration(seconds: 1),
    constraints: const Constraints(networkType: NetworkType.not_required),
  );
}

平台特定限制与解决办法

Android

  • 限制:Android 12+对后台任务调度频率有严格限制,每秒更新可能被系统节流,导致更新延迟。
  • 解决办法:
    • 将通知设为ongoing: true,提升后台任务优先级,降低被节流的概率。
    • Android 13+必须申请POST_NOTIFICATIONS权限,否则通知无法显示。
    • 非必须精确到秒的场景,可将更新间隔调整为2-5秒,平衡实时性与资源消耗;应用前台时仍保持每秒更新。

iOS

  • 限制:
    • 后台任务调度精度低,flutter_workmanager的延迟可能有1-2秒误差。
    • 应用进入后台后,系统可能暂停后台任务,导致倒计时更新中断。
  • 解决办法:
    • 使用UNNotificationServiceExtension(原生Swift扩展)实现通知内容实时更新,无需依赖Flutter后台任务,但需编写少量原生代码(仍属于本地实现)。
    • 短时间倒计时(如10分钟内)可在前台预先调度多个更新任务,或利用iOS的Timer在后台存活期内更新(但后台存活时间有限)。
    • 设置通知的interruptionLevel: .timeSensitive,提升通知优先级,减少被系统暂停的概率。

关键注意事项

  • 通知渠道:Android需提前创建countdown_channel,确保通知能正常展示。
  • 权限申请:iOS需在Info.plist中添加通知权限描述,Android 13+动态申请POST_NOTIFICATIONS权限。
  • 资源消耗:频繁更新通知会增加电量消耗,建议根据需求调整更新频率。
  • 应用被杀场景:若用户手动杀死应用,后台任务会停止,倒计时无法继续更新。Android可通过原生AlarmManager实现更持久的唤醒,iOS则无有效解决办法。

内容的提问来源于stack exchange,提问作者maulik nagvadiya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 00:59:56