创建资源容易,忘记释放是常态。本文汇总 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

在这里插入图片描述


void dispose() {
  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

void dispose() {
  _timer?.cancel();         // 1. 先取消 Timer(防止回调触发)
  _controller.dispose();    // 2. 再释放 Controller
  super.dispose();
}

★ Insight ─────────────────────────────────────
_timer?.cancel()_controller.dispose() 之前执行是有意的顺序。如果先 dispose Controller,而 Timer 恰好在这之后触发(竞争条件),回调中会尝试操作已销毁的 Controller → 崩溃。先取消 Timer 消除了这个竞争窗口。
─────────────────────────────────────────────────

四、SoundscapePage:双异步资源


void dispose() {
  _pulse.dispose();         // AnimationController
  _countdown?.cancel();     // Timer
  super.dispose();
}

五、MoodStorage:数据层资源

// mood_storage.dart

void dispose() {
  _box?.close();  // 关闭 Hive Box,释放文件句柄
  super.dispose();
}

Hive Box 不关闭会导致文件句柄泄漏——虽然应用退出时 OS 会回收,但在长时间运行的应用中可能累积。

六、AppSettings:同样需要

// settings.dart
void dispose() {
  _box?.close();
}

七、资源释放检查清单

在 code review 时,对每个 StatefulWidget 检查:

□ 有 AnimationController?    → .dispose()
□ 有 Timer?                  → .cancel()
□ 有 TextEditingController?  → .dispose()
□ 有 TabController?          → .dispose()
□ 有 addListener?            → .removeListener()
□ 有 FocusNode?              → .dispose()
□ 有 ScrollController?       → .dispose()
□ 有 StreamSubscription?     → .cancel()

八、常见遗漏模式

遗漏 1:initState 中注册但忘记 dispose

// ❌ 常见错误

void initState() {
  super.initState();
  widget.model.addListener(_onChange);
  // 忘记在 dispose 中 removeListener
}

遗漏 2:条件创建忘记条件释放

Timer? _timer;

void start() {
  _timer = Timer.periodic(...);
}


void dispose() {
  _timer?.cancel();  // ✅ 使用 ?. 安全操作符
  super.dispose();
}

小结

资源释放是一个"对称性"问题——每一个 create/register/add/open 都需要对应的 dispose/cancel/remove/close。E-Brufen 的代码中,这个对称性保持得相当好。把它变成习惯,而不是事后补救。


作者简介:E-Brufen Dev,Flutter & 鸿蒙开发者,专注于跨平台移动应用开发与心理健康数字化,项目地址:AtomGit - E-Brufen

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐