Flutter使用Timer定时拉取数据如何解决报错与内存泄漏
问题描述
- 需求:不退出重进页面的前提下,定时从数据库拉取供应商列表并刷新展示
- 初始实现:在
initState生命周期中创建周期性定时器触发拉取逻辑,初始代码如下:
void initState() { super.initState(); Timer.periodic(timeDelay, (Timer t) => fetchFournisseurs()); }
- 异常现象:功能可正常运行,但调试控制台抛出如下报错:
State.setState.<anonymous closure> (package:flutter/src/widgets/fr<…> [VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: setState() called after dispose(): _listeDocumentState#c0f9f(lifecycle state: defunct, not mounted) This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree. This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
- 核心问题:页面执行
dispose销毁后仍被定时器回调触发setState(),存在内存泄漏风险,需要可规避潜在问题的更优实现方案。
问题根因
Timer.periodic创建的是独立于Widget生命周期的周期性定时任务,创建后如果没有主动调用cancel()终止,会一直按照设定间隔触发回调。当页面被销毁、对应State对象已经从组件树移除后,定时器仍然持有State的引用,回调中执行的fetchFournisseurs方法内部会调用setState刷新UI,就会触发上述报错,同时被持有的State引用无法被GC回收,造成内存泄漏。
标准修复方案
按照Flutter生命周期规范做三层防护,彻底规避问题:
- 将定时器实例声明为State类的成员变量,方便后续统一管理销毁
- 定时器回调执行拉取逻辑前,先判断
mounted属性,确认组件仍在组件树中才执行后续逻辑 - 重写
dispose生命周期方法,在页面销毁时主动取消定时器,释放资源、切断引用
完整实现代码如下:
class _ListeDocumentState extends State<ListeDocument> { // 声明定时器成员变量 late Timer _supplierFetchTimer; // 定时拉取间隔,可根据业务需求调整 final Duration fetchInterval = const Duration(seconds: 30); // 请求锁,避免上一次拉取未完成时重复触发请求 bool _isFetching = false; @override void initState() { super.initState(); // 初始化定时器 _supplierFetchTimer = Timer.periodic(fetchInterval, (Timer timer) async { // 第一层防护:组件已销毁直接返回 if (!mounted) return; // 避免重复请求 if (_isFetching) return; _isFetching = true; try { await fetchFournisseurs(); } finally { _isFetching = false; } }); } @override void dispose() { // 第二层防护:页面销毁时主动取消定时器,彻底切断引用 _supplierFetchTimer.cancel(); super.dispose(); } Future<void> fetchFournisseurs() async { // 原有数据库拉取逻辑 // 注意:所有setState调用前必须判断mounted if (!mounted) return; setState(() { // 更新供应商列表数据 }); } }
可选优化项
- 应用前后台切换适配:混入
WidgetsBindingObserver监听应用生命周期,应用切到后台时取消定时器、回到前台时重建定时器,减少无意义的数据库请求和性能消耗 - 拉取错误处理:给
fetchFournisseurs加异常捕获,避免接口/数据库报错导致定时器后续回调中断 - 首次拉取优化:initState中创建完定时器后可以手动触发一次
fetchFournisseurs(),不用等第一个定时周期到了才加载首屏数据
内容的提问来源于stack exchange,提问作者NewbieDeveloper
相关产品推荐
相关产品推荐

