Flutter之Stream(一) 介绍

2,112 阅读4分钟

官网英文地址

官网中文地址

异步编程:Stream

重点:

  • Stream 提供异步数据序列
  • 数据序列包括用户生成事件和从文件中读取的数据
  • 你可以使用 await for 或者 listen()处理Stream
  • Stream提供了响应错误的方法
  • 有两种Stream:single subscription(单订阅者) 或者 broadcast(广播)。

Future和Stream类是Dart异步编程的核心。

Future表示一个无法立即完成的计算过程。普通函数返回结果,而异步函数返回Future。 这个Future最终会包含结果,当结果准备好时,Future会通知调用者。

异步函数:被 async标记的函数。 例如:Future lookUpVersion() async => '1.0.0'; 只能返回Future

Stream是异步事件序列。这就像是一个异步迭代器。不会立即获取值,而是在Stream准备好事件时,获取到迭代值。

Stream初步使用

一个简单的Stream使用过程,对Stream产生直观的印象。

  • 创建Stream:可以通过async*和 yield ,当然还有其他方式
  • 监听Stream:stream.listen(),返回一个StreamSubscription
  • 结束Stream:事件全部提交onDone,或者手动StreamSubscription.cancel()

Demo地址 入口 main_stream.dart

stream1_simple_use.dart

//1.Stream创建
//创建的方式有多种,这里我们使用异步生成器(async*)
//周期性发送整数Stream,当使用yield时,向Stream提交事件(一个整数)
Stream<int> timedCounter(Duration interval, [int maxCount]) async* {
  int i = 0;
  while (true) {
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

main() {
  Stream<int> stream = timedCounter(Duration(milliseconds: 200), 5);
  //2.监听Stream
  StreamSubscription<int> subscription =
      stream.listen(_onData, onDone: _onDone);
  //3.取消订阅,我们先注释掉
  //_delayCancel(subscription, milliseconds: 500);
}

//处理Stream提交的数据
void _onData(int event) => print("$event");

//Stream所有事件提交完成的回调
void _onDone() =>  print("onDone");

//延迟取消
void _delayCancel(StreamSubscription<int> subscription, {int milliseconds}) {
  Future.delayed(Duration(milliseconds: milliseconds))
      .then((value) => subscription.cancel());
}
//main()执行结果
0
1
2
3
4
onDone

//如果执行了_delayCancel
0
1

创建Stream

创建方式有多种,列举有代表性的方式

使用异步生成器(async*) 

上面的例子已经提到过

使用Future序列

stream2_create_stream_by_future.dart

mport 'dart:async';

//创建一个Future,延迟毫秒数,返回整数i
Future<int> createFuture(int delayMilliseconds, int i) {
  return Future.delayed(Duration(milliseconds: delayMilliseconds))
      .then((value) => i);
}

Stream<T> streamFromFutures<T>(Iterable<Future<T>> futures) async* {
  for (var future in futures) {
    //请求future 并返回future的结果给Stream
    var result = await future;
    yield result;
  }
}

main() {
  List<int> values = [0, 1, 2, 3, 4];
  List<Future<int>> futures =
      values.map((i) => createFuture(i * 200, i)).toList();
  Stream<int> stream = streamFromFutures(futures);
  stream.listen(_onData, onDone: _onDone);
}

void _onData(int event) => print("$event");

void _onDone() => print("onDone");

执行结果

//main()执行结果
0
1
2
3
4
onDone

使用StreamController

实际使用过程中,使用 async*来创建Stream的场景很少。async函数的数据源往往是固定的。很多情况下,我们需要处理多个数据源,又或者数据只是在需要发送的时机发送,此时可以使用StreamController。 stream3_create_stream_by_streamcontroller.dart

//可以看到,只要拥有了controller实例,就可以发送event了,相对来说是灵活的。
//StreamController的细节很多,我们这里不介绍它的使用。
main() {
  var controller = StreamController<int>();
  controller.stream.listen(_onData, onDone: _onDone);
  controller.add(1);
  controller.add(2);
  controller.add(3);
  controller.close();
}

void _onData(int event) => print("$event");

void _onDone() => print("onDone");

接收Stream事件

await 处理事件

stream4_receving_stream_by_await_for.dart

import 'dart:async';
//生成一个Stream<int>
Stream<int> countStream(int to) async* {
  for (int i = 1; i < to; i++) yield i;
}

//求和(迭代处理Stream事件),因为Stream是异步的,所以返回一个Future<int>
Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (var value in stream) {
    print("$value");
    sum += value;
  }
  return sum;
}

main() async {
  //使用await,sumStream执行完,才会执行接下来的print
  var sum = await sumStream(countStream(5));
  print("sum:$sum");
}

输出结果

0
1
2
3
4
sum:10

listen 监听事件

上面的例子已经介绍了。

两种类型的Stream

通过上面对流的使用,现在来看这两种流。Single subscription Streams(单订阅者流) 或者 Broadcast Stream(广播流)

Single subscription Streams

  • 事件序列构成了某个整体。事件必须按照正确的顺序送达,且不能有丢失。比如说文件读取。
  • 流中的事件只能被接收一次,错过了某次事件,则剩余事件毫无意义。当开始监听时,形象点来说,数据是一块一块的形式提供出来。

截屏2020-05-12上午10.41.25.png

Broadcast Stream

  • 相对于单订阅者流,这种Stream,事件不需要是严格序列。可以容忍极少的丢失。随机性的,不定时性的。
  • 相对于单订阅者流,这种Stream,订阅者可以有多个,同时响应事件。

截屏2020-05-12上午10.42.01.png 代码示例 stream5_two_kind_stream.dart

import 'dart:async';

main() {
  broadcastStreamsTest();
  //singleSubscriptionStreamsTest();
}

void broadcastStreamsTest() {
  //此时返回的是 _AsyncBroadcastStreamController,广播流
  var _streamController = StreamController<int>.broadcast();
  _streamController.stream.listen((event) => print("$event"));
  _streamController.stream.listen((event) => print("$event"));
  _streamController.sink.add(100);
  _streamController.close();
}

void singleSubscriptionStreamsTest() {
  //此时返回的是 _AsyncStreamController,单订阅者流
  var _streamController = StreamController<int>();
  _streamController.stream.listen((event) => print("$event"));
  _streamController.stream.listen((event) => print("$event"));
  _streamController.sink.add(100);
  _streamController.close();
}

执行singleSubscriptionStreamsTest(),报错了。提醒我们流已经被监听了。

单订阅者流,只能有一个订阅者

Unhandled exception:
Bad state: Stream has already been listened to.
#0      _StreamController._subscribe (dart:async/stream_controller.dart:683:7)
#1      _ControllerStream._createSubscription (dart:async/stream_controller.dart:833:19)
#2      _StreamImpl.listen (dart:async/stream_impl.dart:475:9)
#3      singleSubscriptionStreamsTest (package:flutter_sample/stream/stream5_two_kind_stream.dart:21:28)
#4      main (package:flutter_sample/stream/stream5_two_kind_stream.dart:5:3)

Stream操作符

前面提到 Stream就像是一个异步迭代器。 相仿于同步的迭代器(list?map?),我们想要在迭代器中完成一些操作。比如返回最后一个值?返回>0的值?求和?又或者整数流变成字符串流? 我们可以用同步的思想去理解这些异步的操作。

Stream->Future

如果只需要一个结果,则返回一个Future. 大部分操作符,在 Iterable 类中都有类似的。

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();

我们挑几个写写Demo,再次感受下stream6_stream_to_future.dart

import 'dart:async';

//构建Stream
Stream<int> countStream(int to) async* {
  for (int i = 0; i < to; i++) yield i;
}

//构建List
List<int> countList(int to) {
  List<int> list = List();
  for (int i = 0; i < to; i++) list.add(i);
  return list;
}

main() {
  reduce();
  forEach();
}

//我们发现 区别就在于 Stream/Future加了await去执行。
//用同步的方式去写异步。剩下部分,Stream和List是一样的。
void reduce() async {
  Stream<int> stream = countStream(5);
  Future<int> future = stream.reduce((previous, element) => previous + element);
  var futureSum = await future;

  List<int> list = countList(5);
  var listSum = list.reduce((value, element) => value + element);
  print("reduce futureSum:$futureSum  listSum:$listSum");
}

void forEach() async {
  Stream<int> stream = countStream(5);
  StringBuffer streamSb = new StringBuffer();
  await stream.forEach((value) => streamSb.write("$value,"));

  List<int> list = countList(5);
  StringBuffer listSb = new StringBuffer();
  list.forEach((value) => listSb.write("$value,"));
  print("stream forEach:$streamSb  list forEach:$listSb");
}

结果打印

reduce futureSum:10  listSum:10
stream forEach:0,1,2,3,4,  list forEach:0,1,2,3,4,

Stream-> Stream

如果需要得到的仍然是一个序列,大概感受下

Stream<R> cast<R>();
Stream<S> expand<S>(Iterable<S> Function(T element) convert);
Stream<S> map<S>(S Function(T event) convert);
Stream<T> skip(int count);
Stream<T> skipWhile(bool Function(T element) test);
Stream<T> take(int count);
Stream<T> takeWhile(bool Function(T element) test);
Stream<T> where(bool Function(T event) test);

我们写个Demo,再次感受下 stream6_stream_to_stream.dart

void map() async {
  Stream<int> stream = countStream(5);
  Stream<String> strStream = stream.map((event) => "index($event)");
  StringBuffer streamSb = new StringBuffer();
  await strStream.forEach((value) => streamSb.write("$value,"));

  List<int> list = countList(5);
  StringBuffer listSb = new StringBuffer();
  var strList = list.map((e) => "index($e)").toList();
  strList.forEach((element) => listSb.write("$element,"));
  print("map stream:$streamSb  list:$listSb");
}

结果打印

map 
stream:index(0),index(1),index(2),index(3),index(4),  
list:index(0),index(1),index(2),index(3),index(4),

截屏2020-05-12上午10.42.30.png

Stream与Iterable

Stream 理解成 Asynchronus Iterable. 除了相似点之外,还有Stream特别的,比如说一下等等。

Future pipe(StreamConsumer<T> streamConsumer);
Future<E> drain<E>([E futureValue]);
Stream<T> handleError(Function onError, {bool test(error)});
Stream<T> timeout(Duration timeLimit,
    {void Function(EventSink<T> sink) onTimeout});
Stream<S> transform<S>(StreamTransformer<T, S> streamTransformer);

StreamTransformer

transform() 函数

transform()函数是更通用版的“map”。通常map来看,值的转换是一一对应的。然后,往往我们需要获取多个输入值,才能产生一个输出值。此时StreamTransformer,就可以完成这个操作。

我们写一个例子. 场景 输入一个字母空格序列:O,n,e, ,W,o,r,l,d, ,O,n,e, ,D,r,e,a,m (注意 e和W之间有一个空格) 输出一个单词序列:One World One  Dream  我们用StreamTransformer来实现 letterStream转换成 wordStream 截屏2020-05-12上午10.42.49.png stream8_stream_transformer.dart

//一个字母和空格流,空格分割的字母序列都是一个单词
Stream<String> buildLetterStream() async* {
  String s = "One World One Dream";
  for (int i = 0; i < s.length; i++) {
    String letter = s.substring(i, i + 1);
    yield letter;
  }
}

main() async {
  
  Stream<String> letterStream = buildLetterStream();
  var wordSplitter = WordSplitter();
  //字母组成单词。字母序列-->单词序列
  Stream<String> wordStream = letterStream.transform(wordSplitter.transformer());
  await wordStream.forEach((word) => print(word));
}

class WordSplitter {
  StreamTransformer<String, String> _transformer;
  //存储字母
  StringBuffer _wordBuilder = new StringBuffer();

  StreamTransformer transformer() {
    _transformer = StreamTransformer<String, String>.fromHandlers(
        handleData: _handleData, handleDone: _sinkWord);
    return _transformer;
  }

  //One World One Dream 对输入的letter进行处理,并决定是否需要重新发送给新的Stream
  void _handleData(String letter, EventSink<String> sink) {
    //读到空格,则把累计的字母构成单词输出。
    if (letter == " ") {
      _sinkWord(sink);
    } else {
      //否则存储字母
      _wordBuilder.write(letter);
    }
  }

  //读完时,把剩余字母构成单词输出
  void _sinkWord(EventSink<String> sink) {
    if (_wordBuilder.isNotEmpty) {
      sink.add(_wordBuilder.toString());
      _wordBuilder.clear();
    }
  }
}

输出结果

One
World
One
Dream

总结

到这里,我们介绍了Stream的基本用法。

  • Stream是Dart异步编程的基石
  • async*,StreamController等都可以创建Stream
  • await 处理Stream事件,listen监听Stream事件
  • Stream有两种
    • Single Subscription Stream 严格事件序列,只能有一个订阅者
    • Broadcast Stream 非严格事件序列,可以有多个订阅者
  • Stream转换
    • Iterable操作符(map,forearch,reduce等等)
    • 更强大的StreamTransformer