Flutter实现多任务倒计时器后台持续运行方案求助
问题根源
你当前使用的CountDownController实例是绑定在当前页面的State中的,按下返回键后页面被销毁,State和持有的控制器、定时器实例都会被系统回收,倒计时自然就停止运行了。
实现方案
要实现和页面生命周期解绑的持久倒计时,你需要把定时器的逻辑从页面层抽离,通过全局/跨页面的状态持有者管理任务,再配合前台服务保活,具体步骤如下:
- 第一步:抽象倒计时任务模型,统一管理每个任务的ID、总时长、剩余时长、运行状态、完成回调
- 第二步:创建全局的定时器管理类,独立于页面生命周期运行,统一调度所有倒计时任务
- 第三步:若需要应用退到后台也不被系统杀死,需配置原生端前台服务权限
- 第四步:使用本地通知插件实现计时结束的提醒能力
示例代码
首先在pubspec.yaml中添加所需依赖:
dependencies: flutter: sdk: flutter circular_countdown_timer: ^0.2.3 provider: ^6.1.1 flutter_local_notifications: ^16.1.0
1. 倒计时任务模型与全局管理类
import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // 倒计时任务模型 class CountDownTask { final String taskId; final int totalDuration; int remainingDuration; bool isRunning; final Function? onComplete; CountDownTask({ required this.taskId, required this.totalDuration, required this.remainingDuration, this.isRunning = false, this.onComplete, }); } // 全局倒计时管理类 class CountDownManager extends ChangeNotifier { final Map<String, CountDownTask> tasks = {}; final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); // 单例实例 static final CountDownManager _instance = CountDownManager._internal(); factory CountDownManager() => _instance; CountDownManager._internal() { _initNotifications(); } // 初始化通知 void _initNotifications() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid); await _notificationsPlugin.initialize(initializationSettings); } // 添加新倒计时任务 void addTask(CountDownTask task) { tasks[task.taskId] = task; if (task.isRunning) startTaskTimer(task.taskId); notifyListeners(); } // 启动任务倒计时 void startTaskTimer(String taskId) { final task = tasks[taskId]; if (task == null || !task.isRunning) return; Future.doWhile(() async { await Future.delayed(const Duration(seconds: 1)); if (!tasks.containsKey(taskId) || !task.isRunning || task.remainingDuration <= 0) { if (task.remainingDuration <= 0) { task.onComplete?.call(); _sendCompleteNotification(taskId); } return false; } task.remainingDuration--; notifyListeners(); return true; }); } // 发送完成通知 void _sendCompleteNotification(String taskId) async { const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'countdown_channel', '倒计时提醒', importance: Importance.max, priority: Priority.high, ); const NotificationDetails notificationDetails = NotificationDetails(android: androidDetails); await _notificationsPlugin.show( taskId.hashCode, '倒计时结束', '您的任务倒计时已完成', notificationDetails, ); } // 获取指定任务剩余时长 int getRemainingDuration(String taskId) => tasks[taskId]?.remainingDuration ?? 0; }
2. 页面中使用改造后的倒计时
import 'package:circular_countdown_timer/circular_count_down_timer.dart'; import 'package:flutter/material.dart'; class CountDownPage extends StatefulWidget { const CountDownPage({super.key}); @override State<CountDownPage> createState() => _CountDownPageState(); } class _CountDownPageState extends State<CountDownPage> { final CountDownController _controller = CountDownController(); final String _taskId = "task_001"; // 每个任务对应唯一ID final CountDownManager _manager = CountDownManager(); @override void initState() { super.initState(); // 初始化任务,如果之前已经启动过,直接读取剩余时长 if (_manager.getRemainingDuration(_taskId) <= 0) { _manager.addTask(CountDownTask( taskId: _taskId, totalDuration: 120, remainingDuration: 120, isRunning: false, )); } // 监听全局管理器的状态更新,同步到页面的倒计时控制器 _manager.addListener(() { if (mounted) setState(() {}); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("多任务倒计时")), body: Center( child: CircularCountDownTimer( width: MediaQuery.of(context).size.width / 6, height: MediaQuery.of(context).size.width / 6, duration: _manager.getRemainingDuration(_taskId), initialDuration: 0, fillColor: Colors.green, ringColor: Colors.white, controller: _controller, backgroundColor: Colors.white54, strokeWidth: 10.0, strokeCap: StrokeCap.round, isTimerTextShown: true, isReverse: false, onComplete: () { // 此处回调仅页面存在时触发,全局回调在管理类中配置 }, textStyle: const TextStyle(fontSize: 20.0, color: Colors.black), ), ), floatingActionButton: FloatingActionButton( onPressed: () { final task = _manager.tasks[_taskId]; if (task != null) { task.isRunning = true; _manager.startTaskTimer(_taskId); _controller.start(); } }, child: const Icon(Icons.play_arrow), ), ); } // 拦截返回键,仅退出页面不销毁倒计时任务 @override Future<bool> didPopRoute() async { Navigator.pop(context); return true; } }
后台保活补充配置
如果需要应用退到后台后倒计时也不会被系统终止,你需要额外配置:
- 安卓端:在
AndroidManifest.xml中添加前台服务权限,启动倒计时时启动前台服务,在通知栏展示运行中的通知,避免系统回收应用进程 - iOS端:在Xcode中开启后台模式,勾选
Audio, AirPlay, and Picture in Picture和Background fetch权限,实现后台保活
内容的提问来源于stack exchange,提问作者ARPIT JAISWAL IET Student
相关产品推荐
相关产品推荐

