前言
Stream
------ 快递分拣中心的智能传送带系统
。
在移动应用中,数据 就像血液
一样在应用中流动:用户滑动列表时不断加载的新条目、聊天室实时刷新的消息、文件下载时跳动的进度条等,这些场景背后都隐藏着一个关键问题:如何高效管理持续产生的异步数据?
传统的Future
虽然能处理单次异步操作 ,但面对连绵不断的数据流 却显得力不从心
。Stream
正是为此而生 ------ 它像一条智能传送带 ,让数据按需流动、精准分发。然而许多开发者仅停留在简单使用listen()
,却未能真正理解其设计哲学。
本文将带你穿透API
表层,用系统化思维构建Stream
知识体系,最终通过企业级实战案例,让你掌握"让数据流动"
的艺术。
操千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意。
一、基本概念
官方定义 :
Stream
是Dart
中表示连续异步数据序列 的核心对象,用于处理多个按时间顺序传递
的异步事件。
这些事件可以是:
- 数据事件(
Data Event
) :携带业务数据 (如网络响应
、用户输入
)。 - 错误事件(
Error Event
) :传递异常信息 (如网络超时
、数据解析失败
)。 - 完成事件(
Done Event
) :标识数据流终止 (如文件读取完成
)。
其核心设计目标 是解耦数据生产与消费
,允许数据在"生产者"
和"消费者"
之间按需流动,双方无需同步等待彼此。
可将想象成一个快递分拣中心的智能传送带系统:
- 包裹 :每个数据包 (如
用户操作
、网络响应
、文件内容
)就像快递包裹,可能包含有效数据 、错误信息 (破损包裹
)或完成信号 (运输结束
)。 - 传送带 :数据按固定方向流动的通道,包裹按顺序进入传送带(
FIFO
原则),但包裹的产生 和分拣是异步进行的。 - 分拣机器人 :通过
map
、where
等操作符(类似分拣规则
)对包裹进行拆包
、检查
、重新打包
。 - 分拣中心控制台 :
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
生态深度整合
Stream
是Flutter
响应式编程体系的核心基础设施:
- 状态管理 :
BLoC
、Riverpod
等库依赖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> |
获取流的第一个数据事件(流为空时抛出错误)。 | 若流未关闭且无数据会永久等待;需用await 或catchError 处理。 |
last (扩展) |
Future<T> |
获取流的最后一个数据事件(流为空时抛出错误)。 | 必须等待流关闭;对无限流无效。 |
single (扩展) |
Future<T> |
检查流是否仅有一个数据事件(流为空或有多个数据时抛出错误)。 | 必须等待流关闭;误用于多数据流会抛出错误。 |
length (扩展) |
Future<int> |
计算流中数据事件的总数量。 | 会消费整个流的数据;对无限流导致永久阻塞。 |
关键说明:
-
原生属性 :直接定义在
Stream
类中(如isBroadcast
、isEmpty
)。 -
扩展方法 :通过
dart:async
扩展机制实现,语法类似属性(如stream.first
),本质为异步操作。 -
通用规则 :除
isBroadcast
外,其他属性/方法均需await
;直接调用会消费流数据,导致流无法重复使用。 -
开发建议:
- 优先使用
StreamBuilder
或显式listen()
处理流数据。 - 避免直接调用
first
/last
等扩展方法,除非明确需要单次数据消费,建议通过take(1)
或where
限制数据范围。 - 对广播流需手动管理订阅的取消(
subscription.cancel()
)。
- 优先使用
常见问题(FAQ
):
-
Q : 为什么
isEmpty
返回Future<bool>
而不是bool
?
A: 流的异步特性决定了必须等待数据传递完成才能判断是否为空。 -
Q :
isBroadcast
为true
时是否一定支持多监听?
A : 是的,但需确保数据源支持多监听(如StreamController.broadcast
)。 -
Q : 如何避免
single
方法抛出错误?
A : 使用take(1)
限制数据量:dartfinal 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
:
- 单订阅流在监听后,
hasListener
为true
,取消订阅后为false
。 - 广播流中任意监听者存在时,
hasListener
为true
。
④、sink
与 stream
的对称性:
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)
作用 :从同步集合(如 List
、Set
)创建流,按顺序发射所有元素后关闭。
场景:将内存中的数据集转换为流式处理管道。
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
接口,处理add
、addError
、close
事件。 - 通常用于框架开发,普通业务代码优先使用
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
手动管理数据推送。
- 外部输入 :
②、处理管道
- 操作符链 :通过
map
、where
、transform
等方法对数据流进行转换
、过滤
、聚合
。 - 内存管理:逐条处理数据,避免一次性加载全部数据到内存。
③、消费者层
- 订阅机制 :通过
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
的精髓。
欢迎一键四连 (
关注
+点赞
+收藏
+评论
)