系统化掌握Dart编程之异步编程(六):Stream筑基篇

前言

Stream ------ 快递分拣中心的智能传送带系统

在移动应用中,数据 就像血液一样在应用中流动:用户滑动列表时不断加载的新条目、聊天室实时刷新的消息、文件下载时跳动的进度条等,这些场景背后都隐藏着一个关键问题:如何高效管理持续产生的异步数据?

传统的Future虽然能处理单次异步操作 ,但面对连绵不断的数据流 却显得力不从心Stream正是为此而生 ------ 它像一条智能传送带 ,让数据按需流动、精准分发。然而许多开发者仅停留在简单使用listen(),却未能真正理解其设计哲学。

本文将带你穿透API表层,用系统化思维构建Stream知识体系,最终通过企业级实战案例,让你掌握"让数据流动"的艺术。

千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意

一、基本概念

官方定义StreamDart中表示连续异步数据序列 的核心对象,用于处理多个按时间顺序传递异步事件

这些事件可以是:

  • 数据事件(Data Event :携带业务数据 (如网络响应用户输入)。
  • 错误事件(Error Event :传递异常信息 (如网络超时数据解析失败)。
  • 完成事件(Done Event标识数据流终止 (如文件读取完成)。

核心设计目标解耦数据生产与消费,允许数据在"生产者""消费者"之间按需流动,双方无需同步等待彼此。

可将想象成一个快递分拣中心的智能传送带系统:

  • 包裹 :每个数据包 (如用户操作网络响应文件内容)就像快递包裹,可能包含有效数据错误信息破损包裹)或完成信号运输结束)。
  • 传送带 :数据按固定方向流动的通道,包裹按顺序进入传送带(FIFO原则),但包裹的产生分拣是异步进行的。
  • 分拣机器人 :通过mapwhere等操作符(类似分拣规则)对包裹进行拆包检查重新打包
  • 分拣中心控制台StreamController负责启动传送带、调节流速(pause/resume)或关闭系统(close)。

深入理解

  • 包裹按需生成 :快递车(数据源)将包裹逐个放到传送带上,无需一次性堆满所有包裹(避免内存爆炸)。
  • 异步分拣 :分拣工人(消费者)无需守在传送带起点,包裹到达时会自动触发分拣动作(监听回调)。
  • 多级分拣流程 :包裹可经过多个分拣站(操作符链),依次完成过滤变形合并等操作(如stream.map(...).where(...))。

核心思想

Stream的本质是一个高度可定制的异步分拣系统 ,开发者扮演分拣中心总控师的角色,通过定义包裹来源生产者)、分拣规则操作符)、派送策略监听逻辑),实现数据从源头到终点的智能流动 。掌握Stream,就是掌握让数据像快递包裹一样高效精准有序流动的能力。


二、核心特性

2.1、异步性

  • 数据按非阻塞方式传递,消费者通过订阅listen)被动接收事件,无需主动轮询
  • 数据可能在任何时间到达,消费者通过回调函数await for处理。

2.2、序列性

  • 事件严格按先进先出FIFO)顺序传递,保证处理顺序与发送顺序一致。

2.3、多监听者支持

  • 单订阅流(Single-Subscription :仅允许一个监听者,确保数据完整性和顺序性(默认类型)。
  • 广播流(Broadcast :允许多个监听者,适用于事件广播场景(需显式声明isBroadcast: true)。

2.4、冷流(Cold)与热流(Hot

  • 冷流 :数据生成从监听时开始,每次监听会重新触发数据生产(如async*生成的流)。
  • 热流 :数据实时流动,与监听时机无关(如StreamController创建的流)。

2.5、物流类比理解

  • 包裹来源

    • 发件人(生产者) :可能是仓库(StreamController)、自动包裹生成机(async*函数),或外部快递联盟(WebSocket)。

    • 包裹产生规则

      dart 复制代码
      // 示例:模拟快递包裹生成(每秒产生一个包裹)  
      Stream<String> generatePackages() async* {  
        int count = 1;  
        while (true) {  
          await Future.delayed(Duration(seconds: 1));  
          yield "包裹-$count";  // 生成包裹,如"包裹-1", "包裹-2"  
          count++;  
        }  
      }  
  • 分拣流程

    • 单线分拣 vs 多线分拣

      • 单线分拣(Single-Subscription Stream
        一个传送带只服务一个分拣工人(监听者),确保包裹顺序严格一致(如重要文件必须按顺序签收)。
      • 多线分拣(Broadcast Stream
        传送带广播包裹,允许多个分拣工人同时处理(如多个分拣机器人同时扫描包裹条形码)。
    • 冷流 vs 热流

      类型 快递场景类比 Stream行为
      冷流 预约制快递:客户下单后,仓库才开始打包发货 每次监听时重新开始生成数据(async*生成的流)
      热流 实时物流:包裹已在运输中,新客户只能收到当前及之后的包裹 数据实时流动,与监听时机无关(StreamController创建的流)
  • 分拣控制

    • 包裹拦截subscription.pause()(暂停分拣)
    • 恢复分拣subscription.resume()
    • 终止分拣subscription.cancel()(工人离开岗位)或controller.close()(关闭分拣中心)

三、核心价值

3.1、解决持续性异步数据流的天然需求

许多场景中,数据并非一次性产生,而是持续动态按节奏生成的。例如:

  • 实时通信聊天消息WebSocket推送需要持续监听并处理 。
  • 用户交互屏幕滑动事件输入框内容变化需要实时响应。
  • 数据监控传感器数据设备状态需要周期性采集。

Stream为此类场景提供了原生支持 ,无需手动管理事件队列定时轮询,只需通过listen()订阅数据流,即可自动接收并处理异步事件。


3.2、实现资源高效利用内存安全

传统一次性异步操作(如Future)在处理大规模数据时面临挑战:

  • 内存压力 :若一次性加载全部数据(如1GB文件),可能引发内存溢出
  • 响应延迟:用户需等待所有数据加载完成才能交互。

Stream通过逐块处理数据解决这些问题:

dart 复制代码
// 逐行读取大文件,内存占用恒定  
File('large_file.txt')  
    .openRead()                  // 创建字节流  
    .transform(utf8.decoder)     // 转换为文本流  
    .transform(LineSplitter())   // 拆分为行数据流  
    .listen((line) => processLine(line)); // 逐行处理  

数据像流水一样逐块流过处理管道,每个数据块处理完成后立即释放内存,确保应用高效稳定运行。


3.3、构建声明式数据处理管道

Stream允许通过链式操作符 组合数据处理逻辑,显著提升代码可读性可维护性

搜索框优化示例

dart 复制代码
searchInput.stream  
    .distinct()                            // 去重:输入内容相同时跳过  
    .where((query) => query.length > 2)    // 过滤:至少3个字符才搜索  
    .asyncMap((query) => fetchResults(query)) // 异步获取结果  
    .listen(updateUI);                     // 更新界面  

通过声明式管道清晰表达业务规则避免回调嵌套逻辑层次分明


3.4、与Flutter生态深度整合

StreamFlutter响应式编程体系的核心基础设施:

  • 状态管理BLoCRiverpod等库依赖Stream实现状态变化通知。
  • UI更新StreamBuilder组件将数据流自动映射到界面重建。
  • 跨组件通信 :通过Stream实现松散耦合的组件间数据传递。

计数器应用

dart 复制代码
// BLoC类管理计数逻辑  
class CounterBloc {  
  final _counterController = StreamController<int>();  
  int _count = 0;  

  Stream<int> get counter => _counterController.stream;  

  void increment() {  
    _count++;  
    _counterController.sink.add(_count);  
  }  

  void dispose() => _controller.close();  
}  

// UI层通过StreamBuilder绑定数据  
StreamBuilder<int>(  
  stream: counterBloc.counter,  
  builder: (context, snapshot) {  
    return Text('Count: ${snapshot.data ?? 0}');  
  },  
)  

这种模式将业务逻辑UI彻底解耦,提升代码可测试性可维护性


3.5、处理复杂异步协作场景

Stream提供丰富的组合操作符,简化多数据流协作:

  • 并行处理Stream.asyncExpand同时处理多个异步任务
  • 数据合并StreamZip同步多个流的最新数据
  • 错误隔离handleError实现细粒度的异常捕获与恢复

表单多字段验证

dart 复制代码
final usernameStream = usernameController.stream;  
final passwordStream = passwordController.stream;  

Stream<bool> get isFormValid =>  
    StreamZip([usernameStream, passwordStream])  
        .map((credentials) =>  
            credentials[0].isNotEmpty && credentials[1].length >= 6)  
        .distinct(); // 仅在验证状态变化时触发  

通过流合并自动跟踪多个输入字段状态,实时更新表单提交按钮的可用性。


3.6、核心价值体系

维度 传统方式缺陷 Stream解决方案
数据处理模式 一次性加载,内存压力大 逐块流式处理,内存占用恒定
代码可维护性 回调嵌套,逻辑分散 声明式管道,逻辑集中可追溯
实时响应能力 轮询开销大,响应延迟 订阅推送机制,即时触发
复杂场景支持 手动协调多任务,易出错 内置操作符处理合并、错误、流量控制

掌握Stream意味着获得:

  • 1、系统化的异步编程思维 :将数据流动抽象为可观测可控制的管道。
  • 2、高效的技术工具集 :通过操作符组合解决复杂异步问题。
  • 3、面向未来的架构能力 :深度融入Flutter响应式生态,构建可扩展应用。

Stream不是可选技能,而是处理异步编程的核心基础设施 。它如同编程世界的"中枢神经系统",让数据在应用中智能流动,驱动功能模块高效协作。

四、属性详解

4.1、Stream属性详解

属性 类型 作用描述 注意事项
isBroadcast(原生) bool 标识是否为广播流(允许多个监听者)。 单订阅流不可重复监听;广播流需通过asBroadcastStream()StreamController.broadcast()创建。
isEmpty(原生) Future<bool> 异步判断流是否为空(无任何数据事件)。 需等待流完成;调用后消费数据,后续无法监听。
first(扩展) Future<T> 获取流的第一个数据事件(流为空时抛出错误)。 若流未关闭且无数据会永久等待;需用awaitcatchError处理。
last(扩展) Future<T> 获取流的最后一个数据事件(流为空时抛出错误)。 必须等待流关闭;对无限流无效。
single(扩展) Future<T> 检查流是否仅有一个数据事件(流为空或有多个数据时抛出错误)。 必须等待流关闭;误用于多数据流会抛出错误。
length(扩展) Future<int> 计算流中数据事件的总数量。 会消费整个流的数据;对无限流导致永久阻塞。

关键说明

  • 原生属性 :直接定义在 Stream 类中(如 isBroadcastisEmpty)。

  • 扩展方法 :通过 dart:async 扩展机制实现,语法类似属性(如 stream.first),本质为异步操作

  • 通用规则 :除 isBroadcast 外,其他属性/方法均需 await;直接调用会消费流数据,导致流无法重复使用。

  • 开发建议

    • 优先使用 StreamBuilder 或显式 listen() 处理流数据。
    • 避免直接调用 first/last 等扩展方法,除非明确需要单次数据消费,建议通过 take(1)where 限制数据范围。
    • 对广播流需手动管理订阅的取消(subscription.cancel())。

常见问题(FAQ):

  • Q : 为什么 isEmpty 返回 Future<bool> 而不是 bool
    A: 流的异步特性决定了必须等待数据传递完成才能判断是否为空。

  • Q : isBroadcasttrue 时是否一定支持多监听?
    A : 是的,但需确保数据源支持多监听(如 StreamController.broadcast)。

  • Q : 如何避免 single 方法抛出错误?
    A : 使用 take(1) 限制数据量:

    dart 复制代码
    final result = await stream.take(1).single;  

4.2、StreamController属性详解

属性 类型 作用描述 注意事项
stream Stream<T> 控制器关联的输出流,供外部监听数据。 必须通过 listen() 订阅后才能接收数据。
sink StreamSink<T> 数据入口,用于添加数据(add())、错误(addError())或关闭流(close())。 调用 close() 后继续添加数据会抛出 StateError
isClosed bool 标识控制器是否已关闭(不再接收新数据)。 关闭后不可逆,需通过 controller.close() 终止。
isPaused bool 标识流是否被暂停(通过 pause() 方法)。 需手动调用 resume() 恢复数据流动。
hasListener bool 标识是否有活跃的监听者(通过 listen() 订阅且未取消)。 广播流中可能有多个监听者,但此属性仅表示至少存在一个。
done Future<void> 控制器关闭时完成的 Future(通过 close() 触发)。 用于等待流完全关闭(如资源释放后执行回调)。

关键说明

①、isPaused

  • 调用 pause() 会暂停数据发送,但不会缓存数据,恢复后仅发送后续数据。
  • 若需缓存暂停期间的数据,需配合 StreamController(sync: true) 或自定义缓冲逻辑。

②、done

dart 复制代码
 await controller.close();  
 await controller.done; // 确保流完全关闭  

③、hasListener

  • 单订阅流在监听后,hasListenertrue,取消订阅后为 false
  • 广播流中任意监听者存在时,hasListenertrue

④、sinkstream 的对称性

  • sink 是写入端,stream 是读取端,二者共同构成完整的流管道。

完整生命周期示例

dart 复制代码
final controller = StreamController<int>();  

// 监听数据流  
final subscription = controller.stream.listen(print);  

// 添加数据  
controller.sink.add(1);  
controller.sink.add(2);  

// 暂停流  
controller.sink.pause();  
print(controller.isPaused); // 输出:true  

// 恢复流  
controller.sink.resume();  

// 关闭流  
await controller.close();  
print(controller.isClosed); // 输出:true  

// 等待流完全关闭  
await controller.done;  

五、核心方法详解

5.1、工厂构造函数

①、.fromIterable(Iterable<T> elements)

作用 :从同步集合(如 ListSet)创建流,按顺序发射所有元素后关闭。
场景:将内存中的数据集转换为流式处理管道。

dart 复制代码
Stream<int> stream = Stream.fromIterable([1, 2, 3]);  
stream.listen(print); // 输出:1 → 2 → 3 → onDone  

注意事项

  • 数据立即发射,适用于已知数据集。
  • 若需异步生成数据,改用 async* 生成器。

②、.fromFuture(Future<T> future)

作用 :将单个 Future 转换为流,发射其结果(成功值或错误)后关闭。
场景 :将异步操作(如网络请求文件 I/O)整合到流中。

dart 复制代码
Future<int> fetchData() async => 42;  
Stream.fromFuture(fetchData()).listen(print); // 输出:42 → onDone  

注意事项

  • Future 失败,流会发射错误事件并关闭。

③、.fromFutures(Iterable<Future<T>> futures)

作用 :将多个 Future 转换为流,发射其结果(成功值或错误)后关闭。
场景 :将多个异步操作整合到流中。

dart 复制代码
Future<int> waitTask() async {
  await Future.delayed(Duration(seconds: 2));
  return 10;
}

Future<String> doneTask() async {
  await Future.delayed(Duration(seconds: 5));
  return 'Future complete';
}

void main() {
  final stream = Stream<Object>.fromFutures([doneTask(), waitTask()]);
  stream.listen(print, onDone: () => print('Done'), onError: print);
}

// 输出:
// 10(2秒后)
// Future complete(5秒后)
// Done

注意事项

  • 单订阅流 :不可多次调用 listen(),否则抛出 StateError
  • 错误处理 :任意 Future 抛出错误会触发流的 onError,但流继续处理其他 Future
  • 顺序问题 :结果按 Future 完成顺序发射,futures 的原始顺序

④、.periodic(Duration period, [T Function(int)? computation])

作用周期性生成事件流,每个周期触发一次。
场景 :定时任务(如轮询心跳检测)。

dart 复制代码
// 每秒发射一个递增整数  
Stream.periodic(Duration(seconds: 1), (count) => count)  
    .take(3)  
    .listen(print); // 输出:0 → 1 → 2 → onDone  

注意事项

  • computation 可为空,此时事件值为 null
  • 使用 take/takeWhile 限制事件数量,避免无限流。

⑤、Stream.value(T value)

作用 :创建单值流,立即发射数据后关闭。
场景 :快速包装常量简单值

dart 复制代码
Stream.value("Hello").listen(print); // 输出:Hello → onDone  

注意事项

  • 等效于 Stream.fromIterable([value]),但更简洁。

⑥、.error(Object error, [StackTrace? stackTrace])

作用 :创建错误流,立即发射错误后关闭。
场景:模拟错误场景或传递异常。

dart 复制代码
Stream.error(Exception("Timeout"))  
    .listen(null, onError: print); // 输出:Exception: Timeout → onDone  

注意事项

  • 必须处理 onError,否则错误会向上传播。

⑦、.empty()

作用 :创建空流,直接触发完成事件。
场景占位条件分支中无数据的场景。

dart 复制代码
Stream.empty().listen(  
  print,  
  onDone: () => print("Completed"),  
); // 输出:Completed  

⑧、.multi(void onListen(StreamController<T> controller))

作用手动控制流事件,通过回调暴露 StreamController
场景 :需要动态生成数据自定义复杂逻辑

dart 复制代码
Stream<int> countStream(int max) {  
  return Stream.multi((controller) {  
    for (int i = 1; i <= max; i++) {  
      controller.add(i);  
    }  
    controller.close();  
  });  
}  
countStream(3).listen(print); // 输出:1 → 2 → 3 → onDone  

注意事项

  • 必须手动关闭控制器,否则监听者会无限等待。
  • 适用于需要完全控制事件发射顺序和时机的场景。

⑨、.eventTransformed(Stream source, EventSink<T> transform(EventSink<T> sink))

作用 :通过自定义 EventSink 转换源流的事件。
场景 :实现底层流转换逻辑(如自定义协议解析)。

dart 复制代码
final source = Stream.fromIterable([1, 2, 3]);  
final transformed = Stream.eventTransformed(source, (sink) => MyCustomSink(sink));  

注意事项

  • 需实现 EventSink 接口,处理 addaddErrorclose 事件。
  • 通常用于框架开发,普通业务代码优先使用 transform + StreamTransformer

⑩、选择策略

场景 推荐方法 替代方案
同步数据集转流 fromIterable async* 生成器
单次异步操作转流 fromFuture Future.then + StreamController
多个 Future 处理 fromFutures fromIterable + asyncMap
定时/周期性事件 periodic Timer.periodic + StreamController
快速包装单值 value fromIterable([value])
错误模拟或传递 error throw + async*
动态生成数据流 multi 自定义 StreamTransformer

5.2、核心高频使用方法:第一梯队

几乎每个 Stream 使用场景都会用到的方法,覆盖 80% 的日常开发需求。

①、listen(void onData(T event)?, {onError, onDone, cancelOnError})

作用 :订阅流并处理数据错误完成事件,返回 StreamSubscription 对象。

dart 复制代码
final subscription = stream.listen(
  (data) => print(data),
  onError: (e) => print("Error: $e"),
  onDone: () => print("Done"),
);

注意事项

  • 必须手动取消订阅 :调用 subscription.cancel() 避免内存泄漏。
  • cancelOnError :默认为 false,若设为 true,第一个错误会取消订阅。

②、map<S>(S Function(T) convert)

作用:同步转换每个数据事件。

dart 复制代码
stream.map((num) => num * 2).listen(print); // 输入1 → 输出2  

注意事项

  • 转换函数应为纯函数(无副作用)。

③、where(bool Function(T) test)

作用:过滤不符合条件的数据事件。

dart 复制代码
stream.where((num) => num > 10).listen(print);  

注意事项

  • 过滤条件应避免复杂计算,防止阻塞事件循环。

④、asyncMap<S>(FutureOr<S> Function(T) convert)

作用 :异步转换每个数据事件(返回 Future)。

dart 复制代码
stream.asyncMap((id) => fetchUser(id)).listen(print);  

注意事项

  • 输出顺序与输入顺序一致,即使异步任务完成时间不同。

⑤、handleError(Function onError, {bool test(Object error)?})

作用:捕获并处理流中的错误事件。

dart 复制代码
stream.handleError(
  (e) => print("Handled: $e"),
  test: (e) => e is NetworkException,
);

注意事项

  • 必须重新抛出未处理的错误,否则会静默忽略。

⑥、take(int count)

作用 :仅取前 count 个数据后关闭流。

dart 复制代码
stream.take(3).listen(print); // 仅接收前3个数据  

⑦、skip(int count)

作用 :跳过前 count 个数据。

dart 复制代码
stream.skip(2).listen(print); // 跳过前2个数据  

5.3、控制流方法:第二梯队

用于复杂流控制或数据流整形,覆盖 15% 的中级场景。

①、expand<S>(Iterable<S> Function(T) convert)

作用 :将每个数据事件展开为多个事件(类似 flatMap)。

dart 复制代码
stream.expand((num) => [num, num * 10]).listen(print); // 输入1 → 输出1,10  

②、takeWhile(bool Function(T) test)

作用 :取数据直到条件为 false,之后关闭流。

dart 复制代码
stream.takeWhile((num) => num < 5).listen(print);  

③、skipWhile(bool Function(T) test)

作用 :跳过数据直到条件为 false,之后保留剩余数据。

dart 复制代码
stream.skipWhile((num) => num < 3).listen(print);  

④、distinct([bool Function(T, T)? equals])

作用:跳过连续重复的数据事件。

dart 复制代码
stream.distinct().listen(print); // 输入1,1,2 → 输出1,2  

5.4、高级操作与资源管理:第三梯队

用于底层流操作或资源控制,覆盖 5% 的高级场景。

①、transform<S>(StreamTransformer<T, S> transformer)

作用:应用自定义转换器(如编解码、协议解析)。

scss 复制代码
dart
stream.transform(utf8.decoder).listen(print);  

注意事项

  • 需实现 StreamTransformer 接口。

②、pipe(StreamConsumer<T> consumer)**

作用 :将流数据直接传输到 StreamConsumer(如文件写入)。

dart 复制代码
stream.pipe(File('output.txt').openWrite());

③、drain<T>([T? futureValue])

作用:消费流中所有剩余数据但不处理,用于资源清理。

dart 复制代码
await stream.drain(); // 确保流完全消费  

④、cast<S>()

作用:将流的数据类型强制转换为指定类型。

dart 复制代码
Stream<num> numbers = Stream<int>.fromIterable([1, 2, 3]).cast<num>();

⑤、asBroadcastStream()

作用:将单订阅流转换为广播流。

dart 复制代码
final broadcastStream = stream.asBroadcastStream();

5.5、边缘或聚合操作:第四梯队

用于特定场景的聚合操作或边缘需求。

①、contains(Object? value)

作用 :检查流是否包含指定值,返回 Future<bool>

dart 复制代码
final exists = await stream.contains(42);

②、forEach(void Function(T) action)

作用 :对每个数据执行操作,返回 Future<void>

dart 复制代码
await stream.forEach(print);  

③、reduce(T Function(T, T) combine)

作用:聚合所有数据为单个结果(需流非空)。

dart 复制代码
final sum = await stream.reduce((a, b) => a + b);  

④、join([String separator = ""])

作用:将流中的数据拼接为字符串。

dart 复制代码
final result = await stream.join(",");  

⑤、every(bool Function(T) test)

作用 :检查所有数据是否满足条件,返回 Future<bool>

dart 复制代码
final allValid = await stream.every((num) => num > 0);  

5.6、选择策略

梯队 方法 核心用途 使用频次
第一梯队 listen, map, where 基础数据订阅、转换和过滤 极高
第二梯队 expand, takeWhile 流数据整形与控制
第三梯队 transform, asBroadcast 底层流操作与资源管理
第四梯队 contains, reduce 聚合操作与边缘需求 极低

六、基本用法

Stream 的基本使用流程可分为四步:创建流 → 监听流 → 操作流 → 关闭流

6.1、创建流

①、工厂构造函数(推荐)

arduino 复制代码
// 1、从集合创建 同步数据流
Stream<int> stream = Stream.fromIterable([1, 2, 3]);

// 2、从异步任务创建 异步数据流
Stream<int> stream =
    Stream.fromFuture(Future.delayed(Duration(seconds: 1), () => 42));

// 3、创建周期性数据流 每秒发射递增整数
Stream<int> stream =
    Stream.periodic(Duration(seconds: 1), (count) => count);

②、使用 StreamController动态控制流

dart 复制代码
final controller = StreamController<int>();  

// 添加数据  
controller.sink.add(1);  
controller.sink.addError(Exception("Error"));  
controller.sink.close();  

// 获取输出流  
Stream<int> outputStream = controller.stream;  

6.2、监听流

①、基础监听

dart 复制代码
StreamSubscription<int> subscription = stream.listen(
  (data) => print("数据: $data"),  // 处理数据
  onError: (error) => print("错误: $error"),  // 处理错误
  onDone: () => print("流已关闭"),  // 处理完成
  cancelOnError: true,  // 第一个错误时自动取消订阅
);  

②、订阅管理

dart 复制代码
// 取消订阅
subscription.cancel();  // 释放资源,防止内存泄漏 

//暂停/恢复
subscription.pause();   // 暂停接收数据  
subscription.resume();  // 恢复接收数据 

6.3、操作流

①、转换数据

dart 复制代码
// 同步转换(map)
stream.map((num) => num * 2).listen(print); // 输入1 → 输出2  

// 异步转换(asyncMap)
stream.asyncMap((id) => fetchUser(id)).listen(print); // 异步请求用户数据 

②、过滤数据

dart 复制代码
// 条件过滤(where)
stream.where((num) => num > 10).listen(print); // 只保留大于10的数据  

// 数量限制(take/skip)
stream.take(3).listen(print);  // 仅取前3个数据  
stream.skip(2).listen(print); // 跳过前2个数据  

③、错误处理

dart 复制代码
// 全局捕获错误(`handleError`)
stream.handleError(
    (e) => print("捕获错误: $e"), 
    test: (e) => e is NetworkException  // 只处理特定错误
).listen(print);  

6.4、关闭流与资源释放

①、关闭 StreamController

dart 复制代码
controller.close(); // 关闭控制器,触发onDone事件  

②、使用 await for 处理流

dart 复制代码
try {
  await for (final data in stream) {
    print(data);
  }
} catch (e) {
  print("捕获错误: $e");
}  

③、清理资源

dart 复制代码
// 取消所有订阅
subscription.cancel();  

// 释放控制器
if (!controller.isClosed) {  
    await controller.close();  
}  

6.5、小试牛刀:实时搜索功能

dart 复制代码
// 1. 创建输入流(如搜索框文本变化)  
final searchController = StreamController<String>();  

// 2. 操作流:过滤空值、请求API  
searchController.stream  
    .where((query) => query.isNotEmpty)     // 过滤空字符串  
    .asyncMap((query) => fetchSearchResults(query)) // 异步搜索  
    .listen(updateUI); // 更新界面  

// 3. 模拟用户输入  
searchController.sink.add("Dart");  
searchController.sink.add("Flutter");  

// 4. 关闭资源  
await searchController.close();  

七、设计哲学

7.1、分层架构设计

Stream 的运行机制可分为三个层次

①、生产者层

  • 数据源 :产生异步事件的源头,包括:
    • 外部输入用户交互网络请求文件 I/O
    • 生成器 :通过async*函数按需生成数据。
    • 控制器StreamController手动管理数据推送。

②、处理管道

  • 操作符链 :通过mapwheretransform等方法对数据流进行转换过滤聚合
  • 内存管理:逐条处理数据,避免一次性加载全部数据到内存。

③、消费者层

  • 订阅机制 :通过listen()await for监听数据流。
  • 资源释放 :通过cancel()终止监听,或close()关闭数据源。

核心代码结构解析

dart 复制代码
// 1. 生产者层:创建数据流  
Stream<int> generateData() async* {  
  for (int i = 1; i <= 5; i++) {  
    await Future.delayed(Duration(seconds: 1));  
    yield i; // 每秒发送一个数字  
  }  
}  

// 2. 处理管道:转换与过滤  
final processedStream = generateData()  
    .map((num) => num * 2)      // 数值翻倍  
    .where((num) => num > 3);   // 过滤小于等于3的值  

// 3. 消费者层:订阅数据流  
final subscription = processedStream.listen(  
  (data) => print('接收数据: $data'),  
  onError: (err) => print('错误: $err'),  
  onDone: () => print('流已关闭'),  
);  

// 4. 资源释放(可选)  
Future.delayed(Duration(seconds: 3), () => subscription.cancel());  

7.2、核心行为模式

①、订阅驱动

  • 数据仅在存在活跃订阅者时流动,无监听者时数据可能被丢弃。
  • 示例:StreamController的数据在无监听者时不会被缓存

②、背压控制

  • 通过pause/resume动态调节数据流速,防止消费者处理不过来导致内存堆积

③、错误传播

  • 错误事件沿操作符链向上传递,直到被handleError捕获或导致程序崩溃

7.3、与 Future 的本质区别

维度 Stream Future
数据量 处理多个异步事件 处理单个异步结果
生命周期 持续存在,直到主动关闭 一次性完成(成功或失败)
监听机制 支持多次监听(广播流)或单次监听 仅单次完成,无法重复监听
典型场景 实时聊天、文件流式传输、用户事件监听 单次网络请求、数据库查询

八、总结

掌握Stream的秘诀在于构建数据管道思维

  • 1、理解本质 :把流视为可编程的数据管道,关注数据入口→加工环节→出口消费
  • 2、层次递进:从基础监听→操作符链式调用→多流协作,逐级解锁高阶用法 。
  • 3、实战驱动 :将聊天室、分页加载等场景抽象为流模型,用listen+transform+combine组合拳解决。

优秀的Flutter开发者不仅是界面工程师 ,更是数据流动的架构师 。当你能像设计水利工程一样设计数据管道时,便是真正系统化掌握了Stream的精髓。

欢迎一键四连关注 + 点赞 + 收藏 + 评论

相关推荐
砖厂小工3 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心4 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心4 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
shankss5 小时前
Flutter 下拉刷新库 pull_to_refresh_plus 设计与实现分析
flutter
Kapaseker6 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴6 小时前
Android17 为什么重写 MessageQueue
android
忆江南21 小时前
iOS 深度解析
flutter·ios
明君8799721 小时前
Flutter 实现 AI 聊天页面 —— 记一次 Markdown 数学公式显示的踩坑之旅
前端·flutter
恋猫de小郭1 天前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
MakeZero1 天前
Flutter那些事-交互式组件
flutter