Flutter和Dart之间的关系
Flutter是基于Dart语言的移动UI框架。
Dart语言
Dart是谷歌开发的计算机编程语言。它被用于web、服务器、移动应用和物联网等领域的开发。
Flutter框架
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。
异步示例 - Future
1.使用Future来实现异步
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() {
print('开始data=$_data');
Future(() {
_data = '网络数据';
print('结束data=$_data');
});
}
结果:
flutter: 开始 data=1
flutter: 其他事情
flutter: 结束 data=网络数据
2.Flutter是单线程
import 'dart:io';
String _data = '1';
void main() {
getData();
sleep(Duration(seconds: 2)); //睡几秒
print('其他事情');
}
void getData() {
print('开始 data=$_data');
Future(() {
_data = '网络数据';
print('结束 data=$_data');
});
}
增加了sleep方法调用,无论设置几秒钟,执行顺序不变。 打印结果:
flutter: 开始 data=1
flutter: 其他事情
flutter: 结束 data=网络数据
打印结果不会变,只是在‘开始’和‘其他事情’这两个打印之间增加时长,这点可以说明Flutter是单线程的开发模式。
3.async和await
规则:
- await所在的函数必须必须被async修饰
- await必须修饰异步方法
String _data = '1';
void main() {
getData();
print('其他事情');
}
//async修饰
void getData() async {
print('开始 data=$_data');
//await修饰异步方法Future
await Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
});
print('结束 data=$_data');
}
打印结果:
flutter: 开始 data=1
flutter: 其他事情
flutter: 结束 data=网络数据
注意:await的性质和栅栏函数相似(可能底层实现就是栅栏函数),后面代码的执行,要等到await修饰的异步函数执行完才能继续执行。可以在‘结束’print后面多写几份print,看看打印结果。
4.then
如果想要Future后面的部分代码想在Future执行完再执行,而且不影响其他代码,可以是用then来实现
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() async {
print('开始 data=$_data');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
});
future.then((value) => print('结束 data=$_data'));
print('干点其他事情!');
}
打印结果
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 结束 data=网络数据
通过打印可以看到‘干点其他事情’的print先执行,而‘结束’print是等待Future后才执行的。
5.then中的value参数
通过future.dart文件中的注释了解到:then是一个回调函数,在future完成时进行的回调。value参数是future中的返回值
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() async {
print('开始 data=$_data');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
return _data;
});
future.then((value) => print(value));
print('干点其他事情!');
}
打印结果:
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 网络数据 //then中打印
6.捕获异常
Future中抛出异常,并且捕获这个异常
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() async {
print('开始 data=$_data');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
//抛出异常
throw Exception('网络异常');
});
future.catchError((e) {
//捕获异常
print('捕获了异常' + e.toString());
});
future.then((value) => print(value));
print('干点其他事情!');
}
打印报错
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 捕获了异常Exception: 网络异常
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Exception: 网络异常
#0 getData.<anonymous closure> (package:futruedemo/main.dart:14:5)
#1 new Future.<anonymous closure> (dart:async/future.dart:176:37)
#2 _rootRun (dart:async/zone.dart:1180:38)
#3 _CustomZone.run (dart:async/zone.dart:1077:19)
......
异常捕获到了,但是下面有报错。原因是既然捕获了异常,后面的代码就不要执行then操作了。这个地方联想我们平时的开发,其实很好理解
if (有异常) {
捕获异常
} else {
执行没有异常的逻辑
}
平时我们都是这么写判断逻辑的,也就是说只能走其中的一个逻辑。所以前面的报错代码的原因,应该和这个类似。
解决方法:then中有可选回调方法{Function onError},通过这个回调函数来实现捕获异常的逻辑
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() async {
print('开始 data=$_data');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
//抛出异常
throw Exception('网络异常');
});
//注释future捕获异常代码
// future.catchError((e) {
// //捕获异常
// print('捕获了异常' + e.toString());
// });
//增加onError回调
future.then((value) {
print('then来了');
print(value);
}, onError: (e) {
print('捕获了异常' + e.toString());
});
print('干点其他事情!');
}
打印结果:
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 捕获了异常Exception: 网络异常
可以看到then中的print没有执行。
链式编程应用
1.应用示例
future的catchError回调作用?其实Dart语法提供了链式编程的方式,本人认为future的catchError就是为了链式编程而提供的方法。 链式编程实现:
String _data = '1';
void main() {
getData();
print('其他事情');
}
void getData() async {
print('开始 data=$_data');
Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
//抛出异常
throw Exception('网络异常');
}).catchError((e) {
//捕获异常
print('捕获了异常' + e.toString());
}).then((value) {
print('then来了');
print(value);
});
print('干点其他事情!');
}
打印结果:
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 捕获了异常Exception: 网络异常
flutter: then来了
flutter: null
2.注意链式变成的顺序
应该有人注意到then方法的print打印了,这个是因为链式编程是有顺序要求的。修改一下then和catchError的顺序就好了。
...
Future(() {
for (int i = 0; i < 1000000000; i++) {
_data = '网络数据';
}
//抛出异常
throw Exception('网络异常');
}).then((value) {
print('then来了');
print(value);
}).catchError((e) {
//捕获异常
print('捕获了异常' + e.toString());
});
...
flutter: 开始 data=1
flutter: 干点其他事情!
flutter: 其他事情
flutter: 捕获了异常Exception: 网络异常
注意:then方法会执行,只是then中的回调不会执行
3..其他常用的Future可以链式的方法
- whenComplete:完成后的回调
- timeout:超时回调
这两个在平时开发中使用的频率也很高,使用起来也比较容易,所以就简单记录一下了。
多个Future执行顺序
1.串行队列
多个Future情况,执行顺序类似于串行队列,下面的例子的打印结果可以证明:
void main() {
testFuture();
print('其他事情');
}
//多个future
void testFuture() {
Future(() {
return '任务1';
}).then((value) => print('$value结束'));
Future(() {
return '任务2';
}).then((value) => print('$value结束'));
Future(() {
return '任务3';
}).then((value) => print('$value结束'));
Future(() {
return '任务4';
}).then((value) => print('$value结束'));
print('任务添加完毕');
}
打印结果:
flutter: 任务添加完毕
flutter: 其他事情
flutter: 任务1结束
flutter: 任务2结束
flutter: 任务3结束
flutter: 任务4结束
2.增加sleep方法
在‘任务1’中增加sleep方法,时长1秒:
void testFuture() {
Future(() {
sleep(Duration(seconds: 1));
return '任务1';
}).then((value) => print('$value结束'));
Future(() {
return '任务2';
}).then((value) => print('$value结束'));
Future(() {
return '任务3';
}).then((value) => print('$value结束'));
Future(() {
return '任务4';
}).then((value) => print('$value结束'));
print('任务添加完毕');
}
打印结果
flutter: 任务添加完毕
flutter: 其他事情
flutter: 任务1结束
flutter: 任务2结束
flutter: 任务3结束
flutter: 任务4结束
查看结果可知,增加sleep不会影响异步函数的执行顺序,再次证明异步队列是串行模式,进而可以再次证明,Flutter是单线程的。
3.调整任务执行顺序
任务是按照代码顺序执行的,如果想让‘任务1’执行后,执行‘任务4’。最简单的方式时调整代码顺序。但是这样不好,不好的点在于如果代码又臭又长,你很难把其中的逻辑看透,会埋下很多的隐患。
比较好的解决方式,就是利用链式编程的方式,把要执行的‘任务’放到前一个‘任务’的then中处理:例如‘任务4’在‘任务1’之后执行
void testFuture() {
Future(() {
sleep(Duration(seconds: 1));
return '任务1';
}).then((value) {
print('$value结束');
//执行任务4
return '任务4';
}).then((value) => print('$value结束'));
Future(() {
return '任务2';
}).then((value) => print('$value结束'));
Future(() {
return '任务3';
}).then((value) => print('$value结束'));
print('任务添加完毕');
}
打印结果:
flutter: 任务添加完毕
flutter: 其他事情
flutter: 任务1结束
flutter: 任务4结束
flutter: 任务2结束
flutter: 任务3结束
这样只要处理你要处理的业务模块就好,不用考虑其他异步任务的业务逻辑。
4.任务分组
两个或者多个任务执行后,执行后续操作代码写法:
void main() {
testGroupFuture();
print('其他事情');
}
//分组
void testGroupFuture() {
Future.wait([
Future(() {
return '任务1';
}),
Future(() {
return '任务2';
})
]).then((value) => print(value[0] + value[1]));
}
打印结果:
flutter: 其他事情
flutter: 任务1任务2
使用Future.wait来实现任务分组,then中的value返回的也是一个数组,用下标获取每个任务的返回值。
之前证明了Flutter是单线程执行的,这种方式和用链式的方式(Future.then.then)执行效率上没有区别。在代码的可读性上来说会好一点。
Dart事件循环队列
Dart中有两种队列:事件队列和微任务队列
- 事件队列(event queue):比如我们上面用示例中用到的Futur异步任务加入的队列。
- 微任务队列(microtask queue):它的优先级高于事件队列,只有微任务队列中有微任务存在,就会一直霸占着资源,事件队列只能等待直到微任务队列中所有微任务执行结束。
事件队列
我们平时使用比较多的Future,将异步任务放到优先级比较低的事件队列中,执行逻辑比较好理解:
- 声明一个Future时,Dart将异步任务函数体放到事件队列(event queue)中,然后立即返回,后续代码继续同步执行。
- 后继代码同步执行完毕,按照事件队列的加入顺序,依次取出执行代码块。在代码块中继续按照同步执行。
微任务队列
代码示例
void testFuture4() {
Future x = Future(() => print('a'));
Future(() => print('b'));
scheduleMicrotask(() => print('c'));
x.then((value) => print('d'));
print('e');
}
代码中scheduleMicrotask是调度微任务队列,并把print('c')放入到微任务队列中,看看结果打印:
flutter: e
flutter: c
flutter: a
flutter: d
flutter: b
代码解析
- print('a')放到事件队列中,并用变量x接收Future()返回值;
- print('b')放到事件队列中;
- print('c')放到微任务队列中,该代码块执行优先于1、2的代码块;
- x.then中的print('d')依赖第1步执行之后,才能执行;
- print('e')是同步代码,优先执行,所以先打印‘e’;
- 同步代码执行完毕,开始查看微任务队列,打印‘c’;
- 微任务队列为空,开始执行事件队列,按照加入的顺序,先执行打印‘a’,然后执行then方法,打印‘d’;
- 最后打印‘b’;
then的再次分析
紧接着Future中回调结束后,会执行then的回调方法。那么then的回调是在事件队列中,还是微任务队列中呢?
猜测1:事件队列中
我们还用上面例子中的代码,这次把scheduleMicrotask和x.then代码互换一下位置:(为了下面说明方便,这里把打印字母换成打印数字,注意,3和4对应的代码c和d,互换了顺序)
void testFuture4() {
Future x = Future(() => print('1'));
Future(() => print('2'));
//调整代码部分
x.then((value) => print('4'));
scheduleMicrotask(() => print('3'));
//调整代码部分
print('5');
}
执行结果:
flutter: 5
flutter: 3
flutter: 1
flutter: 4
flutter: 2
如果是事件队列中,那么执行顺序应该是5-3-1-2-4,但显然和执行打印结果不一样。所以说在事件队列中这个不成立。
猜测2:微任务队列中
本人开始认为是在微任务队列中,then方法的定义中有段注释:
上面例子代码:同步代码打印5,微任务打印3,执行事件队列中的打印1之后将打印4放到微任务中,然后执行微任务中的打印4,最后打印2。很完美的解释通了。
官方的注释+示例代码,这样可以证明then是将回调任务放在微任务队列中。
其实也不在微任务队列中
但当我遇到下面的代码时,整个人就凌乱了:
void main() {
Future x = Future(() {
print('1');
scheduleMicrotask(() => print('2'));
return '1结束';
});
x.then((value) => print(value + '3'));
print('4');
}
代码分析:(注意:这是不正确的)
- 打印4,同步执行
- 开始异步执行Future,打印1,将print('2')加入到微任务队列中,Future执行完毕。
- 此时微任务队列中不为空,执行打印2
- 最后执行then中的回调‘1结束3’
- 打印顺序:4-1-2-1结束3
看打印结果:
可能有人会问注释中怎么解释,这个也是我之前一直任务在微任务队列中的主要原因,但无法解释上述代码,所以我个人偏向于注释作者偷懒的概率较大。
此处是以目前个人对Dart学习而总结出的结论,因为网上也没有找到具体官方解释,所以我只能用我当前看到的现象进行总结。欢迎看到文章此处的同学一起讨论一下。