在Dart中,异步编程是必不可少的,尤其是处理I/O操作(如网络请求、文件操作或数据库查询)。现在介绍Dart中常用的几种异步编程方式,并剖析其优缺点及应用场景。111
async 和 await
async 和 await 是 Dart 中用于简化异步编程的重要特性。它们的设计让代码结构更直观,但也有一些限制和需要注意的地方。111222
优点
1. 简化异步代码结构
-
使用
async和await,异步代码看起来像同步代码,逻辑更加线性,易于理解和维护。 -
示例:
dart// 使用 async/await Future<void> fetchData() async { try { final response = await http.get(Uri.parse('https://api.example.com')); print(response.body); } catch (e) { print('Error: $e'); } }
与回调方式相比:
dart
// 使用回调
void fetchData() {
http.get(Uri.parse('https://api.example.com')).then((response) {
print(response.body);
}).catchError((e) {
print('Error: $e');
});
}
2. 更高的可读性和可维护性
- 消除了嵌套的回调地狱,让代码更加整洁,减少逻辑错误的可能性。
3. 与 Dart 的同步代码无缝集成
- 可以在同步代码中调用
async函数,并等待其结果完成,而不影响代码的整体结构。
4. 任务依赖更容易实现
-
通过
await的线性逻辑,可以自然地表达任务之间的依赖关系。 -
示例:
dartFuture<void> processTasks() async { final result1 = await task1(); final result2 = await task2(result1); final result3 = await task3(result2); print(result3); }
缺点
1. 无法并发执行
-
如果需要并发执行多个异步任务,单纯使用
async/await会导致这些任务按顺序执行,降低效率。 -
示例(任务依次执行,耗时更长):
dartFuture<void> sequentialTasks() async { final result1 = await task1(); final result2 = await task2(); final result3 = await task3(); }
解决方法: 使用 Future.wait 实现并发:
dart
Future<void> concurrentTasks() async {
final results = await Future.wait([task1(), task2(), task3()]);
}
2. 错误捕获可能遗漏
-
如果在异步任务中没有正确使用
try-catch,错误可能会被吞掉或传播到全局,难以调试。 -
示例(错误未捕获的情况):
dartFuture<void> fetchData() async { await someAsyncFunction(); // 如果函数内部抛出异常,但没有 try-catch,错误可能未捕获 }
3. 不能直接取消任务
- Dart 的
async/await机制本身不支持直接取消正在执行的异步任务。若需实现任务取消功能, 需要使用其他策略,例如使用 Stream 或设计自定义的控制逻辑。
4. 可能导致性能问题(过多的等待)
-
如果不合理地使用
await,例如在不必要的地方等待某些任务完成,会拖慢整体性能。 -
示例(不必要的阻塞):
dartFuture<void> fetchData() async { for (var i = 0; i < 10; i++) { final data = await fetchSingleItem(i); // 每次都等待一个请求完成 print(data); } }
优化方法: 并发处理任务:
dart
dart Future<void> fetchData() async {
final tasks = List.generate(10, (i) => fetchSingleItem(i));
final results = await Future.wait(tasks);
results.forEach(print);
}
适用场景
适合:
- 单任务的异步操作
- 如网络请求、文件读取等单次异步任务。
- 任务之间有依赖关系
- 需要按照顺序执行多个异步操作。
- 需要简洁的代码逻辑
- 简化回调地狱,提高代码可读性。
不适合:
- 需要并发执行多个任务
- 直接使用
async/await会导致任务按顺序执行。
- 直接使用
- 实时更新的数据流
- 例如监听事件流,
async/await不能直接处理Stream。
- 例如监听事件流,
- 需要取消任务的场景
async/await无法直接中止正在进行的任务。
总结
优点:
- 让异步代码更清晰、结构化,减少复杂度。
- 更高的可维护性和可读性。
- 内置的错误处理机制。
- 易于表达任务的依赖关系。
缺点:
- 无法并发执行,可能降低效率。
- 仅适用于
Future,限制较多。 - 不支持任务取消。
- 对复杂异步场景(如并发任务、实时数据)支持不足。
在实际开发中,应根据具体场景结合 Future.wait、Stream 等工具,合理利用 async/await 提升代码质量和性能。
FutureBuilder
使用 FutureBuilder 是处理异步数据流构建 Flutter UI 的一种方法,它适用于特定的使用场景。
优点
- 简化异步操作与 UI 集成
FutureBuilder将异步数据加载和 UI 更新整合在一个小部件中,代码更加简洁和结构化。- 自动监听
Future的状态(等待、完成、失败等),无需手动处理异步结果。
- 动态更新 UI
- 根据
Future的状态 (ConnectionState) 动态更新 UI,如显示加载指示器、错误信息或加载完成后的内容。
- 根据
- 无需额外的状态管理工具
- 对于简单的异步任务,
FutureBuilder提供了快速实现的方式,避免引入复杂的状态管理工具(如Provider或Bloc)。
- 对于简单的异步任务,
- 内置错误处理
- 提供
snapshot.hasError来检测并处理Future中的异常,增强应用的健壮性。
- 提供
- 代码复用性强
- 可以封装成通用的组件,减少重复代码。
缺点
-
每次重新构建都会重新触发 Future
FutureBuilder会随着父级组件的重建而重新执行Future。这可能导致不必要的重复调用(如重复的网络请求)。
解决方法: 使用initState缓存Future,避免重复构建:
dartlate Future<String> _cachedFuture; @override void initState() { super.initState(); _cachedFuture = fetchData(); } @override Widget build(BuildContext context) { return FutureBuilder<String>( future: _cachedFuture, builder: (context, snapshot) { // 构建逻辑 }, ); } -
不适合复杂的 UI 和异步逻辑
- 对于复杂的异步操作(例如多个
Future的依赖关系)或需要全局共享数据,FutureBuilder会让代码变得难以维护。 替代方案: 考虑使用Provider、Bloc等状态管理工具。
- 对于复杂的异步操作(例如多个
-
状态管理局限性
FutureBuilder只能处理一次性的异步任务,不适用于需要持续更新的数据流(比如 WebSocket 或实时数据)。
替代方案: 使用StreamBuilder处理持续更新的流。
-
缺乏缓存机制
- 每次重新构建都会重新请求数据(如果没有手动缓存
Future),可能导致性能问题。
解决方法: 引入缓存策略,例如将Future结果存储到本地变量或使用状态管理工具。
- 每次重新构建都会重新请求数据(如果没有手动缓存
-
调试困难
- 当
FutureBuilder嵌套或包含复杂逻辑时,调试错误会变得困难,尤其是当涉及异步异常或多重依赖时。
- 当
适用场景
适合:
- 简单的一次性异步任务(如单个网络请求)。
- 快速实现异步任务的 UI,逻辑较为独立的组件。
- 无需全局共享或长期维护的数据。
不适合:
- 复杂的异步依赖关系或需要全局状态管理的场景。
- 需要实时更新的数据流(如传感器数据、WebSocket)。
- 对性能要求较高,频繁重建的场景。
总结
FutureBuilder 是一个灵活的工具,适用于构建异步数据流的用户界面。在处理简单的异步场景时,有效减少代码量。对于复杂应用,建议结合状态管理库如 Provider或 Bloc使用,或考虑使用更高级的构建小部件如 StreamBuilder。深入理解FutureBuilder` 的特性和限制,能帮助开发者更准确地评估项目需求,从而做出更合理的设计选择。
Future.wait
Future.wait 是 Dart 中处理多个异步任务并等待它们全部完成的重要工具。它非常适合同时发起并行的异步操作,但也有其限制和使用场景。以下是它的优缺点分析:
优点
1. 并发处理多个异步任务
-
Future.wait可以同时启动多个异步任务,并在所有任务完成后继续执行下一步逻辑,从而提高执行效率。 -
示例:
dartFuture<void> fetchData() async { final results = await Future.wait([ http.get(Uri.parse('https://api.example.com/data1')), http.get(Uri.parse('https://api.example.com/data2')), http.get(Uri.parse('https://api.example.com/data3')), ]); results.forEach((response) => print(response.body)); }
2. 结果统一管理
- 返回的
Future会包含所有子任务的结果(按启动顺序返回的列表),使得后续的结果处理更加方便。
3. 简化代码结构
- 避免嵌套的回调地狱,将多个任务整合到一个地方处理,代码更加清晰易读。
4. 支持短路操作
-
当
Future.wait中的一个任务失败时(除非使用eagerError: false),整个操作会抛出异常,方便错误捕获和处理。 -
示例(带错误处理):
darttry { await Future.wait([ asyncTask1(), asyncTask2(), asyncTask3(), ]); } catch (e) { print('One of the tasks failed: $e'); }
缺点
1. 所有任务失败即中断
-
- 默认情况下(
eagerError: true),如果一个Future抛出异常,整个Future.wait将立即中止,并抛出错误,而不会继续等待其他任务的完成。
解决方法: 使用
eagerError: false,允许其他任务继续执行:dartFuture.wait( [asyncTask1(), asyncTask2(), asyncTask3()], eagerError: false, ); - 默认情况下(
2. 资源竞争和性能问题
-
同时启动多个耗时或高资源占用的任务可能导致性能瓶颈(例如过多的并发网络请求可能会触发服务器限流)。
解决方法:
控制并发任务的数量(如分批执行):
dartFuture<void> fetchInBatches(List<Future> tasks, int batchSize) async { for (var i = 0; i < tasks.length; i += batchSize) { await Future.wait(tasks.sublist(i, i + batchSize)); } }
3. 结果解析复杂度增加
- 当任务之间没有明显的返回值标识时,解析结果可能变得复杂,特别是当返回结果顺序和任务顺序有关时。
4. 缺乏个别任务的控制
Future.wait一旦启动,无法单独取消某些任务(Dart 的Future本身不支持取消)。
5. 不适用于依赖链任务
-
如果任务之间有依赖关系(如任务 B 需要任务 A 的结果),
Future.wait并不适用。**解决方法:**使用链式
await处理依赖任务:dartfinal resultA = await asyncTaskA(); final resultB = await asyncTaskB(resultA);
适用场景
适合:
- 独立的并行任务
- 各任务相互独立,互不依赖(如多个网络请求、文件读写等)。
- 需要在所有任务完成后处理结果
- 如批量数据处理、合并结果等。
- 需要快速实现并发处理
- 代码逻辑简单,任务数量有限。
不适合:
- 任务间存在依赖关系
- 使用链式
await或嵌套Future。
- 使用链式
- 高资源消耗的任务
- 并发任务过多时可能导致性能下降,应分批处理。
总结
优点总结
- 并发处理提升效率。
- 统一结果管理。
- 简化代码结构。
缺点总结
- 默认中断策略对容错性较低。
- 可能导致性能瓶颈。
- 不适用于依赖任务或任务控制需求。
在使用 Future.wait 时,需要平衡任务的独立性、资源消耗和代码复杂度,来确定最优的实现方案。
使用Completer自定义 Future
在 Dart 中,Completer 是一个用于手动控制 Future 的工具。它允许你创建一个 Future,并在代码的某个时刻完成(resolve)或抛出错误(reject)。通过 Completer,开发者可以更灵活地控制异步操作,但也有一定的限制。
优点
1. 灵活控制异步操作
-
通过
Completer,可以手动完成(complete)或失败(completeError)一个Future,适用于需要更多控制的场景。 -
示例:
dartFuture<String> fetchData() { Completer<String> completer = Completer(); // 模拟异步操作 Timer(Duration(seconds: 2), () { completer.complete("Data fetched!"); }); return completer.future; }
2. 分离逻辑与异步控制
- 使用
Completer时,可以在不同的代码段或异步操作中决定何时完成Future,避免直接在任务逻辑中嵌套Future。
3. 适用于异步回调封装
-
在需要将基于回调的异步 API 转换为
Future的场景中,Completer非常有用。 -
示例:
dartFuture<int> getSumAsync(int a, int b) { Completer<int> completer = Completer(); // 模拟异步计算 Timer(Duration(seconds: 1), () { completer.complete(a + b); }); return completer.future; }
4. 显式控制错误处理
-
可以通过
completeError手动抛出异常,并在调用链中捕获,提供更细粒度的错误管理。 -
示例:
dartFuture<String> fetchWithError() { Completer<String> completer = Completer(); Timer(Duration(seconds: 2), () { if (DateTime.now().second % 2 == 0) { completer.complete("Success!"); } else { completer.completeError("An error occurred."); } }); return completer.future; }
5. 适合任务拆分与协调
- 可以手动触发多个
Completer来管理复杂的异步任务和分支逻辑。
缺点
1. 可能导致冗余代码
-
在许多情况下,使用
Completer实现异步操作可以通过直接返回Future或使用其他 Dart 异步机制来代替,过度依赖Completer可能导致代码复杂化和冗余。 -
示例(不必要的 Completer):
dart// 不推荐 Future<int> fetchData() { Completer<int> completer = Completer(); Future.delayed(Duration(seconds: 1), () { completer.complete(42); }); return completer.future; } // 推荐 Future<int> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 42; }
2. 需要手动完成
- 如果忘记调用
complete或completeError,Future会一直处于未完成状态,可能导致意外的死锁或内存泄漏。
3. 调试难度增加
- 使用
Completer时,问题的根源可能分布在多个地方,特别是当Future的完成依赖复杂的条件时。
4. 容易滥用
- 开发者可能会倾向于在所有异步操作中使用
Completer,而忽略了更简洁的async/await或Future的构造方法。
5. 增加任务生命周期管理的复杂性
-
需要手动跟踪任务的状态,否则可能出现重复调用
complete或completeError的错误,导致抛出异常。 -
示例(错误调用):
dartCompleter<int> completer = Completer(); completer.complete(42); completer.complete(43); // 会抛出异常:Future already completed
适用场景
适合
- 异步回调转换
- 将基于回调的 API 封装成
Future。 - 示例:将旧的异步网络请求接口改造成基于
Future的形式。
- 将基于回调的 API 封装成
- 复杂任务管理
- 在复杂任务中需要手动控制异步任务的完成或失败。
- 与其他异步工具协作
- 用于在自定义工具中扩展异步行为。
- 多步任务的协调
- 在依赖多个异步任务的场景下,
Completer可以手动完成任务并触发后续操作。
- 在依赖多个异步任务的场景下,
不适合
- 简单异步操作
- 使用
async/await更清晰、更简洁。
- 使用
- 无需手动控制的异步任务
- 如果任务逻辑明确且完成时机固定,直接使用
Future即可。
- 如果任务逻辑明确且完成时机固定,直接使用
总结
优点
- 提供灵活的异步控制。
- 适用于回调封装和复杂任务。
- 能显式管理错误。
- 在异步任务的分步完成中更为高效。
缺点
- 可能导致冗余代码。
- 忘记调用
complete会引发问题。 - 调试复杂度高。
- 易被误用,增加不必要的复杂性。
Completer 是一个强大的工具,但应谨慎使用。在大多数场景中,async/await 和直接使用 Future 就能满足需求,而 Completer 适合处理特殊的异步需求。
microtask
在 Dart 中,microtask 是一种用于调度任务的机制,优先级高于事件循环队列(event queue)的普通任务。它常用于需要在当前事件循环结束前尽快执行的任务。使用 scheduleMicrotask 可以将任务加入微任务队列。
优点
1. 高优先级任务执行
-
Microtask 会在当前任务完成后立即执行,优先级高于事件队列中的普通任务。这对于需要快速响应、避免延迟的操作非常有用。
-
示例:
dartimport 'dart:async'; void main() { print('Start'); Future(() => print('Future task')); scheduleMicrotask(() => print('Microtask')); print('End'); } // 输出顺序: // Start // End // Microtask // Future task
2. 减少响应延迟
- 适合需要在事件循环队列任务之前处理的重要逻辑,避免因普通任务的队列等待导致的响应延迟。
3. 简化异步控制
- 在某些场景下,
microtask比Future更适合用于调度短时间的、高优先级的操作。例如,UI 更新和状态调整等无需等待的操作。
4. 适合依赖链处理
- 如果任务有多个依赖步骤,可以使用
microtask让每一步在当前任务完成后立即执行,确保依赖的任务快速完成。
5. 与事件循环队列分离
- 微任务队列和普通任务队列独立运行,不会相互影响。这使得开发者可以清晰地分配高优先级任务。
缺点
1. 可能阻塞事件循环
-
如果添加了大量微任务,可能会影响事件循环中普通任务的执行,进而导致其他异步任务的执行延迟。
-
示例(无限递归):
dartimport 'dart:async'; void main() { scheduleMicrotask(() { print('Microtask'); scheduleMicrotask(() => print('Nested Microtask')); }); print('End'); }若不控制任务数量,可能导致微任务队列过长。
2. 难以调试
- 微任务的执行顺序和优先级较高,调试时可能会遇到意想不到的行为,尤其是在任务混杂
Future和普通任务的情况下。
3. 对性能的潜在影响
- 频繁使用微任务可能导致性能问题,例如增加 CPU 负载或占用更多调度时间。
4. 可能破坏任务预期顺序
- 如果滥用
microtask,可能打乱普通任务的自然执行顺序,导致任务之间的依赖关系出错。
5. 不适合耗时任务
- 微任务本质上是为短小、快速完成的操作设计的。如果将耗时任务加入微任务队列,可能导致整个线程被阻塞。
适用场景
适合
- 需要立即响应的逻辑
- 例如,快速修正状态、处理同步错误。
- UI 更新和事件优先处理
- 确保关键的状态更新逻辑优先执行。
- 依赖链中的快速处理任务
- 确保后续步骤在当前步骤完成后尽快执行。
- 协调任务顺序
- 控制任务在事件循环队列之前执行。
不适合
- 长时间运行的任务
- 这种任务应放入事件循环的普通任务队列。
- 任务优先级不高的场景
- 使用微任务会增加不必要的复杂性。
- 需要调试复杂任务依赖的场景
- 微任务调试难度大,优先使用更简单的方式(如
Future)。
- 微任务调试难度大,优先使用更简单的方式(如
总结
优点
- 高优先级,任务会在事件队列任务之前执行。
- 减少延迟,适合需要快速响应的场景。
- 简化依赖链和异步控制逻辑。
- 微任务队列独立运行,便于任务优先级管理。
缺点
- 可能阻塞事件循环,影响普通任务执行。
- 调试难度较高,尤其是涉及复杂任务时。
- 滥用可能导致性能问题。
- 不适合耗时任务和优先级较低的操作。
最佳实践
- 在需要关键任务优先执行时使用微任务。
- 避免向微任务队列添加过多任务,以防阻塞事件循环。
- 对耗时任务,使用普通任务或
Future。 - 在复杂场景下,注意任务的依赖顺序,避免微任务和普通任务之间的冲突。
microtask 是 Dart 异步编程的重要工具,但需要谨慎使用,确保合理分配任务优先级和资源。
stream
在 Dart 和 Flutter 中,Stream 是用于处理数据流 的关键工具。它能持续地接收数据,是实现驱动或实时数据更新的重要机制。下面是 Stream 的优缺点分析。
优点
1. 支持实时数据流
-
Stream是处理连续异步数据的核心工具,适合事件驱动、实时数据更新等场景,比如 WebSocket 数据、传感器数据、用户输入事件等。 -
示例:
dartStream<int> numberStream() async { for (int i = 0; i < 5; i++) { await Future.delayed(Duration(seconds: 1)); yield i; } } void main() { numberStream().listen((event) { print('Received: $event'); }); }
2. 灵活的订阅模式
-
支持 单次订阅流(Single-subscription Stream)和 广播流(Broadcast Stream),灵活适应不同需求:
- 单次订阅:适合需要单独消费的数据流。
-
广播流:适合多个监听器同时消费的场景(如事件总线)。
3. 支持数据处理与转换
-
通过
Stream提供的多种操作符(如map、where、take等),可以对流中的数据进行过滤、转换和组合,简化复杂逻辑。 -
示例:
dartStream<int> transformedStream = numberStream().map((event) => event * 2); transformedStream.listen((event) { print('Transformed: $event'); });
4. 异步任务的协调
-
Stream可以轻松协调多个异步任务,例如等待多个流完成,或者合并多个流。 -
示例:
dartStream<int> stream1 = Stream<int>.fromIterable([1, 2, 3]); Stream<int> stream2 = Stream<int>.fromIterable([4, 5, 6]); Stream<int> mergedStream = Stream.fromIterable([...stream1, ...stream2]);
5. 与 Flutter 框架紧密融合
Stream在 Flutter 中被广泛应用,例如与StreamBuilder结合用于实时更新 UI。
6. 资源效率高
Stream默认是惰性计算,只有当有监听器时才会开始发送数据,避免不必要的开销。
7. 错误处理机制
-
提供了对错误的监听功能,可以在数据流中捕获并处理错误,防止整个流崩溃。
-
示例:
dartStream<int> errorStream() async* { for (int i = 0; i < 5; i++) { if (i == 3) throw Exception('Error at $i'); yield i; } } void main() { errorStream().listen( (data) => print('Data: $data'), onError: (error) => print('Caught error: $error'), ); }
缺点
1. 学习曲线较陡
- 初学者需要理解
Stream的核心概念(如订阅、单次订阅与广播、流的生命周期等),以及操作符的使用,可能需要一定时间。
2. 管理复杂性高
-
需要手动管理流的订阅和取消,否则可能引发内存泄漏 或资源浪费。
-
示例(未正确取消订阅):
dartStreamSubscription? subscription; void startListening(Stream<int> stream) { subscription = stream.listen((event) => print(event)); } void stopListening() { subscription?.cancel(); // 如果不调用,流会一直占用资源 }
3. 难以调试
- 在复杂场景中,涉及多个流的组合、转换和监听时,调试任务执行的顺序和错误原因可能较为困难。
4. 对错误处理的依赖
- 如果流中未正确处理错误(
onError),可能导致监听器无法继续接收数据。
5. 性能开销
- 当流数据频繁更新且监听器执行逻辑复杂时,可能引发性能问题。
- 示例:高频数据流(如传感器或动画帧流)如果未优化,可能导致 UI 卡顿。
6. 广播流的副作用
-
广播流的监听器在订阅时不会从头开始接收数据,可能导致未监听到初始事件。
-
示例:
dartStream<int> broadcastStream = Stream<int>.periodic(Duration(seconds: 1), (count) => count).asBroadcastStream(); // 第一个监听器 broadcastStream.listen((event) { print('Listener 1: $event'); }); // 延迟后添加的监听器,无法接收之前的事件 Future.delayed(Duration(seconds: 3), () { broadcastStream.listen((event) { print('Listener 2: $event'); }); });
7. 适用场景有限
- 不适合一次性任务或无需持续更新的数据任务(如单次网络请求),这种场景更适合
Future。
适用场景
适合
- 实时数据流
- WebSocket、传感器数据、用户输入事件等。
- 复杂异步任务
- 多任务协作、组合流的场景。
- 状态管理
- 使用
Stream处理状态变化,并与StreamBuilder等 UI 组件结合。
- 使用
- 高频更新
- 如动画帧流、计时器。
不适合
- 一次性异步操作
- 使用
Future更简单直接。
- 使用
- 低频更新
- 若更新不频繁,可能无需使用流工具。
- 需要持久存储的场景
Stream主要用于临时数据流动,不适合作为长期数据存储机制。
总结
优点
- 支持实时、持续的异步数据流。
- 灵活的订阅模式(单次订阅、广播流)。
- 数据处理与转换能力强。
- 与 Flutter 集成紧密,适合实时 UI 更新。
- 提供错误处理和资源高效性。
缺点
- 学习成本高,需管理流的生命周期。
- 调试复杂流较困难。
- 滥用可能引发性能问题。
- 广播流初始数据的监听有局限。
Stream 是处理异步数据流的强大工具,但在使用时需要权衡其复杂性和场景的实际需求。在实时数据更新或事件驱动开发中,Stream 是不二之选;但对于简单的一次性任务,应优先考虑 Future。
Stream与StreamBuilder的结合使用
Stream 和 StreamBuilder 的结合是 Flutter 中实现实时更新 UI 的重要方式。Stream 提供数据流,StreamBuilder 监听数据流并根据变化构建 UI。以下是它们的结合使用介绍和示例。
Stream 与 StreamBuilder 的基本原理
- Stream
- 用于提供异步数据流,可以持续输出事件或数据。
- StreamBuilder
- 监听一个
Stream,每当流发送新的数据时,StreamBuilder会自动重建其子树(builder回调)。
- 监听一个
- 结合使用的优势
- 实现 UI 的实时更新,例如:网络请求、传感器数据、计时器、WebSocket 等场景。
Stream 与 StreamBuilder 的结合示例
示例 1:简单计时器
dart
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TimerPage(),
);
}
}
class TimerPage extends StatelessWidget {
// 创建一个 Stream,每秒发送一个计时数据
Stream<int> timerStream() async {
int counter = 0;
while (true) {
await Future.delayed(Duration(seconds: 1));
yield counter++;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stream + StreamBuilder Example')),
body: Center(
child: StreamBuilder<int>(
stream: timerStream(), // 监听计时器流
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Waiting for data...');
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text('Timer: ${snapshot.data}', style: TextStyle(fontSize: 24));
}
},
),
),
);
}
}
示例 2:随机数生成
dart
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
class RandomNumberStreamPage extends StatelessWidget {
// 创建一个随机数生成流
Stream<int> randomNumberStream() async {
Random random = Random();
while (true) {
yield random.nextInt(100); // 生成 0-99 的随机数
await Future.delayed(Duration(seconds: 1));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Random Number Stream')),
body: Center(
child: StreamBuilder<int>(
stream: randomNumberStream(), // 监听随机数流
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('No data yet...');
}
return Text('Random Number: ${snapshot.data}', style: TextStyle(fontSize: 24));
},
),
),
);
}
}
重要概念
StreamBuilder 构造参数
-
stream: 要监听的Stream对象。 -
builder: 必须实现的回调函数,根据流中的数据构建 UI。snapshot:表示流的当前状态,包括以下属性:-
connectionState:流的连接状态,值包括:ConnectionState.none:未连接到流。ConnectionState.waiting:正在等待流的数据。ConnectionState.active:流已连接并开始传递数据。ConnectionState.done:流已完成。
-
data:流中最新的数据。 -
hasData:是否有数据。 -
error:流中产生的错误。 -
hasError:是否有错误。
-
流的关闭与取消
-
如果
Stream是由外部提供(如 WebSocket、订阅流),需要手动取消订阅以避免内存泄漏。 -
示例:
dartlate StreamSubscription<int> subscription; void startListening() { subscription = myStream.listen((event) { print('Data: $event'); }); } void stopListening() { subscription.cancel(); // 取消流监听 }
StreamBuilder 与 State Management 的结合
StreamBuilder 可以和状态管理工具(如 BLoC 模式)结合,处理复杂的应用逻辑。
示例:StreamBuilder 与 BLoC
dart
class CounterBloc {
final _controller = StreamController<int>();
int _counter = 0;
// 输出流
Stream<int> get counterStream => _controller.stream;
// 增加计数
void increment() {
_counter++;
_controller.sink.add(_counter);
}
// 释放资源
void dispose() {
_controller.close();
}
}
class CounterPage extends StatelessWidget {
final CounterBloc bloc = CounterBloc();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('StreamBuilder with BLoC')),
body: Center(
child: StreamBuilder<int>(
stream: bloc.counterStream,
initialData: 0,
builder: (context, snapshot) {
return Text('Count: ${snapshot.data}', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: bloc.increment,
child: Icon(Icons.add),
),
);
}
}
结合使用的优缺点
优点
- 实时更新 UI :当
Stream产生新数据时,StreamBuilder自动更新 UI。 - 简单易用 :
StreamBuilder提供了简单的 API,适合快速实现流数据与 UI 的绑定。 - 状态管理便利 :可结合
BLoC等模式处理复杂的逻辑和数据流。 - 灵活的 UI 重构 :通过
builder方法,可以根据流的状态动态改变 UI。
缺点
- 资源管理复杂 :需要注意
Stream和StreamSubscription的生命周期,避免内存泄漏。 - 调试困难:复杂流中,错误或状态切换可能难以调试。
- 性能开销:频繁流更新会频繁触发 UI 重构,需避免高频流影响性能。
总结
-
适用场景:
- 实时数据展示(计时器、传感器、网络数据等)。
- 事件驱动的交互(按钮点击、输入变化等)。
- 状态管理结合流的场景(如
BLoC模式)。
-
注意事项:
- 管理好
Stream的生命周期,及时取消未使用的流。
- 管理好
-
避免高频率流更新引发性能问题。
通过恰当地利用 Stream 和 StreamBuilder,可以有效地在 Flutter 应用中实现实时交互,从而增强用户体验。