Flutter 架构
Flutter框架分三层
Framework,Engine, Embedder
17849932-9b65096d82b2c069.jpg
Framework使用dart语言实现,包括UI,文本,图片,按钮等Widgets,渲染,动画,手势等。此部分的核心代码是flutter仓库下的flutter package,以及sky_engine仓库下的 io, async, ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。
Engine使用C++实现,主要包括:Skia, Dart 和 Text。
Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。其已作为Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS等其他众多产品的图形引擎,支持平台还包括Windows, macOS, iOS,Android,Ubuntu等。
Dart 部分主要包括:Dart Runtime,Garbage Collection(GC),如果是Debug模式的话,还包括JIT(Just In Time)支持。Release和Profile模式下,是AOT(Ahead Of Time)编译成了原生的arm代码,并不存在JIT部分。
Text 即文本渲染,其渲染层次如下:衍生自 Minikin的libtxt库(用于字体选择,分隔行);HartBuzz用于字形选择和成型;Skia作为渲染/GPU后端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics来渲染字体。
Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去,Embedder的主要工作包括渲染Surface设置, 线程设置,以及插件等。平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。
Flutter的线程管理
Flutter Engine自己不创建和管理线程。Flutter Engine线程的创建和管理是由Embedder负责的
Embedder提供四个Task Runner, 每个Task Runner负责不同的任务,Flutter Engine不在乎Task Runner具体跑在哪个线程,但是它需要线程配置在整一个生命周期里面保持稳定。也就是说一个Runner最好始终保持在同一线程运行
1568944133399-27d39b24-ab41-43c9-b41c-7f04748b8a14.png
Platform Task Runner
Flutter Engine的主Task Runner,运行Platform Task Runner的线程可以理解为是主线程。类似于Android Main Thread或者iOS的Main Thread。但是我们要注意Platform Task Runner和iOS之类的主线程还是有区别的。
对于Flutter Engine来说Platform Runner所在的线程跟其它线程并没有实质上的区别,只不过我们人为赋予它特定的含义便于理解区分。实际上我们可以同时启动多个Engine实例,每个Engine对应一个Platform Runner,每个Runner跑在各自的线程里。这也是Fuchsia(Google正在开发的操作引擎)里Content Handler的工作原理。一般来说,一个Flutter应用启动的时候会创建一个Engine实例,Engine创建的时候会创建一个线程供Platform Runner使用。
跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread,试图在其它线程中调用Flutter Engine会导致无法预期的异常。这跟iOS UI相关的操作都必须在主线程进行相类似。需要注意的是在Flutter Engine中有很多模块都是非线程安全的。一旦引擎正常启动运行起来,所有引擎API调用都将在Platform Thread里发生。
Platform Runner所在的Thread不仅仅处理与Engine交互,它还处理来自平台的消息。这样的处理比较方便的,因为几乎所有引擎的调用都只有在Platform Thread进行才能是安全的,Native Plugins不必要做额外的线程操作就可以保证操作能够在Platform Thread进行。如果Plugin自己启动了额外的线程,那么它需要负责将返回结果派发回Platform Thread以便Dart能够安全地处理。规则很简单,对于Flutter Engine的接口调用都需保证在Platform Thread进行。
需要注意的是,阻塞Platform Thread不会直接导致Flutter应用的卡顿(跟iOS android主线程不同)。尽管如此,平台对Platform Thread还是有强制执行限制。所以建议复杂计算逻辑操作不要放在Platform Thread而是放在其它线程(不包括我们现在讨论的这个四个线程)。其他线程处理完毕后将结果转发回Platform Thread。长时间卡住Platform Thread应用有可能会被系统Watchdog强行杀死。
UI Task Runner Thread(Dart Runner)
UI Task Runner被Flutter Engine用于执行Dart root isolate代码(isolate我们后面会讲到,姑且先简单理解为Dart VM里面的线程)。Root isolate比较特殊,它绑定了不少Flutter需要的函数方法。Root isolate运行应用的main code。引擎启动的时候为其增加了必要的绑定,使其具备调度提交渲染帧的能力。对于每一帧,引擎要做的事情有:
-
Root isolate通知Flutter Engine有帧需要渲染。
-
Flutter Engine通知平台,需要在下一个vsync的时候得到通知。
-
平台等待下一个vsync
-
对创建的对象和Widgets进行Layout并生成一个Layer Tree,这个Tree马上被提交给Flutter Engine。当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述。
-
创建或者更新Tree,这个Tree包含了用于屏幕上显示Widgets的语义信息。这个东西主要用于平台相关的辅助Accessibility元素的配置和渲染。
除了渲染相关逻辑之外Root Isolate还是处理来自Native Plugins的消息响应,Timers,Microtasks和异步IO。 我们看到Root Isolate负责创建管理的Layer Tree最终决定什么内容要绘制到屏幕上。因此这个线程的过载会直接导致卡顿掉帧。 如果确实有无法避免的繁重计算,建议将其放到独立的Isolate去执行,比如使用compute关键字或者放到非Root Isolate,这样可以避免应用UI卡顿。但是需要注意的是非Root Isolate缺少Flutter引擎需要的一些函数绑定,你无法在这个Isolate直接与Flutter Engine交互。所以只在需要大量计算的时候采用独立Isolate。
GPU Task Runner
GPU Task Runner被用于执行设备GPU的相关调用。UI Task Runner创建的Layer Tree信息是平台不相关,也就是说Layer Tree提供了绘制所需要的信息,具体如何实现绘制取决于具体平台和方式,可以是OpenGL,Vulkan,软件绘制或者其他Skia配置的绘图实现。GPU Task Runner中的模块负责将Layer Tree提供的信息转化为实际的GPU指令。GPU Task Runner同时也负责配置管理每一帧绘制所需要的GPU资源,这包括平台Framebuffer的创建,Surface生命周期管理,保证Texture和Buffers在绘制的时候是可用的。
基于Layer Tree的处理时长和GPU帧显示到屏幕的耗时,GPU Task Runner可能会延迟下一帧在UI Task Runner的调度。一般来说UI Runner和GPU Runner跑在不同的线程。存在这种可能,UI Runner在已经准备好了下一帧的情况下,GPU Runner却还正在向GPU提交上一帧。这种延迟调度机制确保不让UI Runner分配过多的任务给GPU Runner。
前面我们提到GPU Runner可以导致UI Runner的帧调度的延迟,GPU Runner的过载会导致Flutter应用的卡顿。一般来说用户没有机会向GPU Runner直接提交任务,因为平台和Dart代码都无法跑进GPU Runner。但是Embeder还是可以向GPU Runner提交任务的。因此建议为每一个Engine实例都新建一个专用的GPU Runner线程。
IO Task Runner
前面讨论的几个Runner对于执行任务的类型都有比较强的限制。Platform Runner过载可能导致系统WatchDog强杀,UI和GPU Runner过载则可能导致Flutter应用的卡顿。但是GPU线程有一些必要操作是比较耗时间的,比如IO,而这些操作正是IO Runner需要处理的。
IO Runner的主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备。在Texture的准备过程中,IO Runner首先要读取压缩的图片二进制数据(比如PNG,JPEG),将其解压转换成GPU能够处理的格式然后将数据上传到GPU。这些复杂操作如果跑在GPU线程的话会导致Flutter应用UI卡顿。但是只有GPU Runner能够访问GPU,所以IO Runner模块在引擎启动的时候配置了一个特殊的Context,这个Context跟GPU Runner使用的Context在同一个ShareGroup。事实上图片数据的读取和解压是可以放到一个线程池里面去做的,但是这个Context的访问只能在特定线程才能保证安全。这也是为什么需要有一个专门的Runner来处理IO任务的原因。获取诸如ui.Image
这样的资源只有通过async call,当这个调用发生的时候Flutter Framework告诉IO Runner进行刚刚提到的那些图片异步操作。这样GPU Runner可以使用IO Runner准备好的图片数据而不用进行额外的操作。
用户操作,无论是Dart Code还是Native Plugins都是没有办法直接访问IO Runner。尽管Embeder可以将一些一般复杂任务调度到IO Runner,这不会直接导致Flutter应用卡顿,但是可能会导致图片和其它一些资源加载的延迟间接影响性能。所以建议为IO Runner创建一个专用的线程。
Event Loop
和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程称为isolate。应用程序启动后,开始执行main函数并运行main isolate。
每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列,event和microtask队列有点类似iOS的source0和source1。iOS开发中的RunLoop
- event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。
-
microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。
2e42a13b1eeb2ed673350e7adca16c40.png
Dart事件循环执行如图所示
先查看microtask queue是否为空,不是则先执行microtask queue
一个microtask执行完后,检查有没有下一个microtask,直到microtask queue为空,才去执行event queue
在event queue取出一个事件处理完后,再次返回第一步,去检查microtask queue是否为空
注意:我们可以看出,将任务加入到microtask queue中可以被尽快执行,但也需要注意,当事件循环在处理microtask queue时,event queue会被卡住,应用程序无法处理手势+单击、I/O消息等等事件。
Isolate
可以把它理解为Dart中的线程。但它又不同于线程,它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。
1568944133391-04a7f7f6-cd89-4953-b1f1-30da235aadb7.png
Isolate 类是用来管理线程 包括创建spawn / spawnUri , 暂停 pause, 杀死 kill
Future<Isolate> Isolate.spawn(entryPoint, message)
- entryPoint(必须是一个顶层方法或静态方法)
- message
•① Dart 原始数据类型,如null. bool int. double、string 等
•② Sendport 实例 - ReceivePort().sendPort
•包含①和 ②的list 和 map,也可以嵌套
void multiThread() {
print('multi thread start');
print('当前线程: ${Isolate.current.debugName}');
Isolate.spawn(newThread1, 'hello1');
Isolate.spawn(newThread2, 'hello2');
Isolate.spawn(newThread3, 'hello3');
print('multi thread end');
}
void newThread1(String message) {
print('当前线程: ${Isolate.current.debugName}');
print(message);
}
void newThread2(String message) {
print('当前线程: ${Isolate.current.debugName}');
print(message);
}
void newThread3(String message) {
print('当前线程: ${Isolate.current.debugName}');
print(message);
}
多次运行的话 会发现因为是异步执行 这些打印输出的顺序是不固定的
multi thread start
当前线程: main
当前线程: newThread1
hello1
multi thread end
当前线程: newThread2
hello2
当前线程: newThread3
hello3
lsolate 多线程之间,通信的唯一方式是 Port
- ReceivePort 类: 初始化接收端口,创建发送端口,接受消息,监听消息,关闭端口
-
SendPort 类: 将消息发送给 ReceivePort
通信方式
•单向通信(A->B)
•双向通信 (A<>B)
Isolate单向通信.png
Isolate双向通信.png
单向通信
void multiThread() {
print('multiThread start');
print('当前线程: ${Isolate.current.debugName}');
ReceivePort r1 = ReceivePort();
SendPort p1 = r1.sendPort;
Isolate.spawn(newThread1, p1);
// 接收新线程发送过来的消息
// var msg = r1.first;
// print('来自新线程的消息: $msg');
// r1.first.then((value) => print('来自新线程的消息: $value'));
// r1.listen((value) {
// print('来自新线程的消息: $value');
// // 调用close可以把当前监听关闭,如果不关闭则会一直监听
// r1.close();
// });
r1.forEach((element) {
print('来自新线程的消息: $element');
});
print('multiThread end');
}
void newThread1(SendPort p1) {
print('当前线程: ${Isolate.current.debugName}');
// 发送消息给main线程
p1.send('abc');
}
multiThread start
当前线程: main
当前线程: newThread1
multiThread end
来自新线程的消息: abc
双向通信
void multiThread() async {
print('multiThread start');
print('当前线程: ${Isolate.current.debugName}');
ReceivePort r1 = ReceivePort();
SendPort p1 = r1.sendPort;
Isolate.spawn(newThread, p1);
SendPort p2 = await r1.first;
// p2.send('来自主线程的消息');
var msg = await sendToReceive(p2, 'hello');
print('主线程接收到的消息:$msg');
var msg2 = await sendToReceive(p2, 'dart');
print('主线程接收到的消息:$msg2');
var msg3 = await sendToReceive(p2, 'from main');
print('主线程接收到的消息:$msg3');
print('multiThread end');
}
void newThread(SendPort p1) async {
print('当前线程: ${Isolate.current.debugName}');
// 发送消息给main线程
// p1.send('abc');
ReceivePort r2 = ReceivePort();
SendPort p2 = r2.sendPort;
p1.send(p2);
// r2.listen((message) {
// print(message);
// });
await for (var item in r2) {
var data = item[0];
print('新线程接收到了来自主线程的消息:$data');
SendPort replyPort = item[1];
// 给主线程回复消息
replyPort.send('I get $data');
}
}
Future sendToReceive(SendPort port, msg) {
print('发送消息给新线程:$msg');
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
multiThread start
当前线程: main
当前线程: newThread
发送消息给新线程:hello
新线程接收到了来自主线程的消息:hello
主线程接收到的消息:I get hello
发送消息给新线程:dart
新线程接收到了来自主线程的消息:dart
主线程接收到的消息:I get dart
发送消息给新线程:from main
新线程接收到了来自主线程的消息:from main
主线程接收到的消息:I get from main
multiThread end
SpawnUri
Future<Isolate>spawnUri(uri, args, message)
spawnUri方法有三个必须的参数,
- 第一个是Uri,指定一个新Isolate代码文件的路径,
- 第二个是参数列表
- message
需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,该main函数中的args参数列表,正对应spawnUri中的第二个参数。如不需要向新Isolate中传参数,该参数可传空List
主Isolate中的代码:
void main(List<String> args) {
start();
newIsolate();
init();
}
newIsolate() async {
print("新线程创建");
ReceivePort r = ReceivePort();
SendPort p = r.sendPort;
//执行文件所在路径
Isolate childIsolate = await Isolate.spawnUri(
Uri(path: "./lib/child_isolate.dart"), ["date1", "date1"], p);
Isolate.spawnUri(uri, args, message)
r.listen((message) {
print("主线程接收到的数据 ${message[0]}");
if (message[1] == 2) {
r.close(); // 数据接收完成 关闭通道
childIsolate.kill(); // 杀死新线程,释放资源
}
});
}
start() {
print("应用启动 ${DateTime.now().microsecondsSinceEpoch.toString()}");
}
init() {
print("项目初始化");
}
子Isolate中的代码:
void main(List<String> args, SendPort mainSendPort) {
print("新线程接收到的参数 ${args}");
mainSendPort.send(['开始执行异步操作', 0,]);
sleep(Duration(seconds: 1));
mainSendPort.send(['加载中', 1,]);
sleep(Duration(seconds: 1));
mainSendPort.send(['异步完成', 2,]);
}
运行输出
multiThread start
当前线程: main
当前子线程: newThread
发送消息给新线程:hello
新线程接收到了来自主线程的消息:hello
主线程接收到的消息:I get hello
发送消息给新线程:dart
新线程接收到了来自主线程的消息:dart
主线程接收到的消息:I get dart
发送消息给新线程:from main
新线程接收到了来自主线程的消息:from main
主线程接收到的消息:I get from main
multiThread end
Flutter Engine Runners与Dart Isolate
既然Flutter Engine有自己的Runner,那为何还要Dart的Isolate呢
Dart的Isolate是Dart虚拟机自己管理的,Flutter Engine无法直接访问。Root Isolate通过Dart的C++调用能力把UI渲染相关的任务提交到UI Runner执行这样就可以跟Flutter Engine相关模块进行交互,Flutter UI相关的任务也被提交到UI Runner也可以相应的给Isolate一些事件通知,UI Runner同时也处理来自App方面Native Plugin的任务。
所以简单来说Dart isolate跟Flutter Runner是相互独立的,他们通过任务调度机制相互协作。
Isolate的消耗
在时间上, 通常来说,当我们使用多线程计算的时候,整个计算的时间会比单线程要多,额外的耗时是
- 创建 Isolate
- Copy Message
- 销毁 Isolate
我们是在 Main 线程之外的 isolate 请求的数据,在该线程进行解析,最后传回 Main Isolate,经历了一次 isolate 的创建以及销毁过程。这将会耗费约 50-150ms 的时间
在空间上, Isolate 实际上是比较重的,每当我们创建出来一个新的 Isolate 至少需要 2mb 左右的空间甚至更多.
dart team 已经为我们写好一些非常实用的 package,其中就包括 LoadBalancer
LoadBalancer
在 pubspec.yaml 中添加 isolate 的依赖。
isolate: ^2.0.2
具体使用如下
import 'package:isolate/isolate.dart';
Future<int> useLoadBalancer() async {
//可以通过 LoadBalancer 创建出指定个数的 isolate
Future<LoadBalancer> loadBalancer = LoadBalancer.create(1, IsolateRunner.spawn);
final lb = await loadBalancer;
int res = await lb.run<int, int>(_doSomething1, 1);
return res;
}
Future<int> _doSomething(int v){
return Future.delayed(Duration(seconds: 2), () {
print("doSmoething");
return 22;
});
}
int _doSomething1(int v){
print("doSmoething1 $v");
return 2222;
}
void main(List<String> args) async {
final v = await useLoadBalancer();
print(v);
}
控制台输出为
doSmoething1 1
2222
我们关注的只有 Future<R> run<R, P>(FutureOr<R> function(P argument), argument, 方法。我们还是需要传入一个 function 在某个 isolate 中运行,并传入其参数 argument。run 方法将会返回我们执行方法的返回值。
需要注意的是,使用时只能传递一个参数,返回值也只能有一个
我们可以通过 LoadBalancer 创建出指定个数的 isolate
LoadBalancer将会创建出一个 isolate 线程池,并自动实现了负载均衡
当我们多次使用额外的 isolate 的时候,不再需要重复创建了。
并且 LoadBalancer 还支持 runMultiple,可以让一个方法在多线程中执行
Compute
由于dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作
要使用compute,必须注意的有两点
第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,
第二个参数为动态的消息类型,可以是被运行函数的参数。
compute传参,只能传递一个参数,返回值也只有一个, 这一点与LoadBalancer一样
import 'package:flutter/foundation.dart';
int func2(int v) {
print("子进程收到参数:$v");
print("子进程执行耗时操作");
var sum = 0;
for (int i = 0; i < 9999999; i++) {
sum = i;
}
return sum;
}
int func1(int v1, int v2){
print("子进程收到参数:$v1 $v2");
print("子进程执行耗时操作");
var sum = 0;
for (int i = 0; i < 9999999; i++) {
sum = i;
}
return sum;
}
List<String> func3(List<String> v){
print("子进程收到参数:$v");
print("子进程执行耗时操作");
var sum = 0;
for (int i = 0; i < 9999999; i++) {
sum = i;
}
return ['$sum'];
}
void computeTest() async {
print('外部代码1');
// 这里需要使用外部方法,不能使用匿名函数,不能使用闭包
// 只能传递一个参数,返回值也只有一个
// var result = await compute(func1, 10); //超过一个传参会报错
// var result = await compute(func3, ['10']);
var result = await compute(func2, 10);
print("执行完成:$result");
print('外部代码2');
}
void main(List<String> args) async {
computeTest();
}
控制台输出为
I/flutter ( 5243): 外部代码1
I/flutter ( 5243): 子进程收到参数:10
I/flutter ( 5243): 子进程执行耗时操作
I/flutter ( 5243): 执行完成:9999998
I/flutter ( 5243): 外部代码2
compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的Isolate
Future
Future 是 Dart 中的类,我们可以通过 Future 实例,封装一些异步任务
Future 的含义是未来。未来要执行的一些任务,我们可以放到Future中
Future 有三种状态
•未完成 (Uncompleted)
•已完成,并返回数据 (Completed with data)
•已完成,但返回报错 (Completed with error)
Future 状态相关的返回
- 创建
• Uncompleted - then()
• Completed with data - catchError()
• Completed with error - whenComplete()
• Completed with data + Completed with error
Future 的执行顺序
Future 默认是异步任务,会被丢到事件队列 (event queue)中
- Future.sync()
•同步任务,同步执行(不会被丢到异步队列中) - Future.microtask()
•微任务,会丟到microtask queue 中,优先级比事件任务高 - Future.value(val)
•val是常量(等同于 microtask)
•val 是异步(按照异步逻辑处理)
获取 Future 实例
//自动返回
final myFuture = http.get('https://my.image.url');
final myFuture = SharedPreferences.getInstance;
//手动创建
final myFuture = Future(() { return 123; })
final myFuture = Future.error(Exception());
delayed
// 指定延迟时间
Future.delayed(Duration(seconds: 2), () {
// 回调函数
// throw Error();
return 123;
}).then((value) => print(value)).catchError((error) {
print("报错:$error");
}, test:(error) => error.runtimeType == String,
).whenComplete(() {
print("完成了 无论失败成功");
});
microtask
print("start");
Future((() => print("Future task"))); // 宏任务
Future.value("Future.value").then((value) => print(value)); //微任务
Future.microtask(() => print("Future.microtask")); //微任务
Future.sync(() => print("Future.sync")); // 同步任务
Future.value(Future((() => print("Future.value.Future")))); //宏任务
print("end");
控制台输出为
start
Future.sync
end
Future.value
Future.microtask
Future task
Future.value.Future
any 和 wait
final f1 = Future.delayed(Duration(seconds: 4), (() => 1));
final f2 = Future.delayed(Duration(seconds: 3), (() => 2));
final f3 = Future.delayed(Duration(seconds: 2), (() => 3));
final f4 = Future.delayed(Duration(seconds: 5), (() => 4));
//返回最先完成的future结果
// Future.any([f1, f2, f3, f4]).then((value) {
// print(value);
// });
//等待所有future执行完成, 并收集所有future的返回结果
Future.wait([f1, f2, f3, f4]).then((value) {
print(value);
});
FutureBuilder
FutureBuilder 是 Flutter SDK 中提供的异步组件。
FutureBuilder 是一个类,接受 Future数据,并将数据渲染成界面
FutureBuilder 中,有三个属性
• future
• initialData
• builder(context, snapshot)
- snapshot.connectionState
• ConnectionState.none(未连接异步任务)
• ConnectionState waitine(连接异步任务,等待交互)
• ConnectionState.active(正在交互)
• Connectionstate.done(异步任务完成) - snapshot.hasData (Completed with data)
• snapshot.data - snapshot.hasError (Completed with error)
FutureBuilder(
future: _getData(), // 异步任务
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
print("waiting");
//未完成状态 数据处理
return null;
case ConnectionState.done:
if (snapshot.hasError) {
print("error");
//报错 数据处理
return null;
} else {
print("done");
//请求完成 数据处理
return snapshot.data;
}
}
});
Isolate与 Future 如何选择
•两者都可以执行异步操作,但逻辑不同
•lsolate 的开销比 Future 要大
•lsolate 需要重新开启线程,Future 是单线程内的异步任务
•异步任务耗时短,推荐使用Future,耗时长,推荐使用 lsolate
•如果使用 Future 来处理耗时长的异步任务,会造成阻塞
•耗时<100ms选Future;耗时>100 毫秒 选 lsolate
Stream
Stream 是 Dart 中的异步数据流,可以连续不断的返回多个数据。
Stream 相关的 API
• listen 进行数据监听
• error 接收失败状态
• done 接收结束状态
Stream的类型
- Single-Subscription(单一订阅)
• 数据流只能被 listen 一次(listen 多次会报错)
• StreamController().stream
• Stream stream = Stream.fromIterable(data) - Broadcast(广播)
• 数据流可以被 listen 多次
• StreamController<int>.broadcast();
• Stream.asBroadcastStream()
Single-Subscription
// 创建一个数据流
final StreamController controller = StreamController();
// 第一次监听
controller.stream.listen((event) {
print("$event");
});
// 第二次监听 会报错
// controller.stream.listen((event) {
// print("$event");
// });
controller.sink.add("单一数据流数据");
controller.sink.add("单一数据流数据22");
controller.sink.add("单一数据流数据2233");
controller.sink.add("单一数据流数据2241234");
controller.sink.add("单一数据流数据231231232");
控制台输出为
单一数据流数据
单一数据流数据22
单一数据流数据2233
单一数据流数据2241234
单一数据流数据231231232
Broadcast
StreamController controller = StreamController.broadcast();
// 第一次监听
controller.stream.listen((event) {
print("11 $event");
});
// 第二次监听
controller.stream.listen((event) {
print("22 $event");
});
controller.sink.add("单一数据流数据");
controller.sink.add("单一数据流数据22");
controller.sink.add("单一数据流数据2233");
controller.sink.add("单一数据流数据2241234");
controller.sink.add("单一数据流数据231231232");
控制台输出为
11 单一数据流数据
22 单一数据流数据
11 单一数据流数据22
22 单一数据流数据22
11 单一数据流数据2233
22 单一数据流数据2233
11 单一数据流数据2241234
22 单一数据流数据2241234
11 单一数据流数据231231232
22 单一数据流数据231231232
Stream的相关操作
• Stream.fromFuture() 创建一个Single-Subscription, 当future返回结果时 做出处理
• Stream.fromFutures() 创建一个Single-Subscription, 当一组future返回结果时 做出处理
• Stream.fromIterable() 从集合中创建一个新的Single-Subscription
• Stream.periodic() 创建流,在周期间隔反复广播事件
具体示例如下
fromFuture
Future<String> getData(int seconds) {
return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
}
void main(List<String> args) {
Stream.fromFuture(getData(2)).listen((event) {
print("fromFuture $event");
}).onDone(() {
print("fromFuture done");
});
}
控制台输出为
fromFuture 2022-10-08 14:13:52.077248
fromFuture done
fromFutures
Future<String> getData(int seconds) {
return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
}
void main(List<String> args) {
Stream.fromFutures([getData(1), getData(3)]).listen((event) {
print("fromFutures $event");
}).onDone(() {
print("fromFutures done");
});
}
控制台输出为
fromFutures 2022-10-08 14:15:15.094200
fromFutures 2022-10-08 14:15:17.090584
fromFutures done
fromIterable
Future<String> getData(int seconds) {
return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
}
void main(List<String> args) {
Stream.fromIterable([getData(1), getData(3)]).listen((event) {
print("fromFutures $event");
}).onDone(() {
print("fromFutures done");
});
}
控制台输出为
fromFutures Instance of 'Future<String>'
fromFutures Instance of 'Future<String>'
fromFutures done
periodic
Future<String> getData(int seconds) {
return Future.delayed(Duration(seconds: seconds), () => "${DateTime.now()}");
}
void main(List<String> args) {
Duration interval = Duration(seconds: 2);
// 不带第二个参数的话 会返回null
// Stream<int> stream = Stream<int>.periodic(interval);
// stream.listen((event) {
// print("periodic $event");
// }).onDone(() {
// print("done");
// });
// 带有第二个参数 返回具体的数据
Stream<int> stream1 = Stream<int>.periodic(interval, (data) => data);
stream1.listen((event) {
print("periodic $event");
}).onDone(() {
print("done");
});
}
控制台输出为
periodic 0
periodic 1
periodic 2
periodic 3
periodic 4
periodic 5
.....
StreamBuilder
StreamBuilder 是 Flutter SDK 中提供的异步组件。
StreamBuilder是一个类,接受 stream 数据,并将数据渲染成界面
StreamBuilder中,有三个属性
• stream
• initialData
• builder(context, snapshot)
snapshot与FutureBuilder相同
StreamBuilder的用法也与FutureBuilder相类似 这里就不赘述
async、await
async和await的本质是协程(coroutine)的语法糖,协程可以让单线程支持异步调度的,减少进程调度带来的开销
- async:标记函数是一个异步函数,其返回值类型是 Future
- await:等待某个异步方法执行完毕
•用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码 - 作用
•await 会等待异步任务执行(相当于将异步转成同步)
•async-await 简化代码,防止回调地狱的产生
getUserInfo() {
return Future.delayed(Duration(seconds: 2), () {
print("UserInfo");
return 1;
});
}
getOrderInfo(int id){
return Future.delayed(Duration(seconds: 2), () {
print("当前用户: $id, OrderInfo");
return 22;
});
}
void main(List<String> args) async {
print("start");
//避免回调地狱
var id = await getUserInfo();
var order = await getOrderInfo(id);
print(order);
print("end");
}
控制台输出为
start
UserInfo
当前用户: 1, OrderInfo
22
end
参考资料
Dart 中的并发
Flutter Engine线程管理与Dart Isolate机制
Dart异步编程
dart学习-- Dart之异步编程
Flutter异步编程
网友评论