SMARTWATCH 学习手册有 C 基础 · 嵌入式从零开始
支线三 / Dart 与 Flutter App / 04

等待设备时,界面怎样继续响应

先观察一次延迟结果,再接收连续数据。把 Future、Stream 和回调放在同一条事件顺序中,避免把 async 当成另建任务。

接续上一篇:按钮按下后,为什么修改变量还不够

1 · 同步循环为什么会挡住界面

页面需要响应触摸、执行回调并安排画面更新。如果在这条执行路径上写一个长时间不结束的计算循环,后面的事件只能继续等。给函数加 async 并不会自动把长计算搬到另一个线程。

Dart 使用事件循环处理这类异步事件。等待设备、文件等异步结果时,当前异步函数可以暂停,让其他事件有机会处理;可用的后台执行机制称为 isolate,具有自己的执行和内存关系。本支线先把收发与等待写清楚,计算量真有需求时再学习它。

2 · 一个将来的结果:Future

完整 Dart 片段 · 延迟只用于观察顺序

Future<int> readLater() async {
  await Future<void>.delayed(const Duration(milliseconds: 20));
  return 4000;
}

Future<void> main() async {
  print("request");
  final result = readLater();
  print("continue");
  print(await result);
}

Future 表示将来结束时产生一个整数,Future 表示结束但没有结果值。async 允许在函数中使用 await。调用 readLater 会得到 Future;await 暂停这一函数后续部分,等结果或错误。输出顺序是 request、continue、4000。

真正连接设备可能失败,所以用 try/catch 处理失败,用 finally 恢复按钮等清理状态。await 不意味着一定成功,也不意味着调用者之后的页面仍然存在。

3 · 先等到结果,再同步修改页面

State 类的方法片段 · message 与 millivolts 已声明

Future<void> refresh() async {
  try {
    final value = await readLater();
    if (!mounted) return;
    setState(() => millivolts = value);
  } catch (error) {
    if (!mounted) return;
    setState(() => message = "Read failed: $error");
  }
}

等待放在 setState 外面,得到结果后再同步修改状态。mounted 表示这份 State 仍挂在组件树中;等待期间退出页面后,不应再对已销毁页面更新。若界面允许重复点击,还要用“处理中”状态暂时禁用按钮,避免多次操作交错。

4 · 连续到来的结果:Stream

State 内开始接收模拟数据;需 import dart:async

subscription = Stream<int>.periodic(
  const Duration(seconds: 1), (i) => 3900 + i % 100,
).listen((value) {
  if (!mounted) return;
  setState(() => millivolts = value);
});

一个 Future 通常对应一次异步结束;Stream 对应陆续出现的事件。listen 登记每次事件到来时要执行的函数,返回一个订阅对象。开始接收应由一次初始化或明确操作触发,不能在 build 中不断增加订阅。

订阅保存在 State 字段,生命周期结束时取消

StreamSubscription<int>? subscription;

@override
void dispose() {
  subscription?.cancel();
  super.dispose();
}

cancel 停止这一份订阅后续事件,mounted 检查负责避免销毁后更新;两者解决不同问题。事件还可能失败或结束,需要时分别处理 onError 和 onDone。

5 · 把等待变成可观察的实验

下载异步示例查看打印顺序,再运行配套 Flutter 小应用。点击开始后每秒出现模拟电压;停止后取消订阅并清除旧读数。模拟值只是界面练习,不代表真实采集。

动手观察

让 readLater 故意抛出异常,确认失败后还能再次点击。开始接收后退出页面,观察是否出现销毁后更新或遗留定时器错误。解释这些行为与 FreeRTOS 的任务阻塞有什么相似处、执行机制又有哪些区别。

资料与代码

配套文件

先复制到独立练习目录;用法见 支线示例说明。完整 Flutter 小应用见 tutorial/examples/flutter_watch/README.md。