async 和 await

在Dart中,异步编程是必不可少的,尤其是处理I/O操作(如网络请求、文件操作或数据库查询)。现在介绍Dart中常用的几种异步编程方式,并剖析其优缺点及应用场景。111

async 和 await

asyncawait 是 Dart 中用于简化异步编程的重要特性。它们的设计让代码结构更直观,但也有一些限制和需要注意的地方。111222


优点

1. 简化异步代码结构
  • 使用 asyncawait,异步代码看起来像同步代码,逻辑更加线性,易于理解和维护。

  • 示例:

    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 的线性逻辑,可以自然地表达任务之间的依赖关系。

  • 示例:

    dart 复制代码
    Future<void> processTasks() async {
      final result1 = await task1();
      final result2 = await task2(result1);
      final result3 = await task3(result2);
      print(result3);
    }

缺点

1. 无法并发执行
  • 如果需要并发执行多个异步任务,单纯使用 async/await 会导致这些任务按顺序执行,降低效率。

  • 示例(任务依次执行,耗时更长):

    dart 复制代码
    Future<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,错误可能会被吞掉或传播到全局,难以调试。

  • 示例(错误未捕获的情况):

    dart 复制代码
    Future<void> fetchData() async {
      await someAsyncFunction(); // 如果函数内部抛出异常,但没有 try-catch,错误可能未捕获
    }
3. 不能直接取消任务
  • Dart 的 async/await 机制本身不支持直接取消正在执行的异步任务。若需实现任务取消功能, 需要使用其他策略,例如使用 Stream 或设计自定义的控制逻辑。
4. 可能导致性能问题(过多的等待)
  • 如果不合理地使用 await,例如在不必要的地方等待某些任务完成,会拖慢整体性能。

  • 示例(不必要的阻塞):

    dart 复制代码
    Future<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); 
}

适用场景

适合:
  1. 单任务的异步操作
    • 如网络请求、文件读取等单次异步任务。
  2. 任务之间有依赖关系
    • 需要按照顺序执行多个异步操作。
  3. 需要简洁的代码逻辑
    • 简化回调地狱,提高代码可读性。
不适合:
  1. 需要并发执行多个任务
    • 直接使用 async/await 会导致任务按顺序执行。
  2. 实时更新的数据流
    • 例如监听事件流,async/await 不能直接处理 Stream
  3. 需要取消任务的场景
    • async/await 无法直接中止正在进行的任务。

总结

优点:
  • 让异步代码更清晰、结构化,减少复杂度。
  • 更高的可维护性和可读性。
  • 内置的错误处理机制。
  • 易于表达任务的依赖关系。
缺点:
  • 无法并发执行,可能降低效率。
  • 仅适用于 Future,限制较多。
  • 不支持任务取消。
  • 对复杂异步场景(如并发任务、实时数据)支持不足。

在实际开发中,应根据具体场景结合 Future.waitStream 等工具,合理利用 async/await 提升代码质量和性能。

FutureBuilder

使用 FutureBuilder 是处理异步数据流构建 Flutter UI 的一种方法,它适用于特定的使用场景。

优点

  1. 简化异步操作与 UI 集成
    • FutureBuilder 将异步数据加载和 UI 更新整合在一个小部件中,代码更加简洁和结构化。
    • 自动监听 Future 的状态(等待、完成、失败等),无需手动处理异步结果。
  2. 动态更新 UI
    • 根据 Future 的状态 (ConnectionState) 动态更新 UI,如显示加载指示器、错误信息或加载完成后的内容。
  3. 无需额外的状态管理工具
    • 对于简单的异步任务,FutureBuilder 提供了快速实现的方式,避免引入复杂的状态管理工具(如 ProviderBloc)。
  4. 内置错误处理
    • 提供 snapshot.hasError 来检测并处理 Future 中的异常,增强应用的健壮性。
  5. 代码复用性强
    • 可以封装成通用的组件,减少重复代码。

缺点

  1. 每次重新构建都会重新触发 Future

    • FutureBuilder 会随着父级组件的重建而重新执行 Future。这可能导致不必要的重复调用(如重复的网络请求)。
      解决方法: 使用 initState 缓存 Future,避免重复构建:
    dart 复制代码
    late Future<String> _cachedFuture;
    
    @override
    void initState() {
      super.initState();
      _cachedFuture = fetchData();
    }
    
    @override
    Widget build(BuildContext context) {
      return FutureBuilder<String>(
        future: _cachedFuture,
        builder: (context, snapshot) {
          // 构建逻辑
        },
      );
    }
  2. 不适合复杂的 UI 和异步逻辑

    • 对于复杂的异步操作(例如多个 Future 的依赖关系)或需要全局共享数据,FutureBuilder 会让代码变得难以维护。 替代方案: 考虑使用 ProviderBloc 等状态管理工具。
  3. 状态管理局限性

    • FutureBuilder 只能处理一次性的异步任务,不适用于需要持续更新的数据流(比如 WebSocket 或实时数据)。
      替代方案: 使用 StreamBuilder 处理持续更新的流。
  4. 缺乏缓存机制

    • 每次重新构建都会重新请求数据(如果没有手动缓存 Future),可能导致性能问题。
      解决方法: 引入缓存策略,例如将 Future 结果存储到本地变量或使用状态管理工具。
  5. 调试困难

    • FutureBuilder 嵌套或包含复杂逻辑时,调试错误会变得困难,尤其是当涉及异步异常或多重依赖时。

适用场景

适合:
  • 简单的一次性异步任务(如单个网络请求)。
  • 快速实现异步任务的 UI,逻辑较为独立的组件。
  • 无需全局共享或长期维护的数据。
不适合:
  • 复杂的异步依赖关系或需要全局状态管理的场景。
  • 需要实时更新的数据流(如传感器数据、WebSocket)。
  • 对性能要求较高,频繁重建的场景。

总结

FutureBuilder 是一个灵活的工具,适用于构建异步数据流的用户界面。在处理简单的异步场景时,有效减少代码量。对于复杂应用,建议结合状态管理库如 ProviderBloc使用,或考虑使用更高级的构建小部件如 StreamBuilder。深入理解FutureBuilder` 的特性和限制,能帮助开发者更准确地评估项目需求,从而做出更合理的设计选择。

Future.wait

Future.wait 是 Dart 中处理多个异步任务并等待它们全部完成的重要工具。它非常适合同时发起并行的异步操作,但也有其限制和使用场景。以下是它的优缺点分析:


优点

1. 并发处理多个异步任务
  • Future.wait 可以同时启动多个异步任务,并在所有任务完成后继续执行下一步逻辑,从而提高执行效率。

  • 示例:

    dart 复制代码
    Future<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),整个操作会抛出异常,方便错误捕获和处理。

  • 示例(带错误处理):

    dart 复制代码
    try {
      await Future.wait([
        asyncTask1(),
        asyncTask2(),
        asyncTask3(),
      ]);
    } catch (e) {
      print('One of the tasks failed: $e');
    }

缺点

1. 所有任务失败即中断
    • 默认情况下(eagerError: true),如果一个 Future 抛出异常,整个 Future.wait 将立即中止,并抛出错误,而不会继续等待其他任务的完成。

    解决方法: 使用 eagerError: false,允许其他任务继续执行:

    dart 复制代码
    Future.wait(
      [asyncTask1(), asyncTask2(), asyncTask3()],
      eagerError: false,
    );
2. 资源竞争和性能问题
  • 同时启动多个耗时或高资源占用的任务可能导致性能瓶颈(例如过多的并发网络请求可能会触发服务器限流)。

    解决方法:

    控制并发任务的数量(如分批执行):

    dart 复制代码
    Future<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 处理依赖任务:

    dart 复制代码
    final resultA = await asyncTaskA();
    final resultB = await asyncTaskB(resultA);

适用场景

适合:
  1. 独立的并行任务
    • 各任务相互独立,互不依赖(如多个网络请求、文件读写等)。
  2. 需要在所有任务完成后处理结果
    • 如批量数据处理、合并结果等。
  3. 需要快速实现并发处理
    • 代码逻辑简单,任务数量有限。
不适合:
  1. 任务间存在依赖关系
    • 使用链式 await 或嵌套 Future
  2. 高资源消耗的任务
    • 并发任务过多时可能导致性能下降,应分批处理。

总结

优点总结
  • 并发处理提升效率。
  • 统一结果管理。
  • 简化代码结构。
缺点总结
  • 默认中断策略对容错性较低。
  • 可能导致性能瓶颈。
  • 不适用于依赖任务或任务控制需求。

在使用 Future.wait 时,需要平衡任务的独立性、资源消耗和代码复杂度,来确定最优的实现方案。

使用Completer自定义 Future

在 Dart 中,Completer 是一个用于手动控制 Future 的工具。它允许你创建一个 Future,并在代码的某个时刻完成(resolve)或抛出错误(reject)。通过 Completer,开发者可以更灵活地控制异步操作,但也有一定的限制。


优点

1. 灵活控制异步操作
  • 通过 Completer,可以手动完成(complete)或失败(completeError)一个 Future,适用于需要更多控制的场景。

  • 示例:

    dart 复制代码
    Future<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 非常有用。

  • 示例:

    dart 复制代码
    Future<int> getSumAsync(int a, int b) {
      Completer<int> completer = Completer();
      // 模拟异步计算
      Timer(Duration(seconds: 1), () {
        completer.complete(a + b);
      });
      return completer.future;
    }
4. 显式控制错误处理
  • 可以通过 completeError 手动抛出异常,并在调用链中捕获,提供更细粒度的错误管理。

  • 示例:

    dart 复制代码
    Future<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. 需要手动完成
  • 如果忘记调用 completecompleteErrorFuture 会一直处于未完成状态,可能导致意外的死锁或内存泄漏。
3. 调试难度增加
  • 使用 Completer 时,问题的根源可能分布在多个地方,特别是当 Future 的完成依赖复杂的条件时。
4. 容易滥用
  • 开发者可能会倾向于在所有异步操作中使用 Completer,而忽略了更简洁的 async/awaitFuture 的构造方法。
5. 增加任务生命周期管理的复杂性
  • 需要手动跟踪任务的状态,否则可能出现重复调用 completecompleteError 的错误,导致抛出异常。

  • 示例(错误调用):

    dart 复制代码
    Completer<int> completer = Completer();
    
    completer.complete(42);
    completer.complete(43); // 会抛出异常:Future already completed

适用场景

适合
  1. 异步回调转换
    • 将基于回调的 API 封装成 Future
    • 示例:将旧的异步网络请求接口改造成基于 Future 的形式。
  2. 复杂任务管理
    • 在复杂任务中需要手动控制异步任务的完成或失败。
  3. 与其他异步工具协作
    • 用于在自定义工具中扩展异步行为。
  4. 多步任务的协调
    • 在依赖多个异步任务的场景下,Completer 可以手动完成任务并触发后续操作。
不适合
  1. 简单异步操作
    • 使用 async/await 更清晰、更简洁。
  2. 无需手动控制的异步任务
    • 如果任务逻辑明确且完成时机固定,直接使用 Future 即可。

总结

优点
  • 提供灵活的异步控制。
  • 适用于回调封装和复杂任务。
  • 能显式管理错误。
  • 在异步任务的分步完成中更为高效。
缺点
  • 可能导致冗余代码。
  • 忘记调用 complete 会引发问题。
  • 调试复杂度高。
  • 易被误用,增加不必要的复杂性。

Completer 是一个强大的工具,但应谨慎使用。在大多数场景中,async/await 和直接使用 Future 就能满足需求,而 Completer 适合处理特殊的异步需求。

microtask

在 Dart 中,microtask 是一种用于调度任务的机制,优先级高于事件循环队列(event queue)的普通任务。它常用于需要在当前事件循环结束前尽快执行的任务。使用 scheduleMicrotask 可以将任务加入微任务队列。


优点

1. 高优先级任务执行
  • Microtask 会在当前任务完成后立即执行,优先级高于事件队列中的普通任务。这对于需要快速响应、避免延迟的操作非常有用。

  • 示例:

    dart 复制代码
    import 'dart:async';
    
    void main() {
      print('Start');
      Future(() => print('Future task'));
      scheduleMicrotask(() => print('Microtask'));
      print('End');
    }
    // 输出顺序:
    // Start
    // End
    // Microtask
    // Future task
2. 减少响应延迟
  • 适合需要在事件循环队列任务之前处理的重要逻辑,避免因普通任务的队列等待导致的响应延迟。
3. 简化异步控制
  • 在某些场景下,microtaskFuture 更适合用于调度短时间的、高优先级的操作。例如,UI 更新和状态调整等无需等待的操作。
4. 适合依赖链处理
  • 如果任务有多个依赖步骤,可以使用 microtask 让每一步在当前任务完成后立即执行,确保依赖的任务快速完成。
5. 与事件循环队列分离
  • 微任务队列和普通任务队列独立运行,不会相互影响。这使得开发者可以清晰地分配高优先级任务。

缺点

1. 可能阻塞事件循环
  • 如果添加了大量微任务,可能会影响事件循环中普通任务的执行,进而导致其他异步任务的执行延迟。

  • 示例(无限递归):

    dart 复制代码
    import 'dart:async';
    
    void main() {
      scheduleMicrotask(() {
        print('Microtask');
        scheduleMicrotask(() => print('Nested Microtask'));
      });
      print('End');
    }

    若不控制任务数量,可能导致微任务队列过长。

2. 难以调试
  • 微任务的执行顺序和优先级较高,调试时可能会遇到意想不到的行为,尤其是在任务混杂 Future 和普通任务的情况下。
3. 对性能的潜在影响
  • 频繁使用微任务可能导致性能问题,例如增加 CPU 负载或占用更多调度时间。
4. 可能破坏任务预期顺序
  • 如果滥用 microtask,可能打乱普通任务的自然执行顺序,导致任务之间的依赖关系出错。
5. 不适合耗时任务
  • 微任务本质上是为短小、快速完成的操作设计的。如果将耗时任务加入微任务队列,可能导致整个线程被阻塞。

适用场景

适合
  1. 需要立即响应的逻辑
    • 例如,快速修正状态、处理同步错误。
  2. UI 更新和事件优先处理
    • 确保关键的状态更新逻辑优先执行。
  3. 依赖链中的快速处理任务
    • 确保后续步骤在当前步骤完成后尽快执行。
  4. 协调任务顺序
    • 控制任务在事件循环队列之前执行。
不适合
  1. 长时间运行的任务
    • 这种任务应放入事件循环的普通任务队列。
  2. 任务优先级不高的场景
    • 使用微任务会增加不必要的复杂性。
  3. 需要调试复杂任务依赖的场景
    • 微任务调试难度大,优先使用更简单的方式(如 Future)。

总结

优点
  • 高优先级,任务会在事件队列任务之前执行。
  • 减少延迟,适合需要快速响应的场景。
  • 简化依赖链和异步控制逻辑。
  • 微任务队列独立运行,便于任务优先级管理。
缺点
  • 可能阻塞事件循环,影响普通任务执行。
  • 调试难度较高,尤其是涉及复杂任务时。
  • 滥用可能导致性能问题。
  • 不适合耗时任务和优先级较低的操作。

最佳实践

  1. 在需要关键任务优先执行时使用微任务。
  2. 避免向微任务队列添加过多任务,以防阻塞事件循环。
  3. 对耗时任务,使用普通任务或 Future
  4. 在复杂场景下,注意任务的依赖顺序,避免微任务和普通任务之间的冲突。

microtask 是 Dart 异步编程的重要工具,但需要谨慎使用,确保合理分配任务优先级和资源。

stream

在 Dart 和 Flutter 中,Stream 是用于处理数据流 的关键工具。它能持续地接收数据,是实现驱动或实时数据更新的重要机制。下面是 Stream 的优缺点分析。


优点

1. 支持实时数据流
  • Stream 是处理连续异步数据的核心工具,适合事件驱动、实时数据更新等场景,比如 WebSocket 数据、传感器数据、用户输入事件等。

  • 示例:

    dart 复制代码
    Stream<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 提供的多种操作符(如 mapwheretake 等),可以对流中的数据进行过滤、转换和组合,简化复杂逻辑。

  • 示例:

    dart 复制代码
    Stream<int> transformedStream = numberStream().map((event) => event * 2);
    transformedStream.listen((event) {
      print('Transformed: $event');
    });
4. 异步任务的协调
  • Stream 可以轻松协调多个异步任务,例如等待多个流完成,或者合并多个流。

  • 示例:

    dart 复制代码
    Stream<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. 错误处理机制
  • 提供了对错误的监听功能,可以在数据流中捕获并处理错误,防止整个流崩溃。

  • 示例:

    dart 复制代码
    Stream<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. 管理复杂性高
  • 需要手动管理流的订阅和取消,否则可能引发内存泄漏资源浪费

  • 示例(未正确取消订阅):

    dart 复制代码
    StreamSubscription? subscription;
    void startListening(Stream<int> stream) {
      subscription = stream.listen((event) => print(event));
    }
    
    void stopListening() {
      subscription?.cancel(); // 如果不调用,流会一直占用资源
    }
3. 难以调试
  • 在复杂场景中,涉及多个流的组合、转换和监听时,调试任务执行的顺序和错误原因可能较为困难。
4. 对错误处理的依赖
  • 如果流中未正确处理错误(onError),可能导致监听器无法继续接收数据。
5. 性能开销
  • 当流数据频繁更新且监听器执行逻辑复杂时,可能引发性能问题。
  • 示例:高频数据流(如传感器或动画帧流)如果未优化,可能导致 UI 卡顿。
6. 广播流的副作用
  • 广播流的监听器在订阅时不会从头开始接收数据,可能导致未监听到初始事件。

  • 示例:

    dart 复制代码
    Stream<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

适用场景

适合
  1. 实时数据流
    • WebSocket、传感器数据、用户输入事件等。
  2. 复杂异步任务
    • 多任务协作、组合流的场景。
  3. 状态管理
    • 使用 Stream 处理状态变化,并与 StreamBuilder 等 UI 组件结合。
  4. 高频更新
    • 如动画帧流、计时器。
不适合
  1. 一次性异步操作
    • 使用 Future 更简单直接。
  2. 低频更新
    • 若更新不频繁,可能无需使用流工具。
  3. 需要持久存储的场景
    • Stream 主要用于临时数据流动,不适合作为长期数据存储机制。

总结

优点
  • 支持实时、持续的异步数据流。
  • 灵活的订阅模式(单次订阅、广播流)。
  • 数据处理与转换能力强。
  • 与 Flutter 集成紧密,适合实时 UI 更新。
  • 提供错误处理和资源高效性。
缺点
  • 学习成本高,需管理流的生命周期。
  • 调试复杂流较困难。
  • 滥用可能引发性能问题。
  • 广播流初始数据的监听有局限。

Stream 是处理异步数据流的强大工具,但在使用时需要权衡其复杂性和场景的实际需求。在实时数据更新或事件驱动开发中,Stream 是不二之选;但对于简单的一次性任务,应优先考虑 Future

Stream与StreamBuilder的结合使用

StreamStreamBuilder 的结合是 Flutter 中实现实时更新 UI 的重要方式。Stream 提供数据流,StreamBuilder 监听数据流并根据变化构建 UI。以下是它们的结合使用介绍和示例。


Stream 与 StreamBuilder 的基本原理

  1. Stream
    • 用于提供异步数据流,可以持续输出事件或数据。
  2. StreamBuilder
    • 监听一个 Stream,每当流发送新的数据时,StreamBuilder 会自动重建其子树(builder 回调)。
  3. 结合使用的优势
    • 实现 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、订阅流),需要手动取消订阅以避免内存泄漏。

  • 示例:

    dart 复制代码
    late StreamSubscription<int> subscription;
    
    void startListening() {
      subscription = myStream.listen((event) {
        print('Data: $event');
      });
    }
    
    void stopListening() {
      subscription.cancel(); // 取消流监听
    }

StreamBuilder 与 State Management 的结合

StreamBuilder 可以和状态管理工具(如 BLoC 模式)结合,处理复杂的应用逻辑。

示例:StreamBuilderBLoC
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),
      ),
    );
  }
}

结合使用的优缺点

优点
  1. 实时更新 UI :当 Stream 产生新数据时,StreamBuilder 自动更新 UI。
  2. 简单易用StreamBuilder 提供了简单的 API,适合快速实现流数据与 UI 的绑定。
  3. 状态管理便利 :可结合 BLoC 等模式处理复杂的逻辑和数据流。
  4. 灵活的 UI 重构 :通过 builder 方法,可以根据流的状态动态改变 UI。
缺点
  1. 资源管理复杂 :需要注意 StreamStreamSubscription 的生命周期,避免内存泄漏。
  2. 调试困难:复杂流中,错误或状态切换可能难以调试。
  3. 性能开销:频繁流更新会频繁触发 UI 重构,需避免高频流影响性能。

总结

  • 适用场景:

    • 实时数据展示(计时器、传感器、网络数据等)。
    • 事件驱动的交互(按钮点击、输入变化等)。
    • 状态管理结合流的场景(如 BLoC 模式)。
  • 注意事项:

    • 管理好 Stream 的生命周期,及时取消未使用的流。
  • 避免高频率流更新引发性能问题。

通过恰当地利用 StreamStreamBuilder,可以有效地在 Flutter 应用中实现实时交互,从而增强用户体验。

相关推荐
贾伟康35 分钟前
【天体运行模拟|03】HarmonyOS ArkTS 场景选择实战:组织行星、卫星与天文现象入口
list·harmonyos·arkts·arkui·页面路由
加农炮手Jinx10 小时前
Flutter for OpenHarmony 实战:flutter_animate 声明式动画让 UI 灵动如原生
flutter·ui·华为·harmonyos·鸿蒙
里欧跑得慢10 小时前
Flutter 三方库 function_tree — 鸿蒙应用开发中的动态数学公式解析与计算神器,实现鸿蒙深度适配下的复杂函数逻辑运行时求值实战(适配鸿蒙 HarmonyOS Next ohos)
android·前端·安全·flutter·华为·harmonyos
2501_9197490312 小时前
华为鸿蒙免费制定规则软件—小羊规则
华为·harmonyos·鸿蒙
派小汤13 小时前
Harmony2.2.0通过RdbStore实现通用类操作本地SQLite数据库
数据库·sql·sqlite·鸿蒙·鸿蒙系统
lilian23314 小时前
HarmonyOS 7 新特性(四)|沉浸光感:空间材质、性能分级与降级策略
前端·pytorch·华为·harmonyos·材质
大雷神14 小时前
HarmonyOS AR Engine 物体形状实战:把实体书识别为 RECTANGLE,再放进地面 AR 盒子
ar·restful·harmonyos
AI备忘录14 小时前
(十六)GRE/IPSec 隧道配置命令五厂商对照:华为 华三 锐捷 迈普 思科
运维·服务器·网络·网络协议·网络安全·华为
BlueAsia_Lab16 小时前
华为 SuperCharge 认证,覆盖哪些产品?全品类梳理
运维·服务器·华为