Stream是Dart用来处理异步的API,和一样用来处理异步的Future不一样的是,Stream能够异步的返回多个结果,而Future只能返回一个,若是你对Future有疑问,能够参考做者的上一篇文章,Dart基础——Dart异步Future与事件循环Event Loop。html
Stream periodicStream = Stream.periodic(Duration(seconds: 2), (num) { return num; });
periodic构造方法主要有两个参数,第一个参数类型为Duration(时间间隔),第二个参数类型为Function,Function每隔一个Duration(时间间隔)会被调用一次,参数num为事件调用的次数,从0开始依次递增。git
翻阅源码 Stream.periodic是使用Timer.periodic加_SyncStreamController实现的github
Stream<String> timedCounter(Duration interval, [int maxCount]) async* { int i = 0; while (true) { //延迟interval(时间间隔)执行一次 await Future.delayed(interval); //返回i i++ yield "stream返回${i++}"; if (i == maxCount) break; } }
看到这里你可能会有一些疑问什么是async*和yield?api
yield为一个用async *修饰返回值为Stream的函数返回一个值,它就像return,不过他不会结束函数app
Stream asynchronousNaturalsTo(n) async* { int k = 0; while (k < n) yield k++; }
这里涉及到了Dart的生成器函数概念,在这里你只须要简单理解yield的做用就能够了less
var _controller = StreamController<int>(); var _count = 1; createStream() { //函数每隔一秒调用一次 Timer.periodic(Duration(seconds: 1), (t) { _controller.sink.add(_count); _count++; }); }
咱们主要使用_controller
的两个属性,使用_controller.Stream
获取流,使用_controller.sink.add
向流中添加数据,上面的例子使用定时器,每隔一秒向流中添加数据_count
。异步
接下来介绍一下Stream的经常使用方法async
PS:如下Stream经常使用方法的展现都是用下面代码建立的流ide
Stream periodicStream = Stream.periodic(Duration(seconds: 1), (num) { return num; });
listen做为使用Stream最重要的方法,主要用于监听流的数据变化,每当流的数据变化时,listen中的方法都会被调用。函数
periodicStream.listen((event) { print(event); });
listen方法默认参数为Function,参数中的event为上面示例中返回的num,每当流返回新的数据时,listen方法都会被调用。
控制台输出以下
0 1 2 3 4
打印流返回的数据
listen的onError参数当流出现错误时调用。
listen的onDone参数当流关闭时调用。
还有一个cancelOnError
属性,默认状况下为true,能够将其设置为false以使订阅在发生错误后也能继续进行。
Stream.periodic(Duration(seconds: 1), (num) { return num; }).map((num) => num * 2)
使用map将流返回的数据进行转换
控制台输出以下
0 2 4 6
经过Stream的asBroadcastStream()或StreamController的broadcast将单订阅的流转换为多订阅流
什么是单订阅流和多订阅流?
单订阅流顾名思义,此流只能有一个订阅者,也就是单订阅流的listen方法只能被调用一次,当第二次调用单订阅流的listen时会报错,值得一提的是,当咱们建立流时,默认建立的就是单订阅流。
顾名思义,此流能够有多个订阅者,也就是多订阅流的listen方法能够被屡次调用,经过Stream的asBroadcastStream()或StreamController的broadcast将单订阅流转换为多订阅流。
Stream broadcastStream = Stream.periodic(Duration(seconds: 5), (num) { return num; }).asBroadcastStream();
var _controller = StreamController<int>.broadcast()
第一个区别就是上面提到的订阅者数量的区别
咱们重点要谈论一下两种流的第二个区别
第二个区别就是单订阅流会持有本身的数据,当订阅者出现时将自身持有的数据所有返回给订阅者,而多订阅流不会持有任何数据,若是多订阅流没有订阅者,多订阅流会把数据丢弃。
下面咱们用两端代码来展现两种流处理数据上的差异
建立流
var _controller = StreamController<int>.broadcast(); var _count = 1; createStream() { Timer.periodic(Duration(seconds: 1), (t) { _controller.sink.add(_count); _count++; }); }
订阅流
createStream(); Future.delayed(Duration(seconds: 5), () { _controller.stream.listen((event) { print("单订阅流$event"); }); });
控制台输出以下
能够看到,单订阅流即便前五秒咱们没有订阅,但单订阅流仍是在持有数据,当订阅者出现时将持有的全部数据发送给订阅者。
建立流
var _controller = StreamController<int>.broadcast(); var _count = 1; createStream() { Timer.periodic(Duration(seconds: 1), (t) { _controller.sink.add(_count); _count++; }); }
订阅流
createStream(); Future.delayed(Duration(seconds: 5), () { _controller.stream.listen((event) { print("多订阅流$event"); }); }); Future.delayed(Duration(seconds: 10), () { _controller.stream.listen((event) { print("多订阅流二$event"); }); });
控制台输出
能够看到多订阅流产生的前五条数据都被丢弃了,只有订阅者出现后生成的数据被发送给了订阅者。
代码看完想必你已经理解了单订阅流与多订阅流的第二种区别,我制做了两种流程图帮助你理解
处理 Stream 的方法
下面这些 Stream 类中的方法能够对 Stream 进行处理并返回结果:
Future<T> get first; Future<bool> get isEmpty; Future<T> get last; Future<int> get length; Future<T> get single; Future<bool> any(bool Function(T element) test); Future<bool> contains(Object needle); Future<E> drain<E>([E futureValue]); Future<T> elementAt(int index); Future<bool> every(bool Function(T element) test); Future<T> firstWhere(bool Function(T element) test, {T Function() orElse}); Future<S> fold<S>(S initialValue, S Function(S previous, T element) combine); Future forEach(void Function(T element) action); Future<String> join([String separator = ""]); Future<T> lastWhere(bool Function(T element) test, {T Function() orElse}); Future pipe(StreamConsumer<T> streamConsumer); Future<T> reduce(T Function(T previous, T element) combine); Future<T> singleWhere(bool Function(T element) test, {T Function() orElse}); Future<List<T>> toList(); Future<Set<T>> toSet();
咱们能够使用StreamSubscription对象来对流的订阅进行管理,listen方法的返回值就是StreamSubscription对象
StreamSubscription subscription = Stream.periodic(Duration(seconds: 1), (num) { return num; }).listen((num) { print(num); });
subscription.pause();
subscription.resume();
subscription2.cancel();
当不须要监听流时记得调用这个方法,不然会形成内存泄漏
如下示例用来展现如何操做流订阅
建立流
static var _controller = StreamController<int>(); var _count = 1; createStream() { Timer.periodic(Duration(seconds: 1), (t) { _controller.sink.add(_count); _count++; }); }
建立监听及监听管理对象
StreamSubscription subscription2 = _controller.stream.listen((event) { print("单订阅流$event"); });
操做流订阅的方法
createStream(); Future.delayed(Duration(seconds: 3), () { print("暂停"); subscription2.pause(); }); Future.delayed(Duration(seconds: 5), () { print("继续"); subscription2.resume(); }); Future.delayed(Duration(seconds: 7), () { print("取消"); subscription2.cancel(); });
输出以下
StreamBuilder组件主要有两个参数
第一个参数stream,要订阅的流
第二个参数builder,widget构建函数
能够使用builder函数的snapshot.connectionState属性根据流的不一样状态返回不一样的组件
每当StreamBuilder监听的stream有数据变化时,builder函数就会被调用,组件从新构建。
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_demo/util/util.dart'; /// Copyright (C), 2020-2020, flutter_demo /// FileName: streamBuilder_demo /// Author: Jack /// Date: 2020/12/27 /// Description: class StreamBuilderDemo extends StatelessWidget { //建立流 Stream<int> _stream() { Duration interval = Duration(seconds: 1); Stream<int> stream = Stream<int>.periodic(interval, (num) { return num; }); stream = stream.take(59); return stream; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Stream Demo'), ), body: Center( child: StreamBuilder( stream: _stream(), builder: (BuildContext context, AsyncSnapshot<int> snapshot) { if (snapshot.connectionState == ConnectionState.done) { return Text( '1 Minute Completed', style: TextStyle( fontSize: 30.0, ), ); } else if (snapshot.connectionState == ConnectionState.waiting) { return Text( 'Waiting For Stream', style: TextStyle( fontSize: 30.0, ), ); } return Text( '00:${snapshot.data.toString().padLeft(2, '0')}', style: TextStyle( fontSize: 30.0, ), ); }, ), ), ); } }
上文全部的代码示例都在做者的GiuHub上,https://github.com/jack0-0wu/flutter_demo,里面还包含了一些经常使用flutter功能的展现。