牢骚
故乡容不下灵魂,他乡容不下肉身!若能一世安稳,谁愿颠沛流离。
正文
在 Dart 中,没有多线程的概念,所谓的异步操作全部都是在一个线程里面执行的, 并且不会造成卡顿的原因就是事件循环(Event Loop),
如下图所示,在程序的运行过程中,会有两个事件
补充上图:Micortask Queue 为空 才会执行 EventQueue ,EventQueue 为空时程序结束,实际上,事件循环从启动的之后会一直执行。
在程序执行过程中,如果有异步操作,这个操作就会添加到队列中,当发现队列不为空时,就会然后不断的从队列中取出事件在执行
Microtask Queue
一个顶级的队列,只要这个队列里面不是空的,就一定会执行该队列中的任务,
scheduleMicrotask(() {
print("Hello Flutter");
});
Future.microtask() //内部调用的也是上面的函数
但是需要注意的是,一般的实战中,我们不会手动给这个队列里面添加事件,该队列一般都是由 Dart 自己来处理的。
Event Queue
普通的事件队列,比 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()
Future
Flutter 相当于是一个盒子,内部的代码最终会交给 EventQueue 来执行,Future 的状态大致可分为三种,如下:
Future(() {
print('未完成状态');
})
.then((value) => print('已完成状态'))
.catchError((value) => print('异常状态'));
我们程序中的大部分异步操作都是围绕着这三种状态进行的。
Future 常用的函数
-
Future.error()
Future(() { return Future.error(Exception()); }).then((value) => print('已完成状态')).catchError((value) => print('异常状态'));
创建一个以异常结束的 Future,上面代码最终会执行到 catchError 中。
-
Future.whenComplete()
类似于 try catch 后面的 finnaly,无论成功和失败,最终都会执行到这里
-
Future.them 链式调用
//在 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()
如果要等到多个异步任务都结束之后再进行一些操作,可以使用 Future.wait
Future.wait([getNetData(), getNetData(), getNetData()]).then((value) { value.forEach((element) { print(element); }); });
-
Future.delayed()
延时指定的时间后在执行
Future.delayed(Duration(seconds: 3), () { return "3秒后的信息"; }).then((value) { print(value); });
-
async,await
async:用来表示函数是异步的,定义的函数会返回一个 Future 对象,可以使用 then 添加回调函数
await :后面是一个 Future,表示等待改异步任务的完成,异步完成之后才会继续往下走,await 必须出现在 async 的内部
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; }
FutureBuilder
监听一个 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()
});
}
}
构造
-
future :接受一个 future,当 future 的值发生变化之后,就会自动调用下面的 build 函数,
-
initialData:初始值,在 future 没完成的时候可以暂时使用该值,该值会放在 AsyncSnapshot 的 data 中,在 future 未完成的时候可以使用该值。在 future 出错的时候,该值会被 AsyncSnapshot 从 data 中删掉
-
builder:返回一个 Widget
AsyncSnapshot 用来保存 future 最近的状态,这个状态只有两个情况,一种是有数据 data,一种是错误状态 error。 AsyncSnapshot 中还有 ConnectionState 状态,分别表示的是 none :没有传递 future,waiting:等待中,active:TODO ,done :表示已经完成
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);
}
}
源码其实很简单,仔细看一下就知道整个流程了
StreamBuilder
介绍
上面的 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 中的粘性事件 和 非粘性事件,每种都有它的作用另外,
-
map
使用 map 还可以将事件进行改变或者修改,如下:
controller.stream.map((event) => "Map: $event").listen((event) { print('$event'); });
-
where
除过 map 方法之外,还有一个比较有用的方法是 where ,可以对事件进行过滤
controller.stream .where((event) => event > 13) .map((event) => "Map: $event") .listen((event) { print('$event'); });
-
distinct
去重,如何和上次的数据相同,则不会收到该事件‘
-
语法糖 async*
类似于 async await 的使用,如下
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 还是挺实用的,这次我们用 StreamBuilder 来做一个小游戏,先看效果:
从上面的动画来看,可以将其分为三个部分,第一个部分则是底部的键盘,第二部分就是向下落的题目,第三部分则是得分的结果。
下面我们来实现一下这个小游戏
-
KeyPad
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,后面你就知道了。 -
Puzzle
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 透明度 -
分数计算和拼接上面两个 widget
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站王叔不秃
如果本文有帮助到你的地方,不胜荣幸,如有文章中有错误和疑问,欢迎大家提出!