异步
- 首先
Dart
是单线程的 - 开发中肯定会遇到很多耗时操作的,而单线程肯定不能一直在等待这些耗时操作完成,而造成阻塞
- 那么
Dart
里是如何处理的呢,答:Dart是基于单线程加事件循环来完成耗时操作的处理的
事件循环
-
将需要处理的事件,放到一个事件队列中,不断的从队列中取出对应的事件,并执行。
-
跟咱
iOS
里的Runloop
类似
引用一下官方图,齿轮就是事件循环,不断的从左边取事件执行 -
实际上
Dart
从两个队列执行任务:event
事件队列和microtask
微任务队列; -
而微任务队列的优先级要高于事件队列;
-
所以是优先执行微任务队列中的任务,再执行 事件队列 中的任务
-
一般来说,I/O事件,手势,Timer,绘制,其他外部事件都会放到
event
事件队列中,而微任务通常来源于Dart
内部,并且很少,因为如果微任务太多,可能会阻塞事件队列,而导致一些用户操作可能就没有反应了。 -
两个队列都是按照先进先出的顺序执行任务
Dart的异步操作
- 主要使用
Future
以及async
、await
首先先举个栗子,看一下同步的耗时操作
void main() {
print('start ${DateTime.now()}');
print('${sendRequest()} ${DateTime.now()}');
print('end ${DateTime.now()}');
}
String sendRequest(){
sleep(Duration(seconds: 2));
return 'Request finish';
}
毫无疑问,sendRequest
会阻塞main
函数的执行,可以看到打印start
和Request finish
之间相隔了2秒
使用Future
void main() {
print('start ${DateTime.now()}');
print('${sendRequest()} ${DateTime.now()}');
print('end ${DateTime.now()}');
}
Future<String> sendRequest(){
return Future<String>((){
sleep(Duration(seconds: 2));
return 'Request finish';
});
}
你会看到,sendRequest
丝毫没有阻塞main
函数的执行,直接连续print
了3次,显然这才是我们需要的效果,但是打印的是一个Future实例,那么我们需要拿到最终的结果。
获取Future结果
- 通过.then的回调
void main() {
print('start ${DateTime.now()}');
print('${sendRequest().then((value){
print('$value ${DateTime.now()}');
})} ${DateTime.now()}');
print('end ${DateTime.now()}');
}
执行异常捕获
void main() {
print('start ${DateTime.now()}');
print('${sendRequest().then((value){
print('$value ${DateTime.now()}');
}).catchError((error){
print('$error ${DateTime.now()}');
})} ${DateTime.now()}');
print('end ${DateTime.now()}');
}
Future<String> sendRequest(){
return Future<String>((){
sleep(Duration(seconds: 2));
//return 'Request finish';
throw Exception('出异常了');
});
}
Future的链式调用
- 可以在
then
中继续return
,可以在下一个then
中拿到结果
void main() {
sendRequest().then((value) {
print('11 - $value ${DateTime.now()}');
return '海贼·王路飞';
}).then((value) {
print('22 - $value ${DateTime.now()}');
return '三刀·流索隆';
}).then((value) {
print('33 - $value ${DateTime.now()}');
});
}
Future<String> sendRequest(){
return Future<String>((){
sleep(Duration(seconds: 2));
return 'Request finish';
});
}
其他
Future.value
:返回一个指定值的Future
void main() {
print('start');
Future.value('王路飞').then((value) {
print('$value');
});
print('end');
}
Future.error
:返回一个异常的 Future
void main() {
print('start');
Future.error(Exception('异常了')).then((value) {
print('$value');
});
print('end');
}
image.png
Future.delayed
:延迟执行
void main() {
print('start ${DateTime.now()}');
Future.delayed(Duration(seconds: 2)).then((value) {
print('delayed ${DateTime.now()}');
});
print('end ${DateTime.now()}');
}
当然还有很多api
,可以自行了解
await、async
- 是
Dart
中的关键字,async
声明函数内部有代码需延迟执行,await
声明运算为延迟执行 async
的函数需要返回一个Future
,await
需在async
函数中使用- 实际上是Dart异步编程用于简化异步API操作的,能够将异步的代码使用同步的代码结构实现
举个栗子,如下,sleepTime
的结果,我在sendRequest
里需要拼接一个a
给出去,可能如下写法
void main() {
print('start ');
print(sendRequest().then((value) {
print(value);
}));
print('end ');
}
Future sendRequest() {
final a = ' 12345';
return sleepTime().then((value) {
return value+a;
});
}
///假设异步耗时请求
Future sleepTime(){
return Future((){
sleep(Duration(seconds: 2));
return 'request finish';
});
}
如果用await,async来写 看看
void main() {
print('start ');
print(sendRequest().then((value) {
print(value);
}));
print('end ');
}
Future sendRequest() async{
final a = ' 12345';
var result = await sleepTime();
return result + a;
}
///假设异步耗时请求
Future sleepTime(){
return Future((){
sleep(Duration(seconds: 2));
return 'request finish';
});
}
- 这样可以像写同步代码一样去使用
Future
异步返回的结果
任务执行顺序
- 顺序:
main函数 -> 微任务队列(Microtask Queue)-> 事件队列(Event Queue)
- 可以通过
scheduleMicrotask
创建一个微任务,Future
是加到事件队列 - 那么来看看执行顺序
void main() {
Future(() => print("event task"));
print('start ');
scheduleMicrotask((){
print('scheduleMicrotask');
});
print('end ');
}
- 可以看到main函数里的
start
和end
先打印了,然后打印了微任务scheduleMicrotask
,最后打印了event task
。
Future代码的加入队列
Future
代码分为构造函数的函数体和(.then/.catchError等
)
Future
构造函数的函数体是放在事件队列中.then
的函数体分成几种情况
-
.then
函数体会跟着构造函数体放进事件队列 -
Future
链式调用,.then
函数体会依次放入事件队列 -
Future
里或者.then
里使用微任务,微任务会在Future
完成后才调用来分析一个复杂的情况,彻底搞清楚任务执行顺序
void main() {
print("main start");
Future(() => print("task1"));
final future = Future(() => null);
Future(() => print("task2")).then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
}).then((_) => print("task5"));
future.then((_) {
print('task6');
Future(() => print('task7')).then((value) {
Future(() => print('task8'));
});
}).then((value) {
print('task9');
});
scheduleMicrotask(() => print('task10'));
Future(() => print('task11')).then((_) {
Future(() => print('task12')).then((value) {
Future(() => print('task13'));
});
}).then((_) => print('task14'));
Future(() => print('task15'));
print("main end");
}
用下面的图,捋了一下。
从图中可以梳理出打印顺序为:
- `main start`
- `main end`
- `task10`
- `task1`
- `task6`
- `task9`
- `task2`
- `task3`
- `task5`
- `task4`
- `task11`
- `task14`
- `task15`
- `task7`
- `task12`
- `task8`
- `task13`
与实际打印结果一致
isolate
- 我们知道
Dart
是单线程的,在Dart
中其实有个专门名词叫isolate(隔离) Flutter
中就有一个Root isolate
, 在Root isolate
内部运行一个EventLoop
事件循环- 用官方的话来说
isolate
就是一个隔离的Dart
执行上下文 - Dart是支持多个
isolate
的,isolate
之间不共享任何资源,只能依靠消息机制通信 - 如果确实有非常耗时的操作,可以自己创建
isolate
创建isolate
void main() {
print(Isolate.current.hashCode);
Isolate.spawn(testIsolate, "new Isolate");
}
void testIsolate(String msg) {
print("新的isolate:$msg - ${Isolate.current.hashCode}");
}
通信
void main() {
print(Isolate.current.hashCode);
// 创建管道
ReceivePort receivePort= ReceivePort();
Future iso = Isolate.spawn(testIsolate, receivePort.sendPort,debugName: '哈哈哈');
iso.then((value) {
receivePort.listen((data) {
print('receiveData:$data');
//关闭管道
receivePort.close();
// 杀死isolate
value.kill(priority: Isolate.immediate);
});
});
}
void testIsolate(SendPort port) {
print("新的isolate - ${Isolate.current.debugName} ${Isolate.current.hashCode}");
port.send("Hello World");
}
compute(Flutter对Isolate的封装,需要在Flutter中使用)
@override
Widget build(BuildContext context) {
testCompute();
return MaterialApp(
home: Container(),
);
}
void testCompute() async {
String result = await compute(sendMesg, '哈哈哈');
print('$result');
}
static String sendMesg(String msg) {
print(msg);
return '收到了- 回你'+msg;
}
事件监听
在大前端的开发中,必然存在各种各样和用户交互的情况:比如手指点击、手指滑动、双击、长按等等。
在Flutter中,手势有两个不同的层次:
- 第一层:原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。
- 第二层:手势识别(Gesture Detector):这个是在原始事件上的一种封装。
- 比如我们要监听用户长按,如果自己封装原始事件我们需要监听从用户按下到抬起的时间来判断是否是一次长按事件;
- 比如我们需要监听用户双击事件,我们需要自己封装监听用户两次按下抬起的时间间隔;
- 幸运的是各个平台几乎都对它们进行了封装,而Flutter中的手势识别就是对原始指针事件的封装;
- 包括哪些手势呢?比如点击、双击、长按、拖动等
2.1. 指针事件Pointer
Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:
PointerDownEvent
指针在特定位置与屏幕接触PointerMoveEvent
指针从屏幕的一个位置移动到另外一个位置PointerUpEvent
指针与屏幕停止接触PointerCancelEvent
指针因为一些特殊情况被取消
Pointer的原理是什么呢?
-
在指针落下时,框架做了一个 hit test 的操作,确定与屏幕发生接触的位置上有哪些Widget以及分发给最内部的组件去响应;
-
事件会沿着最内部的组件向组件树的根冒泡分发;
-
并且不存在用于取消或者停止指针事件进一步分发的机制;
原始指针事件使用Listener来监听:
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Listener(
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
onPointerDown: (event) => print("手指按下:$event"),
onPointerMove: (event) => print("手指移动:$event"),
onPointerUp: (event) => print("手指抬起:$event"),
),
);
}
}
2.2. 手势识别Gesture
Gesture是对一系列Pointer的封装,官方建议开发中尽可能使用Gesture,而不是Pointer
建议使用Gesture
Gesture分层非常多的种类:
点击:
- onTapDown:用户发生手指按下的操作
- onTapUp:用户发生手指抬起的操作
- onTap:用户点击事件完成
- onTapCancel:事件按下过程中被取消
双击:
- onDoubleTap:快速点击了两次
长按:
- onLongPress:在屏幕上保持了一段时间
纵向拖拽:
- onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动;
- onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动;
- onVerticalDragEnd:指针和屏幕产生接触结束;
横线拖拽:
- onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动;
- onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动;
- onHorizontalDragEnd:指针和屏幕产生接触结束;
移动:
- onPanStart:指针和屏幕产生接触并可能开始横向移动或者纵向移动。如果设置了
onHorizontalDragStart
或者onVerticalDragStart
,该回调方法会引发崩溃; - onPanUpdate:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了
onHorizontalDragUpdate
或者onVerticalDragUpdate
,该回调方法会引发崩溃。 - onPanEnd:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了
onHorizontalDragEnd
或者onVerticalDragEnd
,该回调方法会引发崩溃。
从Widget的层面来监听手势,我们需要使用:GestureDetector
- 当然,我们也可以使用RaisedButton、FlatButton、InkWell等来监听手势
- globalPosition用于获取相对于屏幕的位置信息
- localPosition用于获取相对于当前Widget的位置信息
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("手势测试"),
),
body: GestureDetector(
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
onTap: () {
},
onTapDown: (detail) {
print(detail.globalPosition);
print(detail.localPosition);
},
onTapUp: (detail) {
print(detail.globalPosition);
print(detail.localPosition);
}
),
);
}
}
image-20200317172326395
二. 跨组件事件
在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。
其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:
- EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
- 这个EventBus我们可以自己实现,也可以使用第三方的EventBus;
这里我们直接选择第三方的EventBus:
dependencies:
event_bus: ^1.1.1
第一:我们需要定义一个希望在组件之间传递的对象:
- 我们可以称之为一个时间对象,也可以是我们平时开发中用的模型对象(model)
class UserInfo {
String nickname;
int level;
UserInfo(this.nickname, this.level);
}
第二:创建一个全局的EventBus对象
final eventBus = EventBus();
第三:在某个Widget中,发出事件:
class HYButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("HYButton"),
onPressed: () {
final info = UserInfo("why", 18);
eventBus.fire(info);
},
);
}
}
第四:在某个Widget中,监听事件
class HYText extends StatefulWidget {
@override
_HYTextState createState() => _HYTextState();
}
class _HYTextState extends State<HYText> {
String message = "Hello Coderwhy";
@override
void initState() {
super.initState();
eventBus.on<UserInfo>().listen((data) {
setState(() {
message = "${data.nickname}-${data.level}";
});
});
}
@override
Widget build(BuildContext context) {
return Text(message, style: TextStyle(fontSize: 30),);
}
}
最后福利
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
![](https://i-blog.csdnimg.cn/blog_migrate/925ee9d2e647142287b5aadd2b9d9181.png)