创建资源容易,忘记释放是常态。本文汇总 E-Brufen 中所有 dispose 场景,形成可复用的资源释放清单。
一、E-Brufen 中的五类资源
| 资源类型 | 示例 | 释放方法 |
|---|---|---|
| AnimationController | _pulse, _controller | .dispose() |
| Timer | _timer, _countdown | .cancel() |
| TextEditingController | _noteController | .dispose() |
| TabController | _tabController | .dispose() |
| ChangeNotifier listener | addListener 回调 | .removeListener() |
| Hive Box | _box | .close() |
二、DiaryPage:最复杂的 dispose
@overridevoiddispose(){widget.moodStorage.removeListener(_loadMoods);// 1. 解绑监听_tabController.dispose();// 2. 释放 Tab 控制器_noteController.dispose();// 3. 释放文本控制器super.dispose();// 4. 调用父类}释放顺序原则:先释放依赖方,再释放被依赖方。
removeListener必须先执行——因为_loadMoods中使用了setState,如果 State 已经被父类 dispose 了还触发 setState,就会崩溃。
三、BreathePage:Timer + AnimationController
// breathe_page.dart — 没有显式的 dispose(因为 BreathingCircle 是子 Widget)// breathing_circle.dart@overridevoiddispose(){_timer?.cancel();// 1. 先取消 Timer(防止回调触发)_controller.dispose();// 2. 再释放 Controllersuper.dispose();}
★ Insight ─────────────────────────────────────_timer?.cancel()在_controller.dispose()之前执行是有意的顺序。如果先 dispose Controller,而 Timer 恰好在这之后触发(竞争条件),回调中会尝试操作已销毁的 Controller → 崩溃。先取消 Timer 消除了这个竞争窗口。─────────────────────────────────────────────────
四、SoundscapePage:双异步资源
@overridevoiddispose(){_pulse.dispose();// AnimationController_countdown?.cancel();// Timersuper.dispose();}五、MoodStorage:数据层资源
// mood_storage.dart@overridevoiddispose(){_box?.close();// 关闭 Hive Box,释放文件句柄super.dispose();}Hive Box 不关闭会导致文件句柄泄漏——虽然应用退出时 OS 会回收,但在长时间运行的应用中可能累积。
六、AppSettings:同样需要
// settings.dartvoiddispose(){_box?.close();}七、资源释放检查清单
在 code review 时,对每个 StatefulWidget 检查:
□ 有 AnimationController? → .dispose() □ 有 Timer? → .cancel() □ 有 TextEditingController? → .dispose() □ 有 TabController? → .dispose() □ 有 addListener? → .removeListener() □ 有 FocusNode? → .dispose() □ 有 ScrollController? → .dispose() □ 有 StreamSubscription? → .cancel()八、常见遗漏模式
遗漏 1:initState 中注册但忘记 dispose
// ❌ 常见错误@overridevoidinitState(){super.initState();widget.model.addListener(_onChange);// 忘记在 dispose 中 removeListener}遗漏 2:条件创建忘记条件释放
Timer?_timer;voidstart(){_timer=Timer.periodic(...);}@overridevoiddispose(){_timer?.cancel();// ✅ 使用 ?. 安全操作符super.dispose();}小结
资源释放是一个"对称性"问题——每一个 create/register/add/open 都需要对应的 dispose/cancel/remove/close。E-Brufen 的代码中,这个对称性保持得相当好。把它变成习惯,而不是事后补救。
作者简介:E-Brufen Dev,Flutter & 鸿蒙开发者,专注于跨平台移动应用开发与心理健康数字化,项目地址:AtomGit - E-Brufen。