我们小学二年级就学过:
Dart 是单线程的并且 Flutter 依赖于 Dart。
但是如果我们要在主线程做一些计算量大的操作,就必然会阻塞线程,使UI更新卡顿甚至卡死。那怎么办呢?
好消息是 Dart 为我们提供了 isolate,isolate 跟线程差不多,他是 Dart 中的线程。
isolate 与线程的区别就是线程与线程之间是共享内存的,而 isolate 和 isolate 之间是不共享的,所以叫 isolate (隔离)。
在flutter 里面主线程就是主 isolate 。如果我们要进行一些大计算量的操作就应该启动一个新的 isolate。
那么应该如何来开启呢?在此之前我想讲个故事。
小红与小蓝的故事
有个舞者叫小红,她正在给观众跳舞。
跳舞的小红但是观众却要求她一边跳舞一边计算一个数字里面有多少个偶数。于是。。。
异步计算这那行啊!你必须给我一边跳一边算,算的时候不能停下来!
于是小红没办法,决定在异世界召唤一个小蓝来帮她计算。
image但是小红和小蓝被异世界的屏障隔离,她们也没有思想共通的超能力。只能在召唤的同时传送一个包裹给小蓝。
小蓝被召唤出来后收到包裹,打开后里面是要计算的数字,就开始计算,但是计算后要怎么把结果告诉小红呢?
上帝做了一个约定,在小红召唤小蓝的时候,会变一个传送装置(传送装置可以用来接收包裹,还可以生成一个专属发送器)。然后把发送器传送给小蓝。
当小蓝被召唤出来后,打开包裹,里面是一个发送器,然后小蓝自己也变一个传送装置,生成一个发送器,然后用小红的发送器把小蓝的发送器发送给小红。发送出去后就坐在传送装置旁边等包裹。
当小红收到小蓝的发送器后就把小蓝的发送器存起来。
当有观众要求小红计算时,就分神一边跳舞,一边生成一个临时传送装置,把要计算的数字和临时发送器打包成一个包裹,然后通过小蓝的发送器发给小蓝,等传送装置出结果。因为不用自己算了,只是等,所以跳舞的时候线条也流畅了,动作也优美了。
说回小蓝这边,小蓝看到传送装置出现了一个包裹,里面是一个临时发送器,还有一个数字。于是小蓝就开始计算。算好了就用临时发送器把数字发送给小红。
小红收到结果后就告诉观众,那个数字有多少个偶数。
故事结束,第一次尝试这样的风格,可能写得有点烂,不过结合代码来看的话,应该还是挺容易理解的。
代码实践
首先我们先让小红跳起舞来。
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小红',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
],
),
),
);
}
接下来让小红计算一个数字里面有多少个偶数。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小红',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
Padding(
padding: EdgeInsets.only(top: 16),
child:
RaisedButton(onPressed: count, child: Text('异步计算偶数的个数')),
),
Text(result)
],
),
),
);
}
int getRandom() {
int a = Random().nextInt(100);
return a + 1000000000;
}
// 异步计算
count() async {
int random = getRandom();
int r = countEven(random);
setState(() {
this.result = '${random.toString()}有${r.toString()}个偶数';
});
}
//计算偶数的个数
int countEven(num num) {
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}
这就是效果
异步计算定义 isolate
我愿称之为召唤小蓝。
首先我们要知道两个类:
ReceivePort
SendPort
ReceivePort 就是故事中的传送装置,而 SendPort 则是发送器。
我们可以通过以下方式创建传送装置和对应的发送器
ReceivePort receive = ReceivePort();
SendPort sender = receive.sendPort;
好的,知道这些就行了。接下来我们定义小蓝。
// 消息包裹,用来存临时发送器和消息
class MessagePackage {
SendPort sender; // 临时发送器
dynamic msg; // 消息
MessagePackage(this.sender, this.msg);
}
// 我是小蓝,负责计算偶数的个数,我必须是顶级函数
blueCounter(SendPort redSendPort) {
// 创建小蓝的传送装置
ReceivePort blueReceivePort = ReceivePort();
// 用小红的发送器把小蓝的发送器发送出去
redSendPort.send(blueReceivePort.sendPort);
// 监听小蓝的传送装置,等待小红叫小蓝计算
blueReceivePort.listen((package) {
// 这里的msg是dynamic,需要转换成 MessagePackage 类,上面自己定义的包裹封装类
MessagePackage _msg = package as MessagePackage;
// 小蓝开始计算
int r = countEven(_msg.msg as num);
// 计算好了用小红的临时发送器告诉小红
_msg.sender.send(r);
});
}
创建isolate
工具人小蓝定义好了,我们去初始化(召唤)一下小蓝。
// 创建isolate
void createIsolate() async {
// 创建小红的接收器,用来接收小蓝的发送器
ReceivePort redReceive = ReceivePort();
// 创建 isolate, 并且把小红的发送器传给小蓝
isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
// 等待小蓝把发送器发送给小红
blueSender = await redReceive.first;
// 不用了记得关闭接收器
redReceive.close();
}
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
// 在initState中初始化isolate
createIsolate();
super.initState();
}
现在小蓝已经被召唤了出来,并且和小红建立了通信。
使isolate 开始计算
接下来我们就让小红开始计算吧。
@override
Widget build(BuildContext context) {
...
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: isolateCount, child: Text('isolate计算偶数的个数')
),
),
...
}
// 开启isolate计算
isolateCount() async {
// 获取要计算的数字
int random = getRandom();
// 创建一个临时传送装置
ReceivePort _temp = ReceivePort();
// 用小蓝的发送装置发送一个消息包裹,里面是临时传送装置的发送器和要计算的数字
blueSender.send(MessagePackage(_temp.sendPort, random));
// 等待临时传送装置返回计算结果
int r = await _temp.first;
// 不用了记得关闭临时接收器
_temp.close();
// 把计算结果告诉观众
setState(() {
this.result = '${random.toString()}有${r.toString()}个偶数';
});
}
需要注意的是当使用完了 isolate 记得要销毁。
@override
void dispose() {
// 销毁 isolate
isolate?.kill(priority: Isolate.immediate);
super.dispose();
}
OK,到这里相信你已经看懂并会使用 isolate 了。
我们来看看效果图。
使用 computed
到这里还没完,也许你会觉得太麻烦了。是的这样用 isolate 太麻烦了,isolate 被设计成可以多次输入输出,而我们做这个计算只有一次输入和输出,那么我们就可以用 flutter 为我们提供的 computed 来完成计算操作,它是对 isolate 的一个封装。下面看看怎么用吧!敲简单的。
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'isolate Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'isolate Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
String result = '';
SendPort blueSender;
Isolate isolate;
@override
void initState() {
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = Tween<double>(begin: 0, end: pi * 2).animate(controller);
controller.repeat();
// 在initState中初始化isolate
createIsolate();
super.initState();
}
@override
void dispose() {
// 销毁 isolate
isolate?.kill(priority: Isolate.immediate);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: animation,
child: Text(
'小红',
style: TextStyle(fontSize: 30, color: Colors.red),
),
builder: (context, child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
}),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(onPressed: count, child: Text('异步计算偶数的个数')),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: isolateCount, child: Text('isolate计算偶数的个数')),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: RaisedButton(
onPressed: computeCount, child: Text('compute计算偶数的个数')),
),
Text(result)
],
),
),
);
}
// 获取随机数
int getRandom() {
int a = Random().nextInt(100);
return a + 1000000000;
}
// 异步计算
count() async {
int random = getRandom();
int r = countEven(random);
setState(() {
this.result = '${random.toString()}有${r.toString()}个偶数';
});
}
// 创建isolate
void createIsolate() async {
// 创建小红的接收器,用来接收小蓝的发送器
ReceivePort redReceive = ReceivePort();
// 创建 isolate, 并且把小红的发送器传给小蓝
isolate = await Isolate.spawn<SendPort>(blueCounter, redReceive.sendPort);
// 等待小蓝把发送器发送给小红
blueSender = await redReceive.first;
// 不用了记得关闭接收器
redReceive.close();
}
// 利用compute计算
computeCount() async {
int random = getRandom();
// compute 的回调函数必须是顶级函数或者static函数
int r = await compute(countEven, random);
setState(() {
this.result = '${random.toString()}有${r.toString()}个偶数';
});
}
// 开启isolate计算
isolateCount() async {
// 获取要计算的数字
int random = getRandom();
// 创建一个临时传送装置
ReceivePort _temp = ReceivePort();
// 用小蓝的发送装置发送一个消息包裹,里面是临时传送装置的发送器和要计算的数字
blueSender.send(MessagePackage(_temp.sendPort, random));
// 等待临时传送装置返回计算结果
int r = await _temp.first;
_temp.close();
// 把计算结果告诉观众
setState(() {
this.result = '${random.toString()}有${r.toString()}个偶数';
});
}
}
// 消息包裹,用来存临时发送器和消息
class MessagePackage {
SendPort sender; // 临时发送器
dynamic msg; // 消息
MessagePackage(this.sender, this.msg);
}
// 我是小蓝,负责计算偶数的个数,我必须是顶级函数
blueCounter(SendPort redSendPort) {
// 创建小蓝的传送装置
ReceivePort blueReceivePort = ReceivePort();
// 用小红的发送器把小蓝的发送器发送出去
redSendPort.send(blueReceivePort.sendPort);
// 监听小蓝的传送装置,等待小红叫小蓝计算
blueReceivePort.listen((package) {
// 这里的msg是dynamic,需要转换成 MessagePackage 类,上面自己定义的包裹封装类
MessagePackage _msg = package as MessagePackage;
// 小蓝开始计算
int r = countEven(_msg.msg as num);
// 计算好了用小红的临时发送器告诉小红
_msg.sender.send(r);
});
}
//计算偶数的个数,此函数需要大量的计算资源和时间
int countEven(num num) {
int count = 0;
while (num > 0) {
if (num % 2 == 0) {
count++;
}
num--;
}
return count;
}
网友评论