Stream是Dart用来处理异步的API,和同样用来处理异步的Future不同的是,Stream可以异步的返回多个结果,而Future只能返回一个
Stream periodicStream = Stream.periodic(Duration(seconds: 2), (num) {
return num;
});
periodic构造方法主要有两个参数,第一个参数类型为Duration(时间间隔),第二个参数类型为Function,Function每隔一个Duration(时间间隔)会被调用一次,参数num为事件调用的次数,从0开始依次递增。
翻阅源码 Stream.periodic是使用Timer.periodic加_SyncStreamController实现的
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?
yield为一个用async *修饰返回值为Stream的函数返回一个值,它就像return,不过他不会结束函数
Stream asynchronousNaturalsTo(n) async* {
int k = 0;
while (k < n) yield k++;
}
这里涉及到了Dart的生成器函数概念,在这里你只需要简单理解yield的作用就可以了
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的常用方法
PS:以下Stream常用方法的展示都是用下面代码创建的流
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功能的展示。
这两个技术在当下如何选择,我之前在公众号上的回复是:如果你已经处于一个比较满意的公司,并考虑长期发展,公司并未使用这两个技术,你可以专心钻研公司当下使用的,或者未来将要使用的,这些才能助你在公司步步高升。
在这里,你必须得安装好你的开发者环境,并且运行你的第一个flutter程序了。如果你还不知道怎么开始,请参考Flutter中文网安装教程或者Flutter官网安装教程进行安装环境。
local_cache_sync是一个非常简单易用的Flutter本地储存库,适用于在本地储存一列轻量数据(例如用户保存在本地的设备信息,或者缓存一系列用户信息),local_cache_sync的所有方法,包括保存与读取,都是同步的,而不是异步的。
众所周知,官方提供了好几个办法来让我们在开发 Flutter app 的过程中可以使用查看 fps等性能数据,如 devtools ,具体见文档 Debugging Flutter apps 、 Flutter performance profiling等。
对于这种样式,我们可以选择自定义Dialog,具体的样式可以根据自己的需要进行修改。 例如,下面是我的实现,由于文本是一个列表,所以我需要新建一个实体类,如下所示。
本文对比的是 UIWebView、WKWebView、flutter_webview_plugin(在 iOS 中使用的是 WKWebView)的加载速度,内存使用情况。测试网页打开的速度,只需要获取 WebView 在开始加载网页和网页加载完成时的时间戳
早在一年前想学习下flutter,但当时对于它布局中地狱式的嵌套有点望而生畏,心想为什么嵌套这么复杂,就没有xml布局方式吗,用jsx方式也行啊,为什么要用dart而不用javascript,走开,劳资不学了。
在优秀的用户体验中,app 的加载速度扮演着重要角色。Flutter web app 的初次加载时间可以通过最小化 JS 包体积来提高。Dart 编译器自带 tree shaking 和延迟加载特性,这两者都可以最大程度地减少 JS 包体积。这篇文章介绍了这两个特性的工作原理,以及如何应用。
Dart语言与其他语言究竟有什么不同呢?在已有的编程语言经验的基础上,我们该如何快速上手呢?本篇文章从编程语言中最重要的组成部分,也就是基础语法与类型变量出发,一起来学习Dart吧
Flutter 与原生之间的通信依赖灵活的消息传递方式:1,Flutter 部分通过平台通道将消息发送到其应用程序的所在的宿主环境(原生应用)。2,宿主环境通过监听平台通道,接收消息。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!