Skip to content

今天又被 Flutter 的 dispose 给上了一课

写代码的时候,蹦出来一个看着很简单,但又很奇怪的报错:

_SmbFilePageState.dispose failed to call super.dispose

我当时就纳闷了,这报错的意思是“dispose 方法里忘了调用 super.dispose()”。

可我代码明明是这么写的,最后一行清清楚楚地调用了 super.dispose() 啊!

dart
// 让我头疼的“问题”代码
@override
void dispose() async { // <-- 注意这个 async
  print("开始释放资源...");
  await _someAsyncCleanup(); // 比如关闭一个网络连接、释放一个文件句柄
  print("资源释放完毕!");

  super.dispose(); // <-- 我明明调用了啊!凭啥说我没调?
}

这不欺负老实人吗?


问题到底出在哪?

经过一番折腾和请教,终于搞明白了。问题就出在 async 关键字上

dispose() 是 Flutter Widget 生命周期里的一个关键环节,它的设计初衷是同步、快速地释放资源

当你给 dispose() 方法加上 async 关键字后,它的返回值就不再是 void,而变成了一个 Future<void>

这时候,整个执行流程就变了:

  1. Flutter 框架调用你的 dispose() 方法。
  2. 你的代码开始执行,遇到第一个 await(比如 await _someAsyncCleanup())。
  3. 关键点来了:在 await 这里,你的 dispose() 方法会“暂停”执行,并立即返回一个 Future 对象给 Flutter 框架。
  4. Flutter 框架拿到这个 Future 后,并不会等它执行完。它会立刻进行下一步检查:“你调用 super.dispose() 了吗?”
  5. 因为你的代码还在 await 那里等着呢,super.dispose() 那一行根本就没机会执行到。所以,Flutter 框架的检查就失败了,然后就抛出了那个“failed to call super.dispose”的异常。

用大白话讲就是:

你妈让你出门前把垃圾倒了。你回了句“好嘞”(返回 Future),然后转身去穿鞋、打领带(await)。你妈一回头,看垃圾还在原地(检查 super.dispose),二话不说就先把你揍了一顿。


那该怎么改?

核心原则:dispose() 方法本身必须保持同步,不能是 async

那如果我真的有一个异步的清理任务要执行怎么办?

很简单,不要在 disposeawait

dart
// 正确的写法
@override
void dispose() {
  // 如果有个异步任务需要启动,直接调用,别等它
  _someAsyncCleanup(); // 这叫 "Fire and Forget"(发射后不管)

  // 其他同步的清理工作放这里
  _someController.dispose();

  // 最后,同步调用 super.dispose()
  super.dispose();
}

Future<void> _someAsyncCleanup() async {
  // 这里执行你耗时的异步清理
  await Future.delayed(Duration(seconds: 1));
  print("后台的异步任务终于执行完了");
}

这样修改后,dispose 方法会瞬间执行完毕,Flutter 框架的检查也能顺利通过。而那个异步的清理任务会在后台自己默默执行,不影响 dispose 的生命周期。


总结

记住这个铁律:

  1. dispose() 方法永远不要标记为 async
  2. 不要在 dispose() 方法内部使用 await
  3. 如果你有异步清理任务,直接调用它,让它在后台运行即可,不要阻塞 dispose 的执行。

Flutter 的生命周期方法有很多“规矩”,我们还是得老老实实遵守。希望这个坑能帮大家节省点时间!