在 Dart 中,没有多线程的概念,所谓的异步操作全部都是在一个线程里面执行的, 并且不会造成卡顿的原因就是事件循环(Event Loop),
如下图所示,在程序的运行过程中,会有两个事件
补充上图:Micortask Queue 为空 才会执行 EventQueue ,EventQueue 为空时程序结束,实际上,事件循环从启动的之后会一直执行。
在程序执行过程中,如果有异步操作,这个操作就会添加到队列中,当发现队列不为空时,就会然后不断的从队列中取出事件在执行
一个顶级的队列,只要这个队列里面不是空的,就一定会执行该队列中的任务,
scheduleMicrotask(() {
print("Hello Flutter");
});
复制代码
Future.microtask() //内部调用的也是上面的函数
复制代码
但是需要注意的是,一般的实战中,我们不会手动给这个队列里面添加事件,该队列一般都是由 Dart 自己来处理的。
普通的事件队列,比 Microtask Queue
低了一个等级,在 Microtask Queue
中没有任务的时候才会执行该队列中的任务
需要异步操作的代码都会放在 EventQueue 中执行,例如:
Future((){
print('这里会被放在 EventQueu 中执行');
})
Future.delayed(Duration(seconds: 1), () {
print('这里会被放在 EventQueu 中执行');
});
复制代码
Future.sync(() => print('Hello'));
Future.value(() => print('world'));
xxx.then()
复制代码
Flutter 相当于是一个盒子,内部的代码最终会交给 EventQueue 来执行,Future 的状态大致可分为三种,如下:
Future(() {
print('未完成状态');
})
.then((value) => print('已完成状态'))
.catchError((value) => print('异常状态'));
复制代码
我们程序中的大部分异步操作都是围绕着这三种状态进行的。
Future(() {
return Future.error(Exception());
}).then((value) => print('已完成状态')).catchError((value) => print('异常状态'));
创建一个以异常结束的 Future,上面代码最终会执行到 catchError 中。
//在 them 中可以接继续返回值,该值会在下一个链式的 then 调用中拿到返回的结果
getNetData().then((value) {
//支持成功到此处
print(value);
return "data1";
}).then((value) {
print(value);
return "data2";
}).then((value) {
print(value);
}).catchError((error) {
//执行失败到此处
print(error);
}).whenComplete(() => print("完成"));
Future.wait([getNetData(), getNetData(), getNetData()]).then((value) {
value.forEach((element) {
print(element);
});
});
Future.delayed(Duration(seconds: 3), () {
return "3秒后的信息";
}).then((value) {
print(value);
});
void main() {
print("start ----------->");
getNetData().then((value) => print(value));
print("end -------------->");
}
// async 会将返回的结果封装为 Future 类型
getNetData() async {
var result1 = await Future.delayed(Duration(seconds: 3), () {
return "网络数据1";
});
var result2 = await Future.delayed(Duration(seconds: 3), () {
return "网络数据2";
});
return result1 + "-----" + result2;
}
监听一个 Future,以 Future 的状态来进行 setState
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
return DemoWidget()
});
}
}
复制代码
FutureBuilder 的作用就是根据 future 的状态来判断当前页面需要显示哪些 widiget,例如 future 在等待的时候显示加载框,完成之后显示内容等。
需要注意的一点是当状态为 done 是,可能会有两种情况,一种是 future 成功了,另一种是 future 失败了,内部有异常,这个时候就不应该获取 data,而是判断 snap.hasData 来进行判断。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
if (snap.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snap.connectionState == ConnectionState.done) {
if (snap.hasData) {
return DemoWidget();
} else {
return Text(snap.error);
}
}
throw "should not happen";
});
}
}
//在精简一些
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.value(10),
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
//如果有数据
if (snap.hasData) {
return DemoWidget();
}
//如果发生错误
if (snap.hasError) {
return Text(snap.error);
}
// 等待中,显示加载框
return CircularProgressIndicator();
});
}
}
复制代码
/// State for [FutureBuilder].
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
//....
@override
void initState() {
//如果没有初始值,则先设置Wie none 状态,如果有,则传入初始值
_snapshot = widget.initialData == null
? AsyncSnapshot<T>.nothing()
: AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
_subscribe();
}
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
//....
_subscribe();
}
}
void _subscribe() {
if (widget.future != null) {
final Object callbackIdentity = Object();
_activeCallbackIdentity = callbackIdentity;
widget.future!.then<void>((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
//任务执行完成,将数据传给 _snapshot ,并刷新
setState(() {
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error, StackTrace stackTrace) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
//出现错误
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
});
}
assert(() {
//判断调试状态
if(FutureBuilder.debugRethrowError) {
Future<Object>.error(error, stackTrace);
}
return true;
}());
});
//没有完成则是等待状态
_snapshot = _snapshot.inState(ConnectionState.waiting);
}
}
复制代码
源码其实很简单,仔细看一下就知道整个流程了
上面的 FutureBuilder
只能给我们一个值,而 StreamBuildder
可以给我们一连串的值 ,例如:
final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);
stream.listen((event) {
print(event);
});
复制代码
class _MyHomePageState extends State<MyHomePage> {
final stream = Stream.periodic(Duration(seconds: 1), (count) => count++);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
color: Colors.white,
child: DefaultTextStyle(
style: TextStyle(fontSize: 30, color: Colors.black),
child: StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snap) {
switch (snap.connectionState) {
case ConnectionState.none:
return Text("NONE:没有数据流");
break;
case ConnectionState.waiting:
return Text("WAITING:等待数据流");
break;
case ConnectionState.active:
if (snap.hasData) {
return Text(snap.data.toString());
}
if (snap.hasError) {
return Text(snap.error.toString());
}
break;
case ConnectionState.done:
return Text("DONE:数据流已关闭");
break;
}
return CircularProgressIndicator();
}),
),
);
}
}
复制代码
其实和 FutureBuilder
差不多,只不过多了一个 active 状态,这个状态在上面没有说是因为用不到**,在这里的意思指的就是数据流是否为活跃的**,如果是活跃的,则就可以获取他的值了
Stream.periodic
的方式来创建一个数据流,如上面的示例所示File("").openRead().listen((event) {
})
将读取的文件信息以数据流的方式转给我们
StreamController
final controller = StreamController();
controller.stream.listen((event) {
print('$event');
});
controller.sink.add(12);
controller.sink.add(13);
controller.sink.add(14);
controller.sink.add(15);
controller.sink.addError("Error");
//关闭后则不能进行任何添加操作
controller.close();
StreamController
这种方式就比 periodic
创建的方式好多了,可以自由的往数据流中添加数据。
需要注意的是使用完成之后要进行关闭操作,否则就会泄漏资源 并且 flutter 会一直警告,
上面的这种方式只能有一个监听,如果添加多个监听则就会保存,那么如何添加多个监听呢,可以使用广播的方式,如下:
final controller = StreamController.broadcast();
只需要在创建的时候使用 broadcast()
即可
这两者有一个非常明显的区别就是缓存,默认的创建方式是有缓存的,而 broadcast 则没有缓存,如下:
final controller = StreamController();
controller.sink.add(12);
controller.sink.add(13);
controller.sink.add(14);
controller.sink.add(15);
controller.sink.addError("Error");
controller.stream.listen((event) {
print('$event');
}, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE'));
//关闭后则不能进行任何添加操作
controller.close();
final controller = StreamController.broadcast();
controller.sink.add(12);
controller.sink.add(13);
controller.sink.add(14);
controller.sink.add(15);
controller.sink.addError("Error");
controller.stream.listen((event) {
print('$event');
}, onError: (error) => print('ERROR:$error'), onDone: () => print('DONE'));
controller.stream.listen((event) {
print('Copy:$event');
});
//关闭后则不能进行任何添加操作
controller.close();
而这种方式不会打印之前的数据。这两种方式就好像 EventBus 中的粘性事件 和 非粘性事件,每种都有它的作用另外,
controller.stream.map((event) => "Map: $event").listen((event) {
print('$event');
});
controller.stream
.where((event) => event > 13)
.map((event) => "Map: $event")
.listen((event) {
print('$event');
});
void main() {
getTime().listen((event) {
print('$event');
});
}
Stream<DateTime> getTime() async* {
while (true) {
Future.delayed(Duration(seconds: 1));
yield DateTime.now();
}
}
StreamBuilder
继承自 StreamBuilderBase
的,核心如下
void _subscribe() {
if (widget.stream != null) {
_subscription = widget.stream!.listen((T data) {
setState(() {
_summary = widget.afterData(_summary, data);
});
}, onError: (Object error, StackTrace stackTrace) {
setState(() {
_summary = widget.afterError(_summary, error, stackTrace);
});
}, onDone: () {
setState(() {
_summary = widget.afterDone(_summary);
});
});
_summary = widget.afterConnected(_summary);
}
}
复制代码
在日常开发中,StreamBuilder 还是挺实用的,这次我们用 StreamBuilder 来做一个小游戏,先看效果:
从上面的动画来看,可以将其分为三个部分,第一个部分则是底部的键盘,第二部分就是向下落的题目,第三部分则是得分的结果。
下面我们来实现一下这个小游戏
class KeyPad extends StatelessWidget {
final StreamController inputController;
final StreamController scoreController;
const KeyPad(this.inputController, this.scoreController);
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
childAspectRatio: 2,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(0.0),
children: List.generate(
9,
(index) =>
FlatButton(
shape: RoundedRectangleBorder(),
onPressed: () {
inputController.sink.add(index + 1);
scoreController.add(-2);
},
child: Text("${index + 1}"),
color: Colors.primaries[index],
),
),
);
}
}
KeyPad
接收了两个 Stream,分别是输入和分数, 底部的键盘是一个 GridView,当点击到对应的按钮上时,则发送输入的数字,已经分数 -2,至于为啥减2,后面你就知道了。
class Puzzle extends StatefulWidget {
final Stream inputStream;
final StreamController scoreController;
const Puzzle({Key key, this.inputStream, this.scoreController})
: super(key: key);
@override
_PuzzleState createState() => _PuzzleState();
}
class _PuzzleState extends State<Puzzle> with SingleTickerProviderStateMixin {
int a, b;
double x;
Color color;
AnimationController _controller;
@override
void initState() {
_controller = AnimationController(vsync: this);
reset();
_controller.addStatusListener((status) {
//动画结束
if (status == AnimationStatus.completed) {
reset(-100.0);
widget.scoreController.sink.add(-3);
}
});
widget.inputStream.listen((event) {
if (event == a + b) {
reset(-100.0);
widget.scoreController.sink.add(7);
}
});
super.initState();
}
reset([from = 0.0]) {
a = Random().nextInt(5) + 1;
b = Random().nextInt(5);
x = Random().nextDouble() * 280;
_controller.duration =
Duration(milliseconds: Random().nextInt(5000) + 5000);
color = Colors.primaries[Random().nextInt(Colors.primaries.length)][200];
_controller.forward(from: from);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
top: 700 * _controller.value - 100,
left: x,
child: Container(
decoration: BoxDecoration(
color: color.withOpacity(0.5),
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(24)),
padding: EdgeInsets.all(8),
child: Text("$a + $b ", style: TextStyle(fontSize: 24)),
),
);
});
}
}
Puzzle
也接收了输入和分数的 Stream,并且创建了一个动画,在 initState 中,监听动画和输入事件,动画结束则表示没有答对题,直接重置,并扣分,收到输入事件之后则 计算结果是否真确,然后重置,并且加分
reset
方法中用于生产题目和 x 轴的位置以及动画的执行时间,最后开启动画
build
中其实是很简单的,使用了 AnimatedBuilder 来监听动画,当动画值改变后则会重新 setState(),内部就是一个小按钮,记录了题目,注意背景颜色是 0.5 透明度
class GemsPage extends StatefulWidget {
@override
_GemsPageState createState() => _GemsPageState();
}
class _GemsPageState extends State<GemsPage> {
final _inputController = StreamController.broadcast();
final _scoreController = StreamController.broadcast();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: StreamBuilder(
stream: _scoreController.stream.transform(TallyTransformer()),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("得分: ${snapshot.data}");
}
return Text("得分: 0");
},
),
),
body: Stack(
children: [
...List.generate(
8,
(index) => Puzzle(
inputStream: _inputController.stream,
scoreController: _scoreController),
),
Align(
alignment: Alignment.bottomCenter,
child: KeyPad(_inputController, _scoreController),
)
],
),
);
}
@override
void dispose() {
_scoreController.close();
_inputController.close();
super.dispose();
}
}
class TallyTransformer implements StreamTransformer {
int sum = 0;
StreamController _controller = StreamController();
@override
Stream bind(Stream<dynamic> stream) {
stream.listen((event) {
sum += event;
_controller.add(sum);
});
return _controller.stream;
}
///类型检查
@override
StreamTransformer<RS, RT> cast<RS, RT>() => StreamTransformer.castFrom(this);
}
参考:B站王叔不秃
如果本文有帮助到你的地方,不胜荣幸,如有文章中有错误和疑问,欢迎大家提出!