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功能的展現。