Flutter Provider场景下Timer定时器无法取消问题咨询
问题解决方案
核心问题根因
- Dart值传递特性导致Timer实例未更新:Dart中参数按值传递,你在
toggleCompanyTimer方法内给传入的timer参数赋值Timer.periodic,仅修改了方法内的局部变量,外部存储的timer实例完全没有更新,点击stop时拿到的还是初始的空Timer甚至null - Timer实例定义位置错误:你把
Timer timer1定义在build方法内,每次页面触发重绘都会重新创建一个全新的空Timer实例,之前启动的Timer实例直接丢失,根本拿不到用来取消 - showNotification参数传递错误:调用
toggleTimer时你直接写了showNotification('title', 'body'),这是直接执行该方法,把返回的Future对象传给了定时器回调,定时器要执行时尝试调用这个Future就会报错。
分步修复方案
第一步:把Timer实例托管到StrategyProvider中
不要在页面层或者组件层存储Timer,直接放到Provider里统一管理,避免实例丢失,修改StrategyProvider.dart代码:
class StrategyProvider with ChangeNotifier { // 切换状态 bool _companyStratOneToggle = false; bool get companyStratOneToggle => _companyStratOneToggle; // 新增:存储当前生效的Timer实例 Timer? _companyStratOneTimer; ChartLiveData _companyChartLiveData = ChartLiveData( ... ); ChartLiveData get companyChartLiveData => _companyChartLiveData; // 修改:不再接收外部timer参数,直接操作内部存储的Timer实例 toggleCompanyTimer(ref, Function showNotification, bool toggle) { if (toggle == false) { // 先取消之前可能存在的Timer避免重复启动 _companyStratOneTimer?.cancel(); _companyStratOneTimer = Timer.periodic( const Duration(seconds: 5), (Timer t) { fetchCompanyLiveStockData( ref, showNotification, ); }, ); } else { _companyStratOneTimer?.cancel(); _companyStratOneTimer = null; print("Timer Canceled!"); } } // 不同策略的切换状态设置方法 setCompanyStratOneToggle() { _companyStratOneToggle = !_companyStratOneToggle; notifyListeners(); } // 可选:页面销毁时调用清理Timer void disposeTimer() { _companyStratOneTimer?.cancel(); _companyStratOneTimer = null; } }
第二步:修改主页面代码,删除build内的Timer定义
class StrategyOneScreen extends StatefulWidget { const StrategyOneScreen({Key? key}) : super(key: key); @override State<StrategyOneScreen> createState() => _StrategyOneScreenState(); } class _StrategyOneScreenState extends State<StrategyOneScreen> { @override void dispose() { // 页面销毁时清理Timer避免内存泄漏 Provider.of<StrategyProvider>(context, listen: false).disposeTimer(); super.dispose(); } @override Widget build(BuildContext context) { // 删掉这里的Timer定义,不需要再传Timer给StrategyScreen return StrategyScreen( stratName: 'Free Strategy', toggle: Provider.of<StrategyProvider>( context, listen: true, ).companyStratOneToggle, toggleTimer: Provider.of<StrategyProvider>( context, listen: false, ).toggleCompanyTimer, setToggle: Provider.of<StrategyProvider>( context, listen: false, ).setCompanyStratOneToggle, chartLiveData: Provider.of<StrategyProvider>( context, listen: true, ).companyChartLiveData, ); } }
第三步:修改StrategyScreen组件的参数和按钮点击逻辑
- 删掉StrategyScreen构造函数里的timer参数
- 修改按钮点击事件,把showNotification改成函数传递,不要直接执行:
class StrategyScreen extends StatefulWidget { const StrategyScreen({ Key? key, required this.stratName, required this.toggle, required this.toggleTimer, required this.setToggle, required this.chartLiveData, }) : super(key: key); final String stratName; final bool toggle; final Function toggleTimer; final Function setToggle; final ChartLiveData chartLiveData; @override _StrategyScreenState createState() => _StrategyScreenState(); } class _StrategyScreenState extends State<StrategyScreen> { @override Widget build(BuildContext context) { return Scaffold( ... Row( children: [ Expanded( child: Center( child: FloatingActionButton.extended( heroTag: 'btn1', onPressed: widget.toggle == false ? () => { widget.toggleTimer( ref, // 改成函数包裹,不要直接执行showNotification () => showNotification('title', 'body'), widget.toggle, ), widget.setToggle(), } : null, label: const Text('Start'), icon: const Icon(Icons.launch), backgroundColor: Colors.greenAccent, ), ), ), Expanded( child: Center( child: FloatingActionButton.extended( heroTag: 'btn2', onPressed: widget.toggle ? () => { widget.toggleTimer( ref, () => showNotification('title', 'body'), widget.toggle, ), widget.setToggle(), } : null, label: const Text('Stop'), icon: const Icon(Icons.stop), backgroundColor: Colors.pink, ), ), ), ], ),
额外优化点
- 多个策略可以在Provider里用Map存储不同策略对应的Timer实例,不用每个策略单独写变量
- 启动Timer前先判断当前Timer是否活跃,避免重复创建多个定时器
- 页面
dispose生命周期必须调用Timer的cancel方法,避免内存泄漏和后台继续执行请求
内容的提问来源于stack exchange,提问作者dev1ce
相关产品推荐
相关产品推荐

